# 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:<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-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.
