Files
boocode/openspec/changes/orchestrator/tasks.md
indifferentketchup 1937af8df9 feat: in-app Orchestrator (Phase 2) — multi-agent conductor
Brings the deterministic Han-flow conductor into BooCode: launch any read-only
flow from BooChat or BooCoder, watch each agent stream live in a Paseo-style
run pane, get an evidence-disciplined report — on local Qwen, persisted and
resumable. Read-only enforced hard via qwen --approval-mode plan (orchestrator
tasks fail closed if qwen is unavailable; never fall to write-capable native).

Backend (apps/coder): re-homed conductor defs, flow_runs/flow_steps schema,
flow-runner + dispatcher onTaskTerminal hook, restart-resume, runs routes
(launch/list/get/cancel), user-channel WS. Contracts: two flow_run_* frames.
Web: orchestrator pane kind + OrchestratorPane, Workflow button + slash flows
(BooChat/BooCoder parity), FlowLauncherDialog, "New Orchestrator" in the + and
split menus, runs history + export. Plan: openspec/changes/orchestrator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:22:48 +00:00

145 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=<synthetic>`.
- [ ] 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 <focus>`): 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 16 are coder-only (one `systemctl restart boocoder` after 16 land
together, or incrementally).
- Group 3 (contracts) must build before groups 710 consume the frame types
(`pnpm -C packages/contracts build` first).
- Groups 710 are web; ship with `docker compose up --build -d boocode`.
- Group 11 lands alongside the code it tests (coder tests after group 4/5).