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>
145 lines
7.4 KiB
Markdown
145 lines
7.4 KiB
Markdown
# 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 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).
|