# Orchestrator (Phase 2) — tasks Decomposition in dependency order, grouped by subsystem. Each group notes its deploy surface. Decisions referenced are in [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md); the HOW is in [design.md](design.md). Deploy surfaces: - **coder** = `sudo systemctl restart boocoder` - **web/contracts** = `pnpm -C packages/contracts build && docker compose up --build -d boocode` --- ## 1. Re-home conductor defs + DispatchFn seam + model param (coder) — [D-1] - [ ] Copy `spine.ts`, `flows/*`, `contracts.ts`, `types.ts`, `render.ts` into `apps/coder/src/conductor/`. Do NOT copy `flow.ts` or `dispatch.ts`. - [ ] Copy the 23 personas (`conductor/agents/*.md`) into the coder tree. - [ ] Add a `DispatchFn` field to `StepContext` (`types.ts`); replace `flows/code-review.ts:10` (`import { dispatchAgent } from '../dispatch.js'`) and its call site (`:62`) with a call through `ctx.dispatch`. - [ ] Parameterize `spine.ts:122` `process.env.CONDUCTOR_MODEL` → the run's `model` (threaded through the spine factory / step context). - [ ] Confirm the Phase-1 CLI (`conductor/`) still builds + runs unchanged (regression oracle). - [ ] Coder build passes (`pnpm -C apps/coder build`). ## 2. Schema: `flow_runs` / `flow_steps` (coder) — [D-5], [D-10] - [ ] Add `flow_runs` to `apps/coder/src/schema.sql` (project_id no FK; band CHECK small|medium|large; status CHECK-named running|completed|failed; input JSONB CHECK `input ? 'question'`; report TEXT nullable; timestamps via `clock_timestamp()`). - [ ] Add `flow_steps` (run_id FK→flow_runs CASCADE; step_id; kind CHECK agent|code; status CHECK-named pending|running|completed|failed|skipped — NO `queued`; task_id→tasks SET NULL nullable; chat_id→chats SET NULL; output TEXT FULL; UNIQUE(run_id, step_id)). - [ ] Indexes `flow_steps(run_id, status)`, `flow_runs(project_id, created_at DESC)`. - [ ] Explicit CHECK names + DROP-IF-EXISTS → guarded-ADD migration discipline. - [ ] Verify idempotent re-apply (schema applies clean twice). ## 3. WS frames in all three registries (contracts + server + web) — [D-6] - [ ] Add `flow_run_started` (`run_id, flow_name, band, steps[{step_id, agent, kind, chat_id, label}]`) + `flow_run_step_updated` (`run_id, step_id, status, run_status?, report?`) to `packages/contracts/src/ws-frames.ts` `WsFrameSchema`; rebuild (`pnpm -C packages/contracts build`). - [ ] Add both to the server loose `InferenceFrame` union (`apps/server/src/services/inference/turn.ts`). - [ ] Add both to the web strict `WsFrame` union (`apps/web/src/api/types.ts`) — the wire-format gate. - [ ] Confirm the per-agent stream reuses existing `delta`/`tool_call`/ `message_complete` by `chat_id` (no new stream frames). ## 4. Flow-runner + onTaskTerminal hook + plan mode + per-step chats (coder) — [D-2], [D-3], [D-4] - [ ] Add `onTaskTerminal(taskId, state)` callback to `createDispatcher`, invoked at the external terminal transitions (`dispatcher.ts:642-646`, `:659-661`). No change to internal run functions. - [ ] Create `apps/coder/src/services/flow-runner.ts`: load flow def, derive ready wave, INSERT `flow_runs`/`flow_steps`, build prompts via `step.run(ctx)` in-process (contracts injected), INSERT a synthetic `chats` row per agent step, INSERT each ready agent step as a `tasks` row with `state='pending'`, `mode_id='plan'` (hardcoded, never user-overridable), `chat_id=`. - [ ] Run `code` steps inline (`flow_steps.task_id` NULL). - [ ] On `onTaskTerminal`: read FULL task output → `flow_steps.output`, mark step, derive + INSERT next wave; on last wave render report → `flow_runs.report`, `status='completed'`. - [ ] Publish `flow_run_started` on launch and `flow_run_step_updated` on each step transition (report on completion). ## 5. Resume on startup (coder) — [D-9] - [ ] Add `initResume` (coder startup) over `flow_runs WHERE status='running'`: task completed → mark step done + advance; task lost/failed → re-dispatch (fresh task, `mode_id='plan'`); keep completed steps. - [ ] Verify a coder restart mid-run reconciles and advances (manual smoke). ## 6. Routes: create / list / reopen (coder) — [D-2], [D-7] - [ ] `POST /api/runs` `{project_id, flow_name, band, input:{question}, model?}` → create run, start flow-runner, return `run_id`. - [ ] `GET /api/runs?project_id=` → runs history. - [ ] `GET /api/runs/:id` → run + steps + report (reopen). ## 7. `orchestrator` pane kind + OrchestratorPane (web) — [D-7] - [ ] Add `orchestrator` to `WorkspacePaneKind` (`api/types.ts`); thread through `useWorkspacePanes`, `Workspace`, `NewPaneMenu`, `ChatTabBar`, `PaneHeaderActions` (follow the `markdown_artifact`/`html_artifact` precedent). - [ ] `OrchestratorPane.tsx`: run header, report-at-top on completion, collapsed roster reusing `AgentStatusDot` (`AgentComposerBar.tsx:204`), expand-one-at-a-time well reusing CoderPane stream rendering keyed by step `chat_id`, mobile single-column inline expand, auto-expand-follows-active. - [ ] Subscribe to `flow_run_started` (build roster) + `flow_run_step_updated` (status/report) + reused stream frames by `chat_id`; handlers idempotent (event-dedup discipline). ## 8. Workflow toolbar button + slash launch wiring, parity (web) — [D-8] - [ ] Add the `Workflow` (lucide) button to `ChatInput`'s controls row between `SquareSlash` and `Globe` (`ChatInput.tsx:648-732`); "Flows" desktop, icon-only mobile. Confirm the row stays one line (no scroll/wrap) on mobile. - [ ] Slash (`/flow `): launch instantly with defaults (band small, current pane's project, text-after = focus), open an Orchestrator pane. - [ ] Verify parity: the button + slash both appear in BooChat (ChatPane) AND BooCoder (CoderPane) since `ChatInput` is shared. ## 9. FlowLauncherDialog (web) — [D-8] - [ ] `FlowLauncherDialog.tsx`: 5 category tabs (Analysis/Discovery/Planning/ Authoring/Review) filtering the flow list (`flows/index.ts`), + size + focus + fast toggle; defaults Analysis/Small/off. On launch → `POST /api/runs`, open the Orchestrator pane. ## 10. Runs history + export (web) — [D-7] - [ ] Runs history entry point in `NewPaneMenu` (backed by `GET /api/runs`); reopening opens an Orchestrator pane via `GET /api/runs/:id`. - [ ] Export in the pane header `…`: copy / save-to-file / send-to-chat (existing `sendToChat`, `lib/events.ts`), conditional on a completed report. ## 11. Tests — [D-2], [D-9] - [ ] Coder vitest for the scheduler: ready-wave derivation from a flow def, `onTaskTerminal` advancement, last-wave report render. Extract a pure helper (e.g. `flow-runner-decisions.ts`) for the wave/advance logic (repo pattern: `turn-guard.ts`/`lifecycle-decisions.ts`). - [ ] Coder vitest for resume: completed step kept, lost/failed step re-dispatched (pure reconcile helper). - [ ] Parity check: assert the `Workflow` button + slash are wired through the shared `ChatInput` (so both panes get them) — code-level assertion, no web test harness exists. --- ## Sequencing notes - Groups 1–6 are coder-only (one `systemctl restart boocoder` after 1–6 land together, or incrementally). - Group 3 (contracts) must build before groups 7–10 consume the frame types (`pnpm -C packages/contracts build` first). - Groups 7–10 are web; ship with `docker compose up --build -d boocode`. - Group 11 lands alongside the code it tests (coder tests after group 4/5).