Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
59c429a
docs(operations): spec + plan for Operations tab (D, 4 tasks)
pskeshu Jun 28, 2026
95f9126
feat(operations): rename Experiment tab to Operations (label only)
pskeshu Jun 28, 2026
380336f
feat(operations): emit burst phase in BURST_START + record in snapshot
pskeshu Jun 28, 2026
2a3d8b0
feat(operations): tactic band — protocol pill + setpoint markers + ph…
pskeshu Jun 28, 2026
6003b8e
docs(operations): redesign D as operation-spine (data-driven + scenar…
pskeshu Jun 28, 2026
b8da138
feat(operations): discard swimlane tactic-band (replaced by operation…
pskeshu Jun 28, 2026
22d9b06
docs(operations): v3 spec — agent-authored typed Operation Plan (DRAF…
pskeshu Jun 28, 2026
9d0c7d8
docs(operations): v3 plan — agent-authored Operation Plan (5 tasks, b…
pskeshu Jun 28, 2026
192fe97
feat(operations): OperationPlan store domain in FileContextStore
pskeshu Jun 28, 2026
1472dd0
feat(operations): declare_operation_plan typed agent tool
pskeshu Jun 28, 2026
f0d09bd
docs(operations): add execution-linkage tasks (transition_tactic, too…
pskeshu Jun 28, 2026
a75d23f
feat(operations): GET /api/operation_plan/{session} route
pskeshu Jun 28, 2026
84f5f17
docs(operations): add plan-item↔operation linkage tasks (tactical out…
pskeshu Jun 28, 2026
66a40d1
feat(operations): transition_tactic store helper
pskeshu Jun 28, 2026
224d865
feat(operations): link execution tools to plan tactics via tactic_id
pskeshu Jun 28, 2026
c445f6c
docs(sdd): Task 7 report — tactic_id threading
pskeshu Jun 28, 2026
442265f
feat(operations): OperationPlanUpdater — execution events transition …
pskeshu Jun 28, 2026
152837e
feat(operations): plan-item tactical outline (plan tactics with the i…
pskeshu Jun 28, 2026
16a85a3
feat(operations): seed Operation Plan goal + planned tactics from the…
pskeshu Jun 28, 2026
5d16f34
feat(operations): operation-spine renderer + plan scenario library (d…
pskeshu Jun 28, 2026
e65834d
feat(operations): live telemetry binding + event-driven refresh
pskeshu Jun 28, 2026
ab4395c
fix(operations): wire seeding + agent plan guidance + queue_burst gua…
pskeshu Jun 28, 2026
f4ab606
fix(operations): surface bound live.* telemetry, paused state styling…
pskeshu Jun 28, 2026
b98a041
chore: untrack SDD scratch doc accidentally committed
pskeshu Jun 28, 2026
3a2bda6
chore: gitignore .superpowers SDD scratch (untrack accidentally-commi…
pskeshu Jun 28, 2026
a30a7d7
feat(operations): pin plan kind/scope/state schema in guidance + norm…
pskeshu Jun 29, 2026
c2a13e2
feat(operations): set-up-an-operation CTA in the idle state (agent-fi…
pskeshu Jun 29, 2026
a22c7a7
style: ruff check + format — lint clean
pskeshu Jun 29, 2026
723da96
fix(tests): annotate test-stub Optionals for mypy
pskeshu Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,4 @@ gently/ui/tui/dist/
# Stray local storage: GENTLY_STORAGE_PATH default (D:\Gently3) resolves
# literally to ./D:/ under the repo on Linux. Not data we track.
/D:/
.superpowers/
76 changes: 76 additions & 0 deletions docs/superpowers/plans/2026-06-28-operations-tab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Operations — agent-authored Operation Plan (D v3) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use `- [ ]`.

**Goal:** The agent emits a typed Operation Plan (its tactics, planned/active/done); Operations renders it ⊕ live telemetry. Built backend-first (the user-directed, low-rework part), then the renderer.

**Architecture:** A typed `OperationPlan` in `FileContextStore` (new domain), written by a forced-tool agent call, served on a route, rendered by a data-driven operation-spine generalized for tactic kinds, with live-telemetry binding + scenario test mode.

**Tech Stack:** Python (FileContextStore YAML, the `@tool`/forced-`tool_choice` pattern, EventBus `CONTEXT_UPDATED`), FastAPI route, vanilla-JS + SVG renderer, pytest.

## Global Constraints
- Source of truth = the agent's typed plan (NOT a backend reconstruction). Live telemetry binds onto declared tactics; the agent declares tactic identity, the system supplies live values.
- Plan schema (see spec §1): `{session_id,title,goal,tactics:[{id,name,kind,state,scope,rationale,structure,live_bind,relations}],updated_at,updated_reason}`. `kind∈{standing_timelapse,reactive_monitor,scripted_protocol,exclusive_burst,oneshot,custom}`, `state∈{planned,active,done}`. No round-robin.
- Forced typed output mirrors `gently/harness/memory/notebook_ask.py` (ASK_TOOL + `tool_choice={'type':'tool',...}` → validated `block.input`) OR the `@tool` auto-schema (`harness/tools/registry.py`).
- Store mirrors `session_intents`/`active/` domains; fire the existing `CONTEXT_UPDATED`.
- Route mirrors `gently/ui/web/routes/context.py` (`/api/context`).
- Renderer = the validated operation-spine (harness `scratchpad/opsdesign/harness.html`) with the audit fixes (queued reads cocked + "next" marker; active phase/status is the headline; flatten — no card-in-card; whole-left-column amber + colored left edge per state; copy "queued"; mono values). Generalize for the tactic kinds (standing→per-embryo cadence strip; reactive→watch/reaction/status; scripted→phases).
- Frontend: no JS harness → `node --check` + Chrome-MCP audit across scenario fixtures.
- Git hygiene: stage only your files by explicit path; never `git add -A`.

---

### Task 1: OperationPlan model + FileContextStore domain
**Files:** Create/extend `gently/harness/memory/model.py` (an `OperationPlan`/`Tactic` dataclass or a documented dict schema); Modify `gently/harness/memory/file_store.py` (`set_operation_plan(session_id, plan)` / `get_operation_plan(session_id)`, YAML under `agent/operation_plans/{session_id}.yaml`, fire `CONTEXT_UPDATED`). Test: `tests/test_operation_plan_store.py`.
- [ ] Confirm the real `FileContextStore` domain pattern from `set_session_intent`/`create_session_intent` (~file_store.py:758) + the `_notify_*`/`CONTEXT_UPDATED` emit. Mirror it.
- [ ] TDD: set→get round-trip preserves the tactics list + states; `CONTEXT_UPDATED` fired on set. `pytest tests/test_operation_plan_store.py -v`; `pytest -q` clean. Commit `feat(operations): OperationPlan store domain in FileContextStore`.

### Task 2: `declare_operation_plan` agent tool (forced typed output)
**Files:** Create `gently/app/tools/operation_plan_tools.py` (+ register in `tools/__init__`). Test: `tests/test_operation_plan_tool.py`.
- [ ] Confirm the `@tool` decorator + the forced-tool pattern (registry.py auto-schema, or a literal schema like notebook_ask.ASK_TOOL). The tool accepts the typed plan (tactics list) and writes it via `store.set_operation_plan`. Resolve the store from context (mirror existing memory tools).
- [ ] TDD: calling the tool with a plan persists it (get returns it) + returns a confirmation; missing store → error. `pytest tests/test_operation_plan_tool.py -v`; `pytest -q` clean. Commit `feat(operations): declare_operation_plan typed agent tool`.

### Task 3: Route `GET /api/operation_plan/{session_id}`
**Files:** Create `gently/ui/web/routes/operation_plan.py` (+ register in `routes/__init__.py`). Test: `tests/test_operation_plan_route.py`.
- [ ] Mirror `routes/context.py` / `routes/temperature.py` `_resolve_session` + `server.context_store`/`gently_store` resolution. Return the stored plan; 404/empty handled; `session=current` resolves newest.
- [ ] TDD (TestClient + mock store): returns the plan; empty when none. `pytest tests/test_operation_plan_route.py -v`; `pytest -q` clean. Commit `feat(operations): GET /api/operation_plan/{session} route`.

### Task 4: The operation-spine renderer + scenario library (frontend)
**Files:** Create `gently/ui/web/static/js/operations-scenarios.js` (plan fixtures: temp_strain, expression_onset, hatching_detect, transmission_survey, decided_plan, async_multi, idle); Rewrite the Overview path in `gently/ui/web/static/js/experiment-overview.js` to the data-driven operation-spine (generalized per tactic kind, audit fixes); port CSS to `experiment.css`. Reference: `scratchpad/opsdesign/harness.html`.
- [ ] Port the renderer (renderOperation/renderTactic/renderReadout/renderPhase) + add per-kind rendering: standing→per-embryo cadence strip; reactive→watch/reaction/status; scripted→phase stepper; exclusive/oneshot→compact. Audit fixes baked in. `?scenario=` dev mode loads a fixture.
- [ ] `node --check` both JS; build a Chrome-MCP harness at `scratchpad/opsv3/` (real repo files) for the controller to audit across fixtures. Commit `feat(operations): operation-spine renderer + plan scenario library (data-driven)`.

### Task 5: Live binding + refresh
**Files:** Modify `experiment-overview.js` (fetch `/api/operation_plan`, bind live telemetry from `/strategy`/get_status onto tactics by `live_bind`, subscribe `CONTEXT_UPDATED` + tactic events → debounced refetch).
- [ ] Bind temperature/current-burst/cadence/signal onto declared tactics' readouts/progress; live refresh on plan-change + telemetry events (debounced). `node --check`; harness check. Commit `feat(operations): live telemetry binding + event-driven refresh`.

## Self-Review
- Store→Task1; tool→Task2; route→Task3; renderer+scenarios→Task4 (audit fixes); live binding→Task5. ✓
- Open confirmations: FileContextStore domain pattern (T1), the forced-tool/@tool pattern (T2), the route store-resolution (T3), the Overview render seam + harness renderer (T4), the live telemetry sources (T5).
- Type consistency: the plan/tactic schema is identical across store (T1), tool (T2), route (T3), fixtures+renderer (T4), binding (T5).

---
## Execution-linkage tasks (added after recon — close the planning→execution loop)

### Task 6: `transition_tactic` store helper
**Files:** Modify `gently/harness/memory/file_store.py` (add `transition_tactic(session_id, tactic_id, state=None, **bind)` next to `set/get_operation_plan` ~:818 — read the plan, find the tactic by `id`, set `state` and merge `bind` into its `live`/`structure`, write back, fire `CONTEXT_UPDATED`; no-op if plan/tactic absent). Test: `tests/test_transition_tactic.py`.
- [ ] TDD: declare a plan, transition a tactic planned→active with a `request_id` bind → get shows the new state + bound value; unknown tactic_id → no-op (no crash). Commit `feat(operations): transition_tactic store helper`.

### Task 7: tactic_id threading + start-edge marking in execution tools
**Files:** Modify `gently/app/tools/timelapse_tools.py` (`enable_monitoring_mode`, `queue_burst`, stop/pause), `gently/app/tools/temperature_protocol_tools.py`, and the burst/protocol event payloads (`exclusive.py` BURST_*, `temperature_protocol.py` TEMP_PROTOCOL_*) to carry an optional `tactic_id`. On execute, the tool calls `cs.transition_tactic(session, tactic_id, 'active')`; on stop/pause → 'done'/'paused'. Test: `tests/test_tool_tactic_linkage.py`.
- [ ] Add optional `tactic_id` param; thread into event `data`; flip the plan tactic active on execute (guard: only if a plan + tactic_id exist). TDD with a fake context store capturing transitions. Commit `feat(operations): link execution tools to plan tactics via tactic_id`.

### Task 8: `OperationPlanUpdater` service (completion edges via the bus)
**Files:** Create `gently/app/operation_plan_updater.py` (a `Service` modeled on `gently/app/temperature_sampler.py` / `TimelineManager`); wire in `gently/app/agent.py` beside the temperature sampler (~:838-849). Test: `tests/test_operation_plan_updater.py`.
- [ ] Subscribe `BURST_COMPLETE`, `TEMP_PROTOCOL_COMPLETED`, `EMBRYO_CADENCE_CHANGED`, `TRIGGER_FIRED`; on each, resolve the tactic (by `tactic_id` in payload, else by kind+embryo) and `cs.transition_tactic(session, tactic_id, 'done', **bind)` binding live values (request_id/mp4_path/setpoint/cadence). Mirror the sampler's start/stop lifecycle + session_id getter. TDD against a fake bus + store. Commit `feat(operations): OperationPlanUpdater — execution events transition plan tactics`.

---
## Plan-item ↔ operation linkage tasks (added — tactics planned at plan time)

### Task 9: PlanItem/ImagingSpec tactical outline
**Files:** Modify `gently/harness/memory/model.py` (`ImagingSpec` ~:200 / `PlanItem` ~:265 — add optional `tactics: list[dict]` outline field: each entry a lightweight tactic `{kind, name, target?, scope?, structure?}`). Ensure the plan-mode planning tools (`gently/harness/plan_mode/tools/planning.py` create_plan_item/update_plan_item) accept/persist it. Test: extend the plan-item model/store tests.
- [ ] Confirm the real ImagingSpec/PlanItem dataclass + how planning tools set the spec. Add the optional `tactics` outline (default empty), persisted in the campaign plan YAML. TDD: a plan item round-trips its tactics outline. Commit `feat(operations): plan-item tactical outline (plan tactics with the imaging spec)`.

### Task 10: Operation Plan goal/plan_item linkage + seeding
**Files:** Modify `gently/app/tools/operation_plan_tools.py` (or a small seeding helper / the agent): resolve the current session's `plan_item_id`/`campaign_id`/goal from the `session_intent` (`get_current_session_intent` ~file_store.py:788) → set them on the Operation Plan top-level; when a session linked to a plan item with a `tactics` outline begins (or on first declare), SEED the Operation Plan's `planned` tactics from the outline. Test: `tests/test_operation_plan_seeding.py`.
- [ ] Confirm the session_intent→plan_item linkage accessors. On declare/seed, populate `plan_item_id`/`campaign_id`/`goal` from the linked plan item and seed `planned` tactics from its outline (idempotent — don't clobber tactics already active/done). TDD with a fake store: a session linked to a plan item with an outline produces a seeded Operation Plan. Commit `feat(operations): seed Operation Plan goal + planned tactics from the linked plan item`.
126 changes: 126 additions & 0 deletions docs/superpowers/specs/2026-06-28-operations-tab-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Design: Operations — the agent-authored Operation Plan (sub-project D, v3)

Status: **DRAFT for review.** Redesigned 2026-06-28 after two from-scratch iterations (swimlane →
operation-spine) and a foundational rethink the user prompted: tactics are richer than a linear plan,
and the tactical picture should be a **typed output the agent authors per experiment**, not a backend
reconstruction. Grounded by an architecture recon (every piece needed already exists).
Base branch: `feature/operations-tab` (off C). Keep: Experiment→Operations rename + the burst-`phase`
event. Discarded: the swimlane band (reverted) and the single-linear-spine assumption.

## 0. The two decisions this encodes (pending user confirm)
1. **Source of truth = the agent.** The agent emits a typed **Operation Plan** (the tactics it has
planned / is running / has run, each typed) via forced `tool_choice` — gently's existing house
pattern (Ask-the-notebook, hatching detector, verifier, classifier all do this; `@tool`
auto-generates schemas). The UI renders the agent's plan **⊕ live telemetry binding**. We do NOT
keep extending `strategy_snapshot`'s timeline-replay *reconstruction* to guess tactics — the agent
owns tactic identity; the backend supplies live values.
2. **Open, grounded ontology.** A tactic is far broader than a phased protocol; the schema covers the
real operational modes (below). No round-robin primitive (the scheduler is priority-queue
"most-overdue"; "round" is legacy naming).

## 1. The Operation Plan (typed agent output)

```
operation_plan = {
session_id, title, goal, # the agent's framing of the run
tactics: [ tactic ],
updated_at, updated_reason # patched on each tactic transition
}
tactic = {
id, name, kind, state, scope,
rationale, # the agent's WHY (no self-rated confidence)
structure, live_bind, relations
}
kind : 'standing_timelapse' | 'reactive_monitor' | 'scripted_protocol' | 'exclusive_burst' | 'oneshot' | 'custom'
state : 'planned' | 'active' | 'done'
scope : { mode:'global'|'embryos', embryo_ids?:[...] }
structure : # by kind
standing_timelapse → { cadence_s, per_embryo:[{embryo_id, cadence_phase:'normal'|'fast'|'burst'|'paused', interval_s}] }
reactive_monitor → { watch, reaction, status:'armed'|'watching'|'fired' } # onset, saturation, burst-on-structure, hatching-detect
scripted_protocol → { phases:[{name, state:'done'|'active'|'todo', count}] } # before/during/after
exclusive_burst → { frames, mode, phase? }
oneshot|custom → { note }
live_bind : [ 'temperature' | 'current_burst' | 'cadence' | 'signal' | ... ] # which telemetry fills readouts/progress
relations : { after?:[tactic_id], layered_on?:[tactic_id], triggers?:[tactic_id] } # concurrency + composition
live : { # agent-authored display hints + updater-merged telemetry
readouts : [{label, value, sub?, bar?, bind?}] # instrument strip entries (e.g. {label:"cadence", value:"120 s"})
phases : [{name, state:'done'|'active'|'todo', count, pips}] # phase stepper for scripted_protocol / exclusive_burst
# flat bound keys merged in by the updater as telemetry arrives:
# request_id, sustained_hz, setpoint, locked, last_fired, new_phase, ...
}
```

The `live` object has two agent-authored sub-keys: `readouts` (a list of instrument-strip rows the agent populates at declaration time) and `phases` (a phase stepper list for scripted or burst tactics). The operation-plan updater additionally merges flat keys (`request_id`, `sustained_hz`, `setpoint`, `locked`, `last_fired`, `new_phase`, and any other telemetry bound via `live_bind`) directly onto `live` as events arrive — the agent seeds these at declaration if the value is already known, or leaves them absent for the updater to fill in.

This single typed object expresses a regular timelapse (standing), an async per-embryo run (standing
with per-embryo cadence — *real* in the orchestrator), a reactive monitor incl. **hatching detection**
(reactive), a temp-change protocol (scripted, phased), an exclusive burst, and arbitrary `custom`
tactics — because the **agent declares each one's kind/structure**.

## 2. Architecture (producer → model → consumer, all reusing existing paths)

### 2.1 Producer — the `operation_plan` typed tool (agent)
A new agent tool `declare_operation_plan(...)` (or `update_operation_plan`) in `gently/app/tools/`,
schema auto-generated by the `@tool` decorator (or a forced literal schema like `notebook_ask.ASK_TOOL`).
The agent calls it at experiment planning and **patches it on each tactic transition** (begin / end /
phase-change). Prompt guidance (in `harness/prompts/templates.py`, beside the monitoring-mode decision
table) tells the agent to keep its Operation Plan current as it operates.

### 2.2 Model — store the plan in `FileContextStore`
A new domain beside `session_intents`/`active/`: `agent/operation_plans/{session_id}.yaml`. Add
`set_operation_plan` / `get_operation_plan` to `FileContextStore` and fire the existing
`CONTEXT_UPDATED` (the store's `_notify_*` already broadcasts to `/ws`). The plan is an agent-authored
typed artifact exactly like campaigns/plan-items already are.

### 2.3 Consumer — Operations renders plan ⊕ live binding
- Route `GET /api/operation_plan/{session_id}` (mirror `routes/context.py`) returns the plan.
- The renderer (the **operation spine**, generalized): one node per tactic in used/active/queued state.
The active node blooms open and **binds live telemetry** by `live_bind` keys — temperature gauge,
current burst, phase progress, cadence — sourced from the orchestrator `get_status` /
`strategy_snapshot` / the tactic events C emits. Per-embryo cadence (standing/async) shows as a
compact strip in the standing-tactic node. Reactive monitors render watch+reaction+status; scripted
protocols render the before/during/after stepper.
- Live refresh: subscribe to `CONTEXT_UPDATED` (plan changed) + the tactic telemetry events
(`TEMP_PROTOCOL_*`, `BURST_*`, `EMBRYO_CADENCE_CHANGED`, `POWER_RAMP_STEP`, `TEMPERATURE_SETPOINT_CHANGED`)
→ debounced refetch+render. (Mirrors `experiment-strip.js` / `context-surface.js`.)
- **Audit fixes** (from the design audit) baked into the renderer: queued reads as a cocked instrument
(blue thread + "next" marker), the active phase/status is the headline, flatten the card (no
card-in-card), the active row's whole left column is amber + colored left edge per state, copy =
"queued", keep mono instrument values + domain copy.

### 2.4 Scenario test mode (your "test it for real experiments" + "different tactic cases")
A repo scenario library of **Operation Plan fixtures** (the prototyped six cases, now as plan objects):
temp-strain, expression-onset, pre-hatching/hatching-detect, transmission survey, decided-plan, idle —
plus async-multi-embryo. `?scenario=<name>` (dev-gated) renders a fixture with no agent/fetch, so
Operations is developable/auditable offline against any tactic case. The fixtures double as the test
corpus + a Chrome-MCP audit target.

## 3. Data flow
agent → `declare_operation_plan` (forced typed) → `FileContextStore` → `CONTEXT_UPDATED` → `/ws` →
Operations refetch → `/api/operation_plan` (the plan) **⊕** live telemetry (`get_status` /
`strategy_snapshot` / events) bound by `live_bind` → spine renderer. Scenario mode: `?scenario=` →
fixture plan → same renderer.

## 4. Why agent-authored over backend reconstruction
`strategy_snapshot.py` already reconstructs the picture by replaying `timeline.jsonl` and re-instantiating
modes — but every new tactic kind needs new reconstruction heuristics, and it can only ever *infer*
intent. The agent *is* the composer; it should *declare* its tactics (authoritative, open-ontology,
self-describing). The backend keeps providing **live values** (what it's good at), not tactic identity.

## 5. Convergence with G (tactics library)
Once tactics are typed, agent-authored objects, **G is "save/reuse these typed tactics"** and D is
"render the active plan of them." This spec is the shared substrate for both — settling it unblocks
both sub-projects.

## 6. Testing
- Store: `set/get_operation_plan` round-trip + `CONTEXT_UPDATED` fired.
- Tool: the forced `declare_operation_plan` returns a schema-valid plan (mirror the notebook_ask test).
- Route: `/api/operation_plan/{session}` returns the stored plan; 404/empty handled.
- Renderer: `node --check` + Chrome-MCP audit across the scenario fixtures (all tactic kinds render).
- Live binding: a unit test that telemetry maps onto a tactic's `live_bind` keys.
- Rig-deferred: the real agent emitting a live plan during an experiment.

## 7. Out of scope (v3)
- Full multi-embryo concurrent lanes (v1 = one standing mode + layered tactics + a per-embryo strip).
- G's authoring/save UI (this spec defines the tactic object G will reuse).
- Migrating away from `strategy_snapshot` for the legacy swimlane consumers (Operations is the new view).
Loading