# 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), `.js` import extensions. Tests: vitest (server + coder); **no web test harness**. - Deploy: `apps/coder` change → `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 + bespoke `code-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` — current `opencode run` subprocess 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 trigger `notify_tasks_new`, schema line 279) + 2s poll. `runTask` routes a `state='pending'` task to a backend. `inflight` map keyed `session_id ?? 'task:'` 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-shot `acp-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` (writes `parent_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_new` trigger (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 VIEW` can't reorder cols. JSONB via `sql.json(value as never)`. `clock_timestamp()` in txns. ## packages/contracts — WS frames - `src/ws-frames.ts` — Zod frames in `WsFrameSchema` (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 `WsFrameSchema` here (rebuild `pnpm -C packages/contracts build`), AND the server's loose `InferenceFrame` union (`services/inference/turn.ts`), AND the web's strict `WsFrame` union (`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:386` `WorkspacePaneKind`): `empty | chat | coder | terminal | settings | markdown_artifact | html_artifact`. **Extra non-chat pane kinds are already precedented** — adding `orchestrator` follows `markdown_artifact`/`html_artifact`. - `hooks/useWorkspacePanes.ts` — pane state, `addSplitPane(kind)`, server-persisted (+ legacy localStorage seed). `Workspace.tsx`, `NewPaneMenu.tsx`, `ChatTabBar.tsx`, `PaneHeaderActions.tsx` all take `kind: 'chat'|'terminal'| 'coder'` — adding a kind touches these. - **`ChatInput.tsx` is the shared composer** rendered by BOTH `ChatPane.tsx` and `CoderPane.tsx` (CoderPane also stacks `AgentComposerBar` above it). Its toolbar row (icons: Globe, ListPlus, Paperclip, Send/Stop, `SquareSlash` for slash) is where the Orchestrator button goes → parity for free. It takes `slashGroups` (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 sharing `arena_id`), parallel, `[SELECTED]` winner. Closest existing fan-out; stays separate but is a structural precedent for "one launch → many tasks grouped". - **BooChat skills** (`apps/server` `routes/skills.ts` + `services/skills`, `getSkillBody`): slash injects a skill body, the single chat model runs it inline. The coder also has `routes/skills.ts` that dispatches a skill as a task. - Event-dedup discipline (root CLAUDE.md): a mutation published via `broker.publishUser` must NOT also `sessionEvents.emit` locally; handlers idempotent. ## Enumerated gaps (searched, not found) - No `flow_runs` / `flow_steps` / `flows` tables, no `depends_on`/`step_index` on `tasks`, no DAG/pipeline concept anywhere (confirmed Phase-1 research). - No `orchestrator` pane 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 in `openspec/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.