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>
6.8 KiB
6.8 KiB
Discovery notes — Orchestrator (Phase 2)
Single source of truth for project context. Specialists: read this first; do not re-grep for what's here. Search further only for what your domain needs that's missing.
Tech stack
- pnpm monorepo.
apps/server(BooChat: Fastify + Postgres),apps/coder(BooCoder: host systemd service, agent dispatch),apps/web(React + Vite),apps/booterm,packages/contracts(@boocode/contracts, cross-app wire SSOT). - DB
boochat(Postgres 16). TypeScript strict, NodeNext (server/coder),.jsimport extensions. Tests: vitest (server + coder); no web test harness. - Deploy:
apps/coderchange →sudo systemctl restart boocoder;apps/web/apps/server→docker compose up --build -d boocode.
Phase 1 assets to reuse (/opt/boocode/conductor/)
src/spine.ts— the Spine→Flow factory (band gating, fold, synthesizer, validator, render), + contracts injection.src/flows/*.ts+flows/index.ts— 22 flows (21 Spine configs + bespokecode-review.ts), registry (getFlow,FLOW_NAMES,describeFlows).src/contracts.ts— Han evidence/yagni contracts (produce/review).src/types.ts—Flow,Step,Spine,Angle,Band,Contract.src/flow.ts— the wave scheduler (dep-aware parallel). This is what Phase 2 must re-home/replace so steps dispatch through BooCoder backends + persist.src/dispatch.ts— currentopencode runsubprocess dispatch. Replaced in Phase 2 by BooCoder backend dispatch.agents/*.md— 23 Han personas (also live in~/.config/opencode/agents/).
apps/coder — execution surfaces
src/services/dispatcher.ts:46—createDispatcher.LISTEN 'tasks_new'fast path (pg triggernotify_tasks_new, schema line 279) + 2s poll.runTaskroutes astate='pending'task to a backend.inflightmap keyedsession_id ?? 'task:<id>'serializes per session.src/services/agent-backend.ts:97—AgentBackend(ensureSession / prompt / closeSession / dispose / health). Backends:backends/opencode-server.ts,warm-acp.ts,claude-sdk.ts; one-shotacp-dispatch.ts/pty-dispatch.ts.AgentEvent(agent-backend.ts:28, union text|reasoning|tool_call|tool_update| commands) → mapped to WS frames by the dispatcher →broker.publishUserFrame.- Tasks are how work is dispatched.
INSERT INTO tasks (project_id, input, agent, model, mode_id, thinking_option_id, session_id, chat_id)then the LISTEN/NOTIFY trigger picks it up. Precedents:routes/messages.ts:233,routes/skills.ts:94(a skill IS already dispatched as a task),routes/arena.ts:49,tools/new_task.ts:54(writesparent_task_id).
apps/coder — schema (src/schema.sql, coder-owned)
tasks(line 18):id, project_id, parent_task_id (FK self, written by new_task, NOT read by dispatcher), state CHECK(pending|running|completed|failed|blocked| cancelled), input, output_summary (≤500 char), agent, model, execution_path, cost_tokens, started_at, ended_at, session_id, arena_id, mode_id, thinking_option_id, chat_id.agent_sessions(line 88): PK(chat_id, agent);backend, agent_session_id, server_port, status(idle|active|crashed|closed), config_hash, token/cost cols.worktrees(line 142),available_agents(line 36),checkpoints(233),claude_session_entries(252).notify_tasks_newtrigger (279).- Schema discipline (root CLAUDE.md): two schema files one DB; coder schema is
applied by the host boocoder service. CHECK migrations: DROP IF EXISTS the
system-named constraint → UPDATE → guarded ADD.
CREATE OR REPLACE VIEWcan't reorder cols. JSONB viasql.json(value as never).clock_timestamp()in txns.
packages/contracts — WS frames
src/ws-frames.ts— Zod frames inWsFrameSchema(SSOT). Existing: snapshot, message_started, delta, reasoning_delta, tool_call, tool_result, message_complete, usage, messages_deleted, chat_renamed, compacted, error.- Adding a frame (cross-app, root CLAUDE.md): add to
WsFrameSchemahere (rebuildpnpm -C packages/contracts build), AND the server's looseInferenceFrameunion (services/inference/turn.ts), AND the web's strictWsFrameunion (apps/web/src/api/types.ts) — the web type is the wire gate; missing it silently drops the frame at JSON-parse.
apps/web — panes + composer
- Pane kinds (
api/types.ts:386WorkspacePaneKind):empty | chat | coder | terminal | settings | markdown_artifact | html_artifact. Extra non-chat pane kinds are already precedented — addingorchestratorfollowsmarkdown_artifact/html_artifact. hooks/useWorkspacePanes.ts— pane state,addSplitPane(kind), server-persisted (+ legacy localStorage seed).Workspace.tsx,NewPaneMenu.tsx,ChatTabBar.tsx,PaneHeaderActions.tsxall takekind: 'chat'|'terminal'| 'coder'— adding a kind touches these.ChatInput.tsxis the shared composer rendered by BOTHChatPane.tsxandCoderPane.tsx(CoderPane also stacksAgentComposerBarabove it). Its toolbar row (icons: Globe, ListPlus, Paperclip, Send/Stop,SquareSlashfor slash) is where the Orchestrator button goes → parity for free. It takesslashGroups(ChatPane passes BooChat skills; CoderPane passes agent-commands+skills),onSlashCommand.SlashCommandPicker.tsx,hooks/useSkills.ts.- Mobile: per prior preference, crowded toolbars must fit one line (no scroll/wrap) and the new button shows icon-only on mobile.
Precedents / related
- Arena (
routes/arena.ts): same task → N contestants (tasks sharingarena_id), parallel,[SELECTED]winner. Closest existing fan-out; stays separate but is a structural precedent for "one launch → many tasks grouped". - BooChat skills (
apps/serverroutes/skills.ts+services/skills,getSkillBody): slash injects a skill body, the single chat model runs it inline. The coder also hasroutes/skills.tsthat dispatches a skill as a task. - Event-dedup discipline (root CLAUDE.md): a mutation published via
broker.publishUsermust NOT alsosessionEvents.emitlocally; handlers idempotent.
Enumerated gaps (searched, not found)
- No
flow_runs/flow_steps/flowstables, nodepends_on/step_indexontasks, no DAG/pipeline concept anywhere (confirmed Phase-1 research). - No
orchestratorpane kind, component, or WS frame yet. - No coding-standards dir hits for orchestration; ADR dir not present under
docs/adr/(none found) — architectural decisions live inopenspec/changes/. - No resume mechanism for a multi-step run after coder restart (single tasks
resume via
agent_sessions; a run spanning tasks does not). - The conductor's scheduler (
conductor/src/flow.ts) is in-process/in-memory; it does not persist step state or survive restart.