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>
7.4 KiB
7.4 KiB
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.tsintoapps/coder/src/conductor/. Do NOT copyflow.tsordispatch.ts. - Copy the 23 personas (
conductor/agents/*.md) into the coder tree. - Add a
DispatchFnfield toStepContext(types.ts); replaceflows/code-review.ts:10(import { dispatchAgent } from '../dispatch.js') and its call site (:62) with a call throughctx.dispatch. - Parameterize
spine.ts:122process.env.CONDUCTOR_MODEL→ the run'smodel(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_runstoapps/coder/src/schema.sql(project_id no FK; band CHECK small|medium|large; status CHECK-named running|completed|failed; input JSONB CHECKinput ? 'question'; report TEXT nullable; timestamps viaclock_timestamp()). - Add
flow_steps(run_id FK→flow_runs CASCADE; step_id; kind CHECK agent|code; status CHECK-named pending|running|completed|failed|skipped — NOqueued; 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?) topackages/contracts/src/ws-frames.tsWsFrameSchema; rebuild (pnpm -C packages/contracts build). - Add both to the server loose
InferenceFrameunion (apps/server/src/services/inference/turn.ts). - Add both to the web strict
WsFrameunion (apps/web/src/api/types.ts) — the wire-format gate. - Confirm the per-agent stream reuses existing
delta/tool_call/message_completebychat_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 tocreateDispatcher, 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, INSERTflow_runs/flow_steps, build prompts viastep.run(ctx)in-process (contracts injected), INSERT a syntheticchatsrow per agent step, INSERT each ready agent step as atasksrow withstate='pending',mode_id='plan'(hardcoded, never user-overridable),chat_id=<synthetic>. - Run
codesteps inline (flow_steps.task_idNULL). - 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_startedon launch andflow_run_step_updatedon each step transition (report on completion).
5. Resume on startup (coder) — [D-9]
- Add
initResume(coder startup) overflow_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, returnrun_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
orchestratortoWorkspacePaneKind(api/types.ts); thread throughuseWorkspacePanes,Workspace,NewPaneMenu,ChatTabBar,PaneHeaderActions(follow themarkdown_artifact/html_artifactprecedent). OrchestratorPane.tsx: run header, report-at-top on completion, collapsed roster reusingAgentStatusDot(AgentComposerBar.tsx:204), expand-one-at-a-time well reusing CoderPane stream rendering keyed by stepchat_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 bychat_id; handlers idempotent (event-dedup discipline).
8. Workflow toolbar button + slash launch wiring, parity (web) — [D-8]
- Add the
Workflow(lucide) button toChatInput's controls row betweenSquareSlashandGlobe(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
ChatInputis 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 byGET /api/runs); reopening opens an Orchestrator pane viaGET /api/runs/:id. - Export in the pane header
…: copy / save-to-file / send-to-chat (existingsendToChat,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,
onTaskTerminaladvancement, 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
Workflowbutton + slash are wired through the sharedChatInput(so both panes get them) — code-level assertion, no web test harness exists.
Sequencing notes
- Groups 1–6 are coder-only (one
systemctl restart boocoderafter 1–6 land together, or incrementally). - Group 3 (contracts) must build before groups 7–10 consume the frame types
(
pnpm -C packages/contracts buildfirst). - 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).