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

7.4 KiB
Raw Blame History

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; the HOW is in 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).