# BooCode roadmap (v1.x–v2.x)

Last updated: 2026-05-31

> **Companion doc:** `boocode_code_review.md` holds the full external-repo inventory, lift rationale, and license analysis. This document is the canonical source for shipping state, version ordering, and what's planned vs. shipped.

## Overview

BooCode is a **3-app monorepo** at `/opt/boocode/` (locked 2026-05-22):

- **BooChat** (`apps/server` + `apps/web`, port `9500`, `code.indifferentketchup.com`) — read-only chat with file-inspection tools. Backend in `apps/server`, SPA in `apps/web`. Database `boochat` (renamed from `boocode` at v2.0).
- **BooCoder** (`apps/coder`, port `9502`, `coder.indifferentketchup.com`) — write tools + external-CLI dispatch. **Shipped v2.0.0–v2.6.6** (repo tag line; v2.6.5–v2.6.6 were BooChat workspace UX + docs). Host systemd service (not Docker since v2.1.0). In-process inference (with `pending_changes` table) AND Paseo-style ACP dispatch for five providers (opencode, goose, claude, qwen + native boocode; cursor + copilot retired at v2.5.3) with PTY fallback where ACP is unavailable. Provider lifecycle is config-backed (`data/coder-providers.json`, enable/disable, two-tier probe — shipped v2.5.4–v2.5.13). opencode now runs as a **warm HTTP server** with persistent per-chat sessions (v2.6 Phase 1); goose/qwen/claude still dispatch one-shot.
- **BooTerm** (`apps/booterm`, port `9501`) — PTY/tmux/xterm.js. **Live since May 2026.** bookworm-slim + node-pty + tmux + xterm.js. Tmux session per pane (`bc-<uuid>`), SSH-out works (openssh-client + gosu in the image). Shares Postgres database `boochat`.

Caddy → Authelia → Tailscale → `100.114.205.53` → 9500/9501/9502. Three apps, **one shared Postgres** (Docker service `boocode_db`, database name `boochat`).

**Architectural commitments:**

- **No embeddings.** Model uses file-view tools (`view_file`, `list_dir`, `grep`, `find_files`) + sidecar analyzers (codecontext, future codesight) + codecontext MCP tools. Walked away from the RAG pipeline May 2026.
- **BooChat is read-only.** Write tools live in BooCoder (shipped v2.0).
- **Mount strategy: blanket `/opt:rw`, permission gating at the write-tool layer.** Per-project scoping is policy, not mount. Path-guard correctness is the #1 test target for v2.0.
- **External CLI agents (`opencode`/`claude`/`goose`/`pi`) live on the host, not in containers.** BooCoder shells out via local-exec PTY or ACP subprocess. Host install inherits Sam's existing `~/.opencode/`, `~/.claude/`, `~/.config/goose/` configs.
- **Protocol roles locked (2026-05-22):** **BooChat = MCP client only** (read-only tool consumer, never enables write-capable MCP servers). **BooCoder = MCP client + MCP server + ACP client (host) + ACP agent (driveable)** — full matrix. BooCoder's ACP-client role replaces raw-PTY dispatch for ACP-capable agents (opencode `opencode acp`, goose `goose acp`); PTY fallback retained for claude/pi/smallcode.
- **Paseo-equivalent dispatcher inside BooCode** (2026-05-22 pivot, **shipped v2.2**). Paseo (`getpaseo/paseo`) is AGPL-3.0 — incompatible with BooCode's **target** MIT license and network-served deployment (the tree is *currently* AGPL-3.0 via the v2.4 Unsloth-Studio lifts — see the **License-debt → relicense to MIT** batch; the Paseo pattern-only rule holds regardless). BooCode reproduces the architecture using license-clean patterns only (`provider-snapshot.ts`, ACP merge/stream/persist, `AgentComposerBar`). Primary architectural template: `Dominic789654/agent-hub` (Apache-2.0). Critical context-management primitive: Roo Code Boomerang Tasks pattern. Observation pattern: Claude Code hooks (siropkin/budi reference).

External code lifted from / referenced in: see `boocode_code_review.md` for full inventory.

-----

## Shipped (status as of 2026-05-26)

|Version                |Theme                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |Tag                          |
|-----------------------| |-----------------------------|
|v1.0                   |Initial scaffold                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |—                            |
|Batches 1–4.4          |Markdown, sidebar, panes, chats-inside-sessions, archive, fork/delete, header polish, settings drawer                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |—                            |
|v1.5                   |resolveProjectPath, BOOTSTRAP_ROOT, vitest pin                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |—                            |
|v1.6, v1.6.1, v1.6.2   |Mobile pass + RightRail mobile drawer                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |—                            |
|v1.7                   |Drag-drop file + paste-as-attachment                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |—                            |
|v1.8, v1.8.1, v1.8.2   |Settings drawer, git_status tool, WS reconnect, per-turn budget reset + Continue affordance + CapHitSentinel                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |—                            |
|v1.9.1                 |Skills system (`/opt/skills/` + `skill_find` / `skill_use` / `skill_resource` + `/skill` slash command)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |`v1.9.1`                     |
|v1.9.7                 |`ask_user_input` elicitation tool                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |`v1.9.7`                     |
|Batch 9 (Agents Tier 2)|`AGENTS.md` + 6 builtin agents + AgentPicker in ChatInput toolbar + `sessions.agent_id`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |folded into `v1.9.1`/`v1.9.7`|
|v1.10.0                |BooTerm: separate container, xterm.js + node-pty + tmux                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |`v1.10.0`                    |
|v1.10.1                |BooTerm-user (spawn as samkintop, login bash, Claude Code/opencode PATH)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |`v1.10.1`                    |
|v1.10.4, v1.10.5       |Mobile terminal + XML tool-call fallback parser                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |—                            |
|v1.11.0                |opencode-style compaction port (auto-overflow, anchored summary, tail preservation)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |—                            |
|v1.11.1                |Compaction follow-up (working indicator during compaction, unit tests, .bak cleanup)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |—                            |
|v1.11.2                |ContextBar (persistent context-usage indicator above MessageList)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |—                            |
|v1.11.3                |`ctx_max` capture via `/upstream/<model>/props` (replaces dead `timings.n_ctx` read)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |`v1.11.3`                    |
|v1.11.5                |ContextBar inline next to agent picker; remove ChatContextPopover; default new sessions to no agent                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |—                            |
|v1.11.6                |Doom-loop guard from opencode (3 identical tool calls → sentinel, abort recursion)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |—                            |
|v1.11.7                |pathGuard secrets filter (continue.dev `DEFAULT_SECURITY_IGNORE_FILETYPES`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |—                            |
|v1.11.8                |web_search + web_fetch tools via SearXNG                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |—                            |
|v1.11.9                |Manual redirect handling — re-run URL guard on each hop (SSRF hardening)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |—                            |
|v1.11.10               |Stream-cap response body at 5MB, abort on overflow                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |`v1.11.x`                    |
|v1.12.0                |codecontext sidecar (Go HTTP shim, NDJSON MCP framing, child.Wait supervisor) + container guidance (BOOCHAT.md/BOOCODER.md) + 7 vendored skills + system-prompt.ts extraction + mtime-watch cache + 8 codecontext tool wrappers + per-agent tool whitelists + .codecontextignore template + agents.ts ALL_TOOL_NAMES single-source-of-truth fix                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |`v1.12.0`                    |
|v1.12.1                |Server-side workspace pane sync (`sessions.workspace_panes jsonb`) + 5-state status indicator overhaul (streaming/tool_running/waiting_for_input/idle/error) + startup hung-row sweep + stale `messages_status_check` constraint dropped + `detectSameNameLoop` reverted (dead code) + stop-handler writes `cancelled` status                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |`v1.12.1`                    |
|v1.12.2                |Live tok/s + ctx_used display next to status indicator while streaming (frontend-only)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |`v1.12.2`                    |
|v1.12.3                |Stale-stream banner — "Previous response didn't complete. [Retry] [Discard]" when streaming row > ~60s with no new tokens. `POST /api/chats/:id/discard_stale` backend endpoint                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |`v1.12.3`                    |
|v1.12.4                |Refactor only — `inference.ts` (1700 LoC) split into `inference/` directory: `turn.ts`, `stream-phase.ts`, `tool-phase.ts`, `error-handler.ts`, `sentinel-summaries.ts`, `payload.ts`, `xml-parser.ts`, `sentinels.ts`, `budget.ts`, `types.ts`, `index.ts`. Shipped as rc1/rc2/rc3 → final. No behavior change. Lined up `stream-phase.ts` as the swap target for v1.13 AI SDK migration                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |`v1.12.4`                    |
|**v1.13.0**            |**`message_parts` table** `(id, message_id, sequence, kind, payload jsonb, created_at)` with kinds `text/tool_call/tool_result/reasoning/step_start`. CHECK constraint, `(message_id, sequence)` unique + index. Dual-write at every site that wrote `tool_calls`/`tool_results` JSON (stream-phase finalize, skills × 2, messages.ts answer flow, chats.ts × 2). `ToolDef<T>` gained `category: 'read_only' | 'write'`. v1.x registry rejects write. Old JSON columns remain authoritative for reads. Strangler-fig phase 1                                                                                                                                                                                                                                                                                                                                                                                                                    |`v1.13.0`                    |
|**v1.13.1-A**          |**AI SDK v6 install + streamCompletion adapter.** `ai@^6`, `@ai-sdk/openai-compatible@^2`. `provider.ts` wraps `createOpenAICompatible` against `config.LLAMA_SWAP_URL`. `streamCompletion` rewritten as adapter over `streamText`. XML fallback parser preserved for qwen3.6's inline `<tool_call>` emissions. **Patched mid-flight:** AI SDK v6 swallows abort signals silently — explicit `if (signal?.aborted) throw` after stream drain. Without it, stop button writes `complete` instead of `cancelled`. reasoning-delta counted + dropped (re-captured in -C). Known regression flagged: live mid-stream tps gone (single trailing publish; TODO for delta-cadence interpolation against `result.usage`)                                                                                                                                                                                                                                |(umbrella tag)               |
|**v1.13.1-B**          |**`messages_with_parts` view** with COALESCE fallbacks against legacy JSON columns. Read sites switched: `chats.ts:427`, `messages.ts:95`, `ws.ts:27`, `payload.ts`, `compaction.ts`. Perf verified at 1ms for 42-message chat. `reasoning_parts` column added to the view (consumed in -C). API contract preserved. Parts become source of truth at read; JSON columns kept by dual-write only                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |(umbrella tag)               |
|**v1.13.1-C**          |**`ask_user_input` correlation ported to parts.** `messages.ts:478/549` now JOINs `message_parts` on `payload->>'id'` and `payload->>'tool_call_id'`. Downstream call sites updated to `{message_id, payload}` shape. 404 fallback for pre-v1.13.0 history (acceptable scope). **Reasoning end-to-end:** `reasoning-delta` accumulated in `stream-phase.ts` adapter via `StreamResult.reasoning` (simpler than the brief's StreamPhaseState approach); `partsFromAssistantMessage` accepts optional `reasoning`, emits at seq 0; `finalizeCompletion` + `executeToolPhase` dual-write reasoning parts; `payload.ts` reads `reasoning_parts` from view, collapses into `OpenAiMessage.reasoning`; `toModelMessages` emits AI SDK `ReasoningPart` in assistant content array. Smoke: 361 chars reasoning at seq 0, 429 chars text at seq 1                                                                                                        |`v1.13.1` (`ac1a71f`)        |
|**v1.13.3**            |**Cleanup bundle, 4 independent items.** (1) `ALTER DATABASE boocode SET statement_timeout = '30s'` — caps damage from query-plan regression on the view's nested subselects; documented in `schema.sql` since `ALTER DATABASE` can't run inside a DO block. (2) Alpha-sorted tool registry — `.sort((a, b) => a.name.localeCompare(b.name))` at `ALL_TOOLS` export; llama.cpp prompt cache hits on byte-identical prefixes, tool-order drift killed hit rate every turn. (3) Periodic 60s in-process sweeper marks `streaming` rows older than 5 min as `failed` and publishes `chat_status='idle'` so the UI dot drops — closes mid-session crash UX gap that the startup sweep (v1.12.1) only handled at boot. (4) `experimental_repairToolCall` wired through AI SDK v6 `streamText` — routes malformed tool calls to a logged passthrough instead of crashing the stream. Owed since v1.13.1-A. 173/173 tests pass (+1 alpha-ordering test)|`v1.13.3` (`a08d809`)        |
|**v1.13.4**            |**Two-tier compaction prune.** `services/inference/prune.ts` with pure `selectPruneTargets` decision helper. Tier 1 hides stale `tool_result` parts via `message_parts.hidden_at` at the 20k-freed threshold (cheap, no inference call); tier 2 falls back to anchored summarize when prune alone isn't enough. Schema additions: `message_parts.hidden_at` column + partial index `ON (message_id) WHERE hidden_at IS NULL`. `messages_with_parts` view filters hidden parts so payload assembly never sees them. Avoids burning an inference round on every overflow. opencode-pattern half-shipped in v1.11.0 — this closes it.                                                                                                                                                                                                                                                                                                              |`v1.13.4` (`ec8593c`)        |
|**v1.13.5**            |**opencode `truncate.ts` port — full tool output retrievable via opaque id.** New `services/truncate.ts` with `tr_<12 base32>` ids on tmpfs (`/tmp/boocode-truncations`, 0o700, 5MB cap matching `view_file`'s `MAX_FILE_BYTES`, 7-day TTL). Three exports: `storeTruncation`, `readTruncation`, `truncateIfNeeded` (wrap-or-passthrough helper). New `view_truncated_output(id)` tool retrieves the full content; model never sees the truncation dir (resolved server-side). Wired through 5 of 7 tool sites: `view_file`, `list_dir`, `web_fetch`, `codecontext_client`, plus alpha-sorted into `ALL_TOOLS` (count 19→20). `cleanupTruncations` piggybacks on the v1.13.3 60s sweeper (TTL pass + orphan reap via parts query on `payload->'output'->>'outputPath'`). grep and find_files deferred (need file_ops refactor to expose uncapped output). 186 tests (was 179, +7 in truncate.test.ts).                                          |`v1.13.5` (`f8fc5db`)        |
|**v1.13.6**            |**Compaction head-assembly audit + reasoning fix.** Audit traced compaction's summary path post-v1.13.1-B read flip across three quadrants — Q1 view read (clean), Q2 parts shape (clean), Q3 reasoning render (FIX NEEDED). v1.13.1-C wired reasoning end-to-end into `inference/payload.ts` but missed the compaction read site, silently degrading summary quality for reasoning-channel models (qwen3.6) since -C shipped. Fix: `CompactionMessage` extended with `reasoning_parts` field; SELECT pulls `reasoning_parts` from `messages_with_parts`; `buildHeadPayload` (now exported for tests) prefixes assistant content with `<reasoning>...</reasoning>\n\n<content>` when reasoning is present; standalone `<reasoning>` tag for tool-call-only turns; omits tag when reasoning is null or empty. 4 new render-branch tests (190 total).                                                                                                |`v1.13.6` (`81d837c`)        |
|**v1.13.7** (uncommitted)|**Stability bundle, 5 fixes from production observability gap.** (1) `provider.ts` — `includeUsage: true` on `createOpenAICompatible`. `@ai-sdk/openai-compatible` defaults this false, omitting `stream_options.include_usage` from request body; llama-swap never emitted the usage block, so `result.usage.inputTokens/outputTokens` resolved `undefined` and `tokens_used`/`ctx_used` landed NULL in **every** assistant row since v1.13.1-A. Surfaces tokens in StatsLine + persisted DB rows going forward (no backfill). (2) `MessageList.tsx:48` — `hasText = m.content.trim().length > 0`. AI SDK v6 streaming occasionally emits a leading `\n` text-delta on tool-call-only turns; the literal newline passed `length > 0` and rendered an empty bubble + ActionRow between each tool call. (3) `MessageBubble.tsx:654` — same trim on `hasContent` (defensive, no-tool-calls path). (4) `payload.ts:64` — `buildMessagesPayload` skips assistant rows with `status='failed'` AND `status='complete' && empty content && no tool_calls`. Without this, a trailing empty/failed assistant + the next attempt's placeholder produced "Cannot have 2 or more assistant messages at the end of the list" rejections from the upstream API. (5) `budget.ts:11` — `BUDGET_NO_AGENT = 30` (was 15). No-agent mode shares the read-only-agent toolset at runtime; the cautious 15-cap was forward-looking for write tools that haven't landed. 190/190 tests still pass.|—                            |

**v1.13.2 deliberately deferred** — keep the dual-write through v1.13.4–v1.13.11 as rollback insurance. Drop legacy columns last.

-----

### Shipped (v1.13.x — strangler-fig closed 2026-05-23)

All v1.13.x batches use the `vMAJOR.MINOR.PATCH-slug` tag scheme adopted 2026-05-22. `CHANGELOG.md` is the canonical per-tag record (slug describes what shipped; tag name alone recalls the batch). The v1.13.x line ran 21 batches over a single intense window; the umbrella `v1.13` tag sits on `211e903` (same commit as `v1.13.20-drop-legacy-cols`), marking the strangler-fig closed. Tags in chronological order:

- `v1.13.0-ai-sdk-v6` — AI SDK v6 migration; `streamCompletion` adapter; `messages_with_parts` view; reasoning_parts end-to-end
- `v1.13.1-cleanup-bundle` — `statement_timeout='30s'`, alpha-sorted tool registry, 60s stuck-row sweeper, `experimental_repairToolCall` pass-through
- `v1.13.2-compaction-prune` — two-tier prune; `message_parts.hidden_at` column + partial index; `messages_with_parts` view CASE refinement
- `v1.13.3-truncate` — opencode `truncate.ts` port; opaque `tr_<…>` id, `view_truncated_output(id)` tool, tmpfs storage
- `v1.13.4-reasoning-fix` — `<reasoning>` prose-prefix in compaction head-assembly for tool-bearing turns
- `v1.13.5-stability-bundle` — `includeUsage: true` on provider, `hasText` trim guard, `BUDGET_NO_AGENT` 15→30, trailing-empty-assistant filter
- `v1.13.6-prefix-stability` — `buildSystemPromptWithFingerprint` SHA-256 + per-session drift observer
- `v1.13.7-compaction-trigger` — overflow trigger lowered to `floor(0.85 × ctx_max)`
- `v1.13.8-tool-cost` — `tool_cost_stats` SQL view + per-tool rolling 100-call mean in AgentPicker
- `v1.13.9-agentlint` — instruction-file AgentLint pass; identity-openers removed; `CLAUDE.local.md` to .gitignore
- `v1.13.10-openspec` — `openspec/changes/<slug>/{proposal,tasks,design}.md` shape; archived batch docs preserved via `git mv`
- `v1.13.11-tools` — tiered tool loading via `BOOCODE_TOOLS` env (`core | standard | all`)
- `v1.13.12-ws-schemas` — Zod schemas for all 27 wire-format frames; `publishFrame` / `publishUserFrame` wrappers; parity test
- `v1.13.13-ws-publish` — all ~80 publish sites converted to the typed wrappers; every WS frame now Zod-validated at boundary
- `v1.13.14-skills-audit` — 26 skills vendored + audited via 5 parallel agent teams; 14 kept, 11 dropped, 1 migrated to BOOCHAT.md/BOOCODER.md
- `v1.13.15-codecontext-synth` — forced second-inference synthesis pass for codecontext overview tools (truncation-aware extraction; auto-fetched top-N files + project docs; 32k payload-budget contract preserved)
- `v1.13.16-xml-parser` — Anthropic `<invoke>` parser support + Levenshtein-based unknown-tool recovery hints (qwen3.6 drift to Claude Code-style tool names like `read_file`); xml-parser test coverage
- `v1.13.17-cross-repo-reads` — `request_read_access` tool + per-session `allowed_read_paths` grants; `pathGuard` extended with `extraRoots`; pause/resume reuses the `ask_user_input` mechanism
- `v1.13.18-codecontext-file-path` — `resolveProjectPath` in `codecontext_client.ts` realpath-resolves `file_path` arg the same way `target_dir` was; closes the silent-fail path the sidecar exhibited on relative paths
- `v1.13.19-html-artifact-panes` — pane-based artifact viewer with on-request HTML; `<!DOCTYPE html>` detection adds `message_parts.kind='html_artifact'` row; Markdown + HTML panes both open via "Open in pane" affordance; iframe sandbox `allow-scripts allow-clipboard-write allow-downloads` (no `allow-same-origin`, `srcDoc`); CSP `connect-src 'none'`. Scope-revised mid-design from auto-bias-to-HTML to Markdown-default / HTML-on-request
- `v1.13.20-drop-legacy-cols` — final strangler-fig step. Drops `messages.tool_calls` + `tool_results` columns; 10 dual-write sites removed (recon caught 2 beyond the original roadmap inventory); `messages_with_parts` view simplified to parts-only subselects via `CREATE OR REPLACE` before the column DROPs (Postgres ordering constraint). Adversarial-review catch: `discard_stale` had a `RETURNING tool_calls, tool_results` clause; fixed via two-step UPDATE-then-SELECT-from-view. `Message` API type retains the fields — view synthesizes them from parts so the wire shape is unchanged
- `v1.13` — **umbrella tag on the same commit as v1.13.20.** Marks the AI SDK v6 + parts-table migration complete

The v1.13.x line is closed. Three batches still sit in the **In flight** column conceptually but none of them are v1.13.x scope: **live-smoke of v1.13.19** (manual browser exercise of the artifact panes — five minutes, independent), and the two v1.14 branches below. Independent siblings (`v1.14.x-mcp`, `v1.14.x-html`, `v1.16`) can ship in any order relative to v1.14 itself.

-----

## v1.14 — Phase C: outer agent loop

**Goal:** explicit multi-step loop per opencode `prompt.ts` `runLoop()`. Replace the current ad-hoc tool-call recursion.

**Scope:**

1. Outer loop continues until model returns non-tool finish OR step cap hit. Step ≠ tool call: one step can contain multiple tool calls in parallel.
1. `agent.steps ?? Infinity` per-agent step cap. AGENTS.md gains `steps:` field. Refactorer `steps: 5`, Architect `steps: 20`, etc.
1. Step-boundary events (`step_start`, `step_finish`) explicit in the parts stream. Per-step snapshot for revert (planned for BooCoder; backend-only in v1.14).
1. Doom-loop guards (v1.11.6) migrate from "abort recursion" to "raise within loop iteration." Same predicate, different control flow.

**Lift sources:**

- `anomalyco/opencode` `session/prompt.ts` `runLoop()` outer agent loop
- `anomalyco/opencode` `agent.steps` per-agent step cap
- AGENTS.md extensions for `steps`, `output_schema` (Qodo agent.toml pattern), `exit_expression` (Qodo pattern), `execution_strategy` (Qodo plan/act)
- **Reference:** RA.Aid three-stage Research/Planning/Implementation as AGENTS.md design principle; expert-tool escape hatch pattern (most subtasks on routine model, escalate to qwopus27b only when needed)
- **Reference:** Roo Code Boomerang Tasks — orchestrator-with-capability-restriction pattern. Adopt as AGENTS.md design principle (orchestrator role can call only dispatch tools, no file reads / MCP / shell).

**Dependencies:** v1.13 merged.

**Estimated:** ~800 LoC.

**Shipped as `v1.14.0-outer-loop`.** Explicit `while (stepNumber < effectiveCap)` loop in `turn.ts`, per-agent `steps:` field from AGENTS.md frontmatter, `MAX_STEPS=200` ceiling, doom-loop guard migrated to loop-iteration style.

-----

## v1.14.x-mcp — single-server MCP-client proof-of-concept (NEW, 2026-05-22)

**Goal:** validate the MCP-client loop end-to-end against one real MCP server before committing to the full opencode `mcp/index.ts` port at v1.15. Small, throwaway-if-needed, slots between v1.14 and v1.15 without disrupting either.

**Scope:**
1. Add a hardcoded MCP client (single server) to BooChat. Initial target: **Context7** (Sam already uses it via opencode, so the config is known to work). Remote HTTP transport at `https://mcp.context7.com/mcp` with optional `CONTEXT7_API_KEY` header.
1. Use the official `@modelcontextprotocol/sdk` TypeScript client. No SSE transport yet (deferred to v1.15). Stdio transport not needed for Context7.
1. Tool discovery on startup: `tools/list`. Tools surface in BooChat alongside `view_file`/`grep`/etc., prefixed `context7_*` to avoid collisions.
1. **Read-only invariant guard:** the client must reject any MCP tool whose `annotations.readOnly` is false (or absent). Fail-closed. This is BooChat-specific defense-in-depth — v1.15 lifts this restriction for BooCoder.
1. Per-server `enabled` flag in `agents.ts`. No glob patterns yet.
1. **No OAuth.** Context7 supports an API key header; that's it for v1.14.x. OAuth lands in v1.15.

**What this proves:**

- MCP protocol loop works end-to-end against a real server in BooCode's Fastify backend.
- Tool-discovery → tool-list → tool-call → result-render → context-budget accounting all hold.
- Read-only enforcement at the client layer is sound.
- Config schema shape is right before v1.15 commits to the opencode-compatible JSON config.

**What this does NOT do:**

- No SSE transport. (v1.15.)
- No OAuth flow. (v1.15.)
- No multiple servers. (v1.15.)
- No per-agent server allow/deny. (v1.15.)

**Dependencies:** v1.13 merged (parts table for tool-call/tool-result emission).

**Estimated:** ~150 LoC.

**Skip-condition:** if v1.14 finishes and Sam wants to leap straight to v1.15, fold this into the early steps of v1.15.

**Shipped as `v1.14.1-mcp-poc`.** Context7 MCP client validated end-to-end.

-----

## v1.14.x-html — pane-based artifact viewer with Markdown + HTML (REVISED, 2026-05-23)

**Goal:** every assistant message gets an "Open in pane" affordance that renders it as an artifact — Markdown by default (the model's normal output), HTML only when the user explicitly asks for it (e.g. "render this as HTML", "make me a dashboard", "build an interactive diagram"). Both artifact types open in BooChat's existing workspace splitter. Markdown panes have **Copy** (raw source) + **Download** (`.md`); HTML panes have **Download** (`.html`) only. No inline iframe preview — artifacts are pane-only.

Inspired by Thariq Shihipar's "HTML > Markdown at length" pattern (`claude.com/blog/using-claude-code-the-unreasonable-effectiveness-of-html`, May 20 2026), but scoped down from that post's "auto-bias to HTML for >100 lines" recommendation: Markdown stays the default everywhere, HTML is an on-request rendering target for cases where interactive controls / diagrams / side-by-side layouts pay off.

**Scope:**

1. **Model-side prompting** (no code change, just AGENTS.md guidance):
- Add HTML-on-request rule to global `AGENTS.md`: "Stay in Markdown by default for all outputs, short or long. Switch to a self-contained `<!DOCTYPE html>...</html>` artifact only when the user explicitly asks (e.g. 'render this as HTML', 'make a dashboard', 'build a diagram')."
- Inline the `web-artifacts-builder` "avoid AI slop" design principles for when HTML is requested: no excessive centered layouts, no purple gradients, no uniform rounded corners, no Inter font, no generic AI aesthetics.
- Cite Thariq's blog post in the rule comment so future audit passes know where the design conventions came from.
1. **Detection at the BooChat backend.** In `apps/server/src/services/inference/stream-phase.ts` post-processing: detect any assistant text part starting with `<!DOCTYPE html>` (case-insensitive, whitespace-trimmed) — or wrapped in a fenced ` ```html` block — and tag it as an HTML artifact. Emit a new part kind `html_artifact` into `message_parts` (CHECK constraint update). Payload: `{html_content, char_count, title}`. Title pulled from `<title>` tag or first `<h1>` if available. Detection is opportunistic — when the model produces HTML (because the user asked), the tag fires; otherwise the message stays plain-Markdown and no `html_artifact` part is written.
1. **Pane-only render surface.** Every assistant message in the chat stream gets an "Open in pane" affordance (icon button in the message footer, alongside the existing copy/regenerate controls). Clicking it opens the message as an artifact pane in BooChat's existing workspace splitter, alongside the file viewer and BooTerm. Pane is dismissible. Pane state persisted via `sessions.workspace_panes jsonb` (the v1.12.1 schema already supports this).
- **Markdown pane** — renders via the same Markdown component used inline in `MessageBubble` (so syntax highlighting, fenced code blocks, tables, etc. all work). Header carries **Copy** (writes raw Markdown source to clipboard via `navigator.clipboard.writeText`) and **Download** (`.md`) buttons.
- **HTML pane** — renders the artifact in a sandboxed iframe at full pane height. Header carries **Download** (`.html`) only. **No Copy button** — HTML source isn't useful clipboard content; if the user wants the source they can Download and inspect.
1. **Download path & filename slug.** Both formats write to `/opt/<project>/.boocode/artifacts/<slug>-<unix-timestamp>.<ext>` (path-guarded same as native write tools), and surface an OS download link via the existing file-serving path.
- Markdown slug: derived from the message's first heading (`# ...`) if present, else the first 6 words of the message body, lowercased + hyphenated.
- HTML slug: derived from the artifact's `<title>` tag if present, else first `<h1>`, else first 6 words of the inner text. Same lowercase-hyphen treatment.
1. **Security stance for HTML pane — locked 2026-05-22:** the iframe is sandboxed with `sandbox="allow-scripts allow-clipboard-write allow-downloads"`. **Crucially, omit `allow-same-origin`** so the artifact has its own opaque origin and cannot read BooChat's cookies, Authelia session, or DOM. Backend serves the iframe content via `srcdoc=...` inline (not `src=`) so no separate URL exists to disclose. CSP header on the iframe response: `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; connect-src 'none'`. The `connect-src 'none'` is the key clause — artifacts can't `fetch()`, can't open WebSockets, can't ping a tracking pixel, can't exfiltrate. JS runs (so interactive knobs/sliders/copy-as-prompt buttons work) but nothing else network-touching does.
1. **Frontend components:**
- `apps/web/src/components/MarkdownArtifactPane.tsx` — pane shell + header (Copy + Download) + Markdown render reusing the existing component.
- `apps/web/src/components/HtmlArtifactPane.tsx` — pane shell + header (Download only) + `<iframe srcdoc={html_content} sandbox="allow-scripts allow-clipboard-write allow-downloads" />`.
- `MessageBubble.tsx` — add "Open in pane" affordance to every assistant message footer. Dispatches workspace-pane action `{type: 'markdown_artifact' | 'html_artifact', message_id, html_content?}`. When the message has an `html_artifact` part, the affordance opens as an HTML pane; otherwise it opens as a Markdown pane.
- Download button → POST to new endpoint `/api/chats/:id/messages/:msg_id/artifacts/download?fmt=md|html` which writes to disk (path-guarded) and returns the absolute path or pre-signed URL for the existing static-file serving route.
1. **No artifact persistence beyond the chat.** Artifacts live in `message_parts.payload->>'html_content'` (for HTML) or are derived on-demand from the assistant message's content (for Markdown). Downloads go to `/opt/<project>/.boocode/artifacts/` and are user-managed from there. No separate artifacts table.
1. **Token-budget guard.** Single HTML artifact can be at most 1MB of HTML in `message_parts.payload`. Larger triggers a streaming abort with a friendly error: "Artifact exceeded 1MB; consider splitting into multiple files or reducing inline assets." Markdown artifacts have no separate cap — they're bounded by the existing message-size envelope.
1. **No `web-artifacts-builder` skill vendor.** That skill (`anthropics/skills/web-artifacts-builder`) is built for Claude.ai's runtime with Vite + Parcel + tspaths + html-inline toolchain. BooChat has no shell execution surface. The pattern transplants; the toolchain doesn't. Treat the skill's "avoid AI slop" design principles as conventions inlined in the HTML-on-request AGENTS.md rule. The init/bundle scripts are out of scope.

**Lift sources:**

- `claude.com/blog/using-claude-code-the-unreasonable-effectiveness-of-html` (Thariq Shihipar, May 20 2026) — design conventions and use-case taxonomy (specs/code-review/design/reports/custom editors). The "auto-bias for >100 lines" recommendation is deliberately NOT lifted.
- HTML iframe sandbox spec (web platform standard, no license issues).
- `anthropics/skills/web-artifacts-builder` — design-principle reference only ("avoid AI slop" rules). **Do not vendor the toolchain.**

**Dependencies:** v1.13 merged (`message_parts` table is where HTML artifacts live). Independent of v1.14 (outer loop) and v1.14.x-mcp (MCP PoC). Can ship in any order relative to those.

**Estimated:** ~400 LoC. Roughly half backend (HTML detection + part-kind extension + download endpoint + path-guard integration + Markdown slug derivation) and half frontend (two artifact-pane components + MessageBubble affordance + pane integration + download wiring).

**Schema addition:**

- `message_parts.kind` CHECK constraint adds `'html_artifact'` to the allowed set.

**Skip-condition:** none — independent batch, ships clean any time after v1.13. Pane-based artifact view is a structural UX improvement (full-height read surface for long replies, durable download path) on top of the HTML-on-request rendering capability.

**Shipped as `v1.13.19-html-artifact-panes` on 2026-05-23.** Two scope-revisions during impl: (a) the HTML-on-request rule landed in `BOOCHAT.md` (always-true rules layer), not `data/AGENTS.md` (per-agent registry) — per BOOCHAT.md's own convention block. (b) Pane state stayed reference-only — `{chat_id, message_id, title}` — content fetched on mount via the existing chat-messages endpoint (Markdown) and a new `GET /api/chats/:id/messages/:msg_id/html_artifact` (HTML). Storing content in pane state would have ridden 1MB blobs through the `session_workspace_updated` WS frame and bloated the jsonb column on multi-pane sessions. Defense-in-depth additions beyond the original proposal: `X-Content-Type-Options: nosniff` + `Content-Security-Policy: sandbox` on the GET serve route, and `assertArtifactsDirSafe` realpaths the artifacts dir after `mkdir` to close a symlink-escape gap that would otherwise let a planted symlink under `.boocode/artifacts/` route writes outside the project root. Smoke not run pre-tag; first deploy is the smoke.

-----

## v1.15 — Phase D: permission ruleset + full MCP client

**Goal:** wildcard permission ruleset (opencode `evaluate.ts` pattern) and a proper MCP client implementation. Foundation for BooCoder to gate writes; immediate value for codecontext to be re-wired as a real MCP server.

**Scope:**
1. Wildcard rule matcher: `{ permission, pattern, action: 'allow' | 'deny' | 'ask' }`. Last-match-wins. Per-agent rulesets layer under per-session rulesets.
1. **Full MCP client implementation:** stdio (local subprocess) + SSE (remote HTTP) transports, `tools/list` discovery, `tools/call` invocation, OAuth via Dynamic Client Registration (RFC 7591), per-server enabled flag, **glob patterns for per-agent tool whitelisting** (matching opencode's `tools` config shape).
1. codecontext sidecar gets re-pointed from static wrappers (v1.12) to real MCP. New connectors become a config-only addition.
1. UI: permission-ask flow when a tool requires `ask` action. Modal or inline card with Allow once / Allow always / Deny. Reuses v1.9.7 elicitation surface.
1. BooChat stays read-only by default — the read-only invariant guard from v1.14.x carries forward (defense-in-depth even with the ruleset).
1. **Config shape: match opencode's JSON schema near-verbatim** so any opencode user can copy `mcp` blocks from `~/.opencode/config.json` into BooCode unchanged. Schema is not copyrightable; matching it is pure interoperability.

**v1 MCP scope limit (security):** local-stdio MCP servers and Context7-style API-key remote servers only. **Remote MCP servers requiring OAuth tokens are deferred** until BooCode has a real secret-storage primitive (sops-encrypted entries, Vault sidecar, or OS keyring). Reason: MCP OAuth tokens are bearer credentials for third-party services; storing them in plaintext PostgreSQL inside the BooCode DB widens the attack surface significantly if Authelia is bypassed. v1.15 ships the OAuth code path but the config schema rejects OAuth servers until secret storage lands.

**Absorbs:** Original Batch 12 (tool approval + plan/act mode) — same outcome via permission rules instead of mode enum.

**Lift sources:**

- `anomalyco/opencode` `permission/evaluate.ts` wildcard ruleset
- `anomalyco/opencode` `mcp/index.ts` MCP client (SSE transport, tools/list, tools/call, OAuth RFC 7591)
- `cline/cline` plan/act invariant — read-only mode pattern (absorbed)

**Dependencies:** v1.13 merged (parts table for permission events). Independent of v1.14.

**Estimated:** ~600 LoC.

**Shipped as `v1.15.0-mcp-multi`.** Multi-server MCP client with stdio transport + config file, per-agent tool glob patterns in AGENTS.md frontmatter.

-----

## v1.16 — codesight repo_health

Call graph, circular dependency detection, dead code flagging. Port `analyze.mjs` from `spirituslab/codesight`. New tool `repo_health(project_id)`. In-process Node (not sidecar). Cache results keyed by `(project_id, file_hashes_sig)` in new `repo_health_cache` table.

Independent batch — ships clean any time after v1.13. Low leverage unless Sam actually uses the dead-code / circular-dep output.

**Lift source:** `spirituslab/codesight` `analyze.mjs`. Drop VS Code wrapper.

**Dependencies:** v1.12 merged (can reuse codecontext parse output where overlapping).

**Estimated:** ~400 LoC.

-----

## v2.0 — BooCoder: pending changes + dual execution paths + ACP host + MCP server

**Major version bump.** New app `apps/coder/` inside the existing monorepo (not a separate repo). Shipped with database rename `boocode` → `boochat` and subdomain split (`code.indifferentketchup.com` → BooChat, `coder.indifferentketchup.com` → BooCoder).

**Shipped v2.0.0–v2.0.5.** All 8 phases complete plus v2.0.5 (Arena, FAST_MODEL, Qwen dispatch). See retrospective below.

**Three protocol roles in one surface:**

1. **MCP client (write-capable allowed).** Inherits the v1.15 client unchanged. BooCoder can enable write-capable MCP servers (`@modelcontextprotocol/server-filesystem` write tools, git commit MCP servers, etc.). All MCP writes route through the same `pending_changes` queue as native writes. Per-task allow/deny means dispatched tasks can have a different MCP roster than the interactive shell.
1. **MCP server (BooCoder's own primitives).** New `apps/coder/services/mcp_server.ts` exposes `boocoder.create_task`, `boocoder.list_pending_changes`, `boocoder.apply`, `boocoder.reject`, `boocoder.dispatch_external_agent`, `boocoder.list_worktrees` as MCP tools. Stdio transport for local consumers (Sam's `opencode` in Termius), HTTP for remote (deferred until OAuth + secret storage). **This is what makes external opencode-on-the-host BooCoder-aware.**
1. **ACP client (host).** Replaces the raw-PTY dispatch path for ACP-capable agents. Spawns `opencode acp` and `goose acp` as JSON-RPC stdio subprocesses. Native session lifecycle, mid-session model/mode switching, file-operation events surfaced as diffs in the BooCoder UI, terminal events that route into BooTerm, permission prompts answered via real dialogs. **MCP servers configured in BooCoder are auto-forwarded to the dispatched ACP agent** (per goose docs — `context_servers` is the field name). One MCP config drives every dispatched agent.

**Two execution paths, same surface (the answer to the May 18 "1 and 2 full featured" question):**

### Path A — in-process write-tool inference loop (Option B / native)

- New write tools: `edit_file`, `create_file`, `delete_file`, `apply_pending`, `rewind`.
- Edits queue in `pending_changes (id, session_id, file_path, diff TEXT, status, created_at)`. Nothing touches disk until `/apply`.
- Per-pane diff UI with Approve/Reject.
- Path-guard layer (`apps/coder/services/path_guard.ts`) enforces per-project scoping using the v1.15 permission wildcard ruleset. Blanket `/opt:rw` mount, policy at the tool layer. **Highest-priority test target: fuzz the path-guard against every traversal-attack pattern, including MCP-served filesystem writes.**

**Lift source:** `plandex-ai/plandex` pending-changes data model and diff/apply/rewind UX vocabulary.

### Path B — ACP/PTY dispatch to external CLI agents (Option A / dispatch)

- New tool `dispatch_external_agent(agent: 'opencode'|'claude'|'goose'|'pi', model: string, task: string, worktree: string)`.
- **Primary path: ACP subprocess** for agents that support it (opencode `opencode acp`, goose `goose acp`). JSON-RPC over stdio. Native session/tool/file/terminal events.
- **Fallback path: raw PTY** for claude/pi/smallcode via `node-pty` with `cwd = /opt/<project>` or a `git worktree add /tmp/booworktrees/<session-id>` worktree per dispatch.
- Dispatch worker checks `available_agents.supports_acp` at runtime and picks the right transport. Same task table, same project registry, same pending-changes flow.
- Captures stdout/stderr/exit-code into PostgreSQL stream tables (PTY path) or maps ACP events to the parts taxonomy (ACP path). WebSocket events surface to all three React surfaces.
- One worktree per active dispatched session.
- User picks per task via UI dropdown at task creation, or the in-process loop calls `dispatch_external_agent` itself.

**Lift sources:**

- `Dominic789654/agent-hub` (Apache-2.0) — task DAG schema, dispatcher worker, project registry, human inbox. **Primary architectural template.**
- `getpaseo/paseo` (AGPL-3.0, **design only — no code lift**) — daemon+clients architecture, `--worktree feature-x` flag, `paseo run/ls/attach/send` CLI verb shape, `/handoff` `/loop` `/orchestrator` skills concept.
- Roo Code Boomerang Tasks pattern — orchestrator capability restriction + down-pass/up-pass context discipline (`new_task` message, `attempt_completion` result, no implicit inheritance) + explicit precedence override clause.
- `covibes/zeroshot` blind-validation invariant — verify gate runs in separate agent context that only sees the diff and acceptance criteria, not the producing conversation.
- **ACP spec** (`agentclientprotocol.com`) — local-subprocess ACP via stdio JSON-RPC. Remote ACP (HTTP/WS) is still work-in-progress per the spec maintainers; v2.0 uses stdio only.
- **Goose ACP docs** (`goose-docs.ai/docs/guides/acp-clients/`) — `context_servers` auto-forward pattern. Critical: one MCP config drives every dispatched agent.

### Shared infrastructure between A and B

- `tasks` table (id, project_id, template_id, parent_task_id, state, input, output_summary, dependencies, agent, model, worktree_path, cost, started_at, ended_at)
- `task_templates` table (reusable spec → task instantiations)
- `pipelines` table + `pipeline_runs` (ordered template invocations)
- `available_agents` table (name, install_path, version, supports_acp, supports_mcp_client, last_probed_at) — populated by startup probe (`which opencode && opencode --version`, etc.)
- `human_inbox` view (state IN ('blocked', 'failed', 'needs_human'))
- Worker process `boocoder-dispatcher` (systemd unit alongside Fastify): picks ready tasks, dispatches via A or B (and within B, ACP or PTY), captures output, marks state.
- New `boocode` CLI as a thin WebSocket/HTTP client against the BooCoder API. Verbs: `boocode run`, `boocode ls`, `boocode attach <id>`, `boocode send <id>`. Mirrors Paseo's UX, license-clean implementation.
- BooCoder-internal MCP server (see role 2 above) registered on the Fastify server alongside the existing HTTP/WS endpoints. Stdio transport for opencode-in-Termius; HTTP transport gated on OAuth + secret storage.

**MCP server eval requirement:** run BooCoder's internal MCP server through the **anthropics `mcp-builder` skill's 10-question evaluation framework** before shipping. Ten independent, read-only, complex questions with verifiable answers in XML format. If the eval doesn't pass, the MCP server isn't shippable.

**Dependencies:** v1.13 (parts table) + v1.14 (outer loop + step boundaries for revert snapshots) + v1.14.x (MCP-client PoC) + v1.15 (full MCP client + permissions for path-guard policy).

**Estimated:** ~1500 LoC for Path A + Path B + shared schema, plus ~400 LoC for the MCP-server role, plus ~300 LoC for the ACP-client role. Multiple sub-versions: v2.0.0 native + ACP, v2.0.1 MCP server, v2.0.2 polish.

**Retrospective (2026-05-25, updated 2026-05-26):** All 8 phases shipped. v2.0.0-alpha through v2.0.5. The full BooCoder foundation is complete: write tools with pending-changes queue, dispatcher with ACP/PTY dual paths, MCP server (6 tools, stdio transport, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, and path-guard fuzz suite (34 traversal-attack tests). v2.1 added host systemd + provider picker; **v2.2 shipped the Paseo provider/dispatch stack** (`v2.2-paseo-providers` + `v2.2.1-pane-scoped-chats`). Runtime isolation (v2.1 optional batch) remains optional pending production bake.

-----

## v2.1 — BooCoder runtime isolation (optional)

Per-session Docker sandbox spawned by BooCoder on first write. Only project path mounted, not `/opt`. Idle-timeout 30 min. Standard OpenHands runtime contract: HTTP API inside container, BooCoder calls in.

**Skip-condition:** if the v2.0 path-guard layer holds up under fuzzing + a few months of production use, runtime isolation becomes optional hardening rather than necessary defense. Track but don't commit.

**Lift source:** `OpenHands/OpenHands` V1 runtime pattern.

**Dependencies:** v2.0.

**Estimated:** ~600 LoC.

**Status:** Still optional. v2.0 path-guard fuzz suite (34 traversal-attack tests) passed. No production pressure to containerize yet.

-----

-----

## v2.2 — Paseo-style providers

**Shipped `v2.2-paseo-providers` (2026-05-26).** Paseo-equivalent provider stack for BooCoder. Seven providers (boocode, cursor, claude, opencode, goose, qwen, copilot) with snapshot API (`provider-snapshot.ts`, ACP cold probe, per-provider model merge, cursor models from ACP). Frontend `AgentComposerBar` replaces v2.1's `ProviderPicker` — provider / mode / model / thinking in the coder composer; `SlashCommandPicker` + `useProviderSnapshot`. ACP dispatch rewritten (`acp-dispatch.ts`, `acp-stream.ts`, `acp-spawn.ts`, `agent-turn-persist.ts`, `acp-tool-snapshot.ts`) with Paseo merge/stream/persist, inline `PermissionCard` prompts, and `reasoning_delta` WS frames. Agent slash-command hints via ACP `available_commands_update` + `AgentCommandsHint`. Arena and MCP entry points accept `mode_id` / `thinking_option_id`. SSH helpers removed; host exec via `host-exec.ts`. Openspec archived to `openspec/changes/archived/v2.2-paseo-providers.md`.

**Shipped `v2.2.1-pane-scoped-chats` (2026-05-26, same commit).** Follow-up fixes: pane-scoped chat resolution (`resolveChatId` requires `pane_id`; client seeds dedicated chats per coder/terminal pane), `CoderMessageList` BooChat-style tool timeline, WS user-delta replace-not-append, inference `buildMessagesPayload` orphan `tool_calls` stripping for shared chats with ACP history.

-----

## Shipped (v2.2.2–v2.6.11 — interactive ACP, provider lifecycle, persistent agent sessions, workspace UX)

All tags `vMAJOR.MINOR.PATCH-slug`, monotonic per minor, assigned at ship time (planning slugs differ — see the numbering-discipline note below). `CHANGELOG.md` is the canonical per-tag record. **Note on numbering divergence:** the *planned-feature* "v2.3 — Provider lifecycle" actually shipped under the **v2.5.4–v2.5.13** tags; the *planned-feature* "v2.4 — BooCoder as ACP agent" remains **unshipped** even though v2.4.0/v2.4.1 *tags* shipped unrelated content (Unsloth lifts, sidecar routing). The patch-tag thread and the conceptual-milestone thread have diverged — read tags as the ship record, the `## v2.x` feature sections below as the milestone plan. The v2.3.0–v2.5.1 tags were never CHANGELOG-backfilled; summarized here from commit bodies.

- `v2.2.2-xml-placeholder-reject` — reject placeholder XML tool args (`...`, `<path>`, empty/whitespace, angle-bracket sentinels) at parse time; appends raw block to prose instead of silent-deleting. Fixes qwen3.6 answer-then-spurious-tools duplicate-row tail
- `v2.3.0-sampling-params-ask-user` — per-agent sampling params (`top_p`/`top_k`/`min_p`/`presence_penalty`) in AGENTS.md frontmatter threaded through inference (null = omit, preserve provider default); `ask_user_input` interactive card wired into both BooCoder frontends (CoderPane + standalone coder SPA)
- `v2.3.1-permission-questions` — enrich ACP `permission_requested` frame with `kind` (`tool`|`question`|`plan`|`elicitation`) + `input` + `description`; PermissionCard renders interactive radio/checkbox forms for AskUserQuestion; ACP `createElicitation` (experimental) JSON-Schema-driven forms
- `v2.3.2-coder-answer-endpoint` — fix `ask_user_input` submit in CoderPane (register `answer_user_input` on the boocoder service; `apiPrefix` routes through `/api/coder/...` so the right inference runner picks up the answer)
- `v2.4.0-unsloth-studio-lift` — port of Unsloth Studio modules: `tool-call-parser.ts` (replaces `xml-parser.ts`; balanced-brace JSON scanner, `hasToolSignal`/`stripToolMarkup`/`parseToolCallsFromText`, stripping at all 3 final-write sites) + `web/html-to-md.ts` (parse5 HTML→Markdown for `web_fetch`). **License consequence (recorded 2026-05-31):** Unsloth Studio is AGPL-3.0-only — this lift (plus `llama-args-validator.ts` from `v2.4.1-sidecar-routing`) put the **whole BooCode tree under AGPL-3.0** (`LICENSE` + all five `package.json` are `AGPL-3.0-only`; three files carry the AGPL SPDX header). Scheduled for removal in the **License-debt → relicense to MIT** batch
- `v2.4.1-sidecar-routing` — route per-agent `llama_extra_args` to `LLAMA_SIDECAR_URL` via `X-Agent-Flags` (boot guard if set but URL unset); `resolveRoute` + PrefixFingerprint `route` field. AGENTS.md tool-gap fix: 8 post-hoc tools (`request_read_access`, `view_truncated_output`, `ask_user_input`, `git_status`, …) added to every agent's whitelist
- `v2.5.0-task-model` — lightweight task-model services (`TASK_MODEL_URL` dedicated llama-server, falls back to `LLAMA_SWAP_URL`+`FAST_MODEL`) for auto-naming/search-rewrite/tags/summaries; search-query rewriting on step 0 when web tools enabled; `sessions.tags` column
- `v2.5.1-budget-100` — tool-call budgets raised 50/10/50 → **100/100/100** (read-only / non-read-only / no-agent); per-agent `max_tool_calls` still overrides. `.claude/worktrees/` added to `.codecontextignore`
- `v2.5.2-coder-ux-fixes` — dispatcher reacts immediately via Postgres `LISTEN/NOTIFY` (`tasks_new` trigger, 2s poll fallback); mobile nav-drawer bfcache fix (`useViewport` re-syncs on `pageshow`/`visibilitychange`); reasoning "Thinking" collapsible in MessageBubble (ACP `agent_thought_chunk` + native `reasoning_parts`); paste-to-chip verbatim; "New file from pasted text" RightRail affordance; DiffPanel approve/reject repointed to real routes. Ships the `v2-6-persistent-agent-sessions` openspec as planning docs only
- `v2.5.3-remove-cursor-copilot` — retire cursor + copilot providers entirely (argv cases, manifest, command maps, cursor model-CLI branch, `cursor-models.ts`). Built-ins now: claude, opencode, goose, qwen, native boocode
- `v2.5.4-provider-lifecycle-phase1` — **(v2.3 milestone, phase 1/5)** config-backed provider layer (`CODER_PROVIDERS_PATH` default `/data/coder-providers.json`; `provider-config.ts` never-throws loader; `buildResolvedRegistry` merge) over built-ins; `agent-probe` iterates the resolved registry. No runtime change when no config file exists
- `v2.5.5-provider-lifecycle-phase2` — **(phase 2/5)** snapshot lifecycle status (`loading`|`ready`|`unavailable`|`error`) + `enabled` flag; always lists every registered provider; two-tier probe (fast `which` vs cold ACP, skipped unless forced / `PROVIDER_PROBE_TTL_MS` 24h stale / DB-empty); `provider-types-parity.test.ts`
- `v2.5.6-provider-lifecycle-phase3` — **(phase 3/5)** generic ACP dispatch (`resolveLaunchSpec` from config `launchCommand`; spawn `spec.binary`/`args`/`env`); built-in dispatch byte-identical (regression-tested). Config-defined custom ACP providers dispatch with no new switch case
- `v2.5.7-claude-models-and-picker-fix` — fix the empty provider picker (a v2.5.5 regression: `getProviderSnapshot` returned sync `loading` entries the composer filtered out → now awaits build, returns terminal entries); wire config `models` (replace) / `additionalModels` (merge); claude static models bumped to opus/sonnet/haiku latest-aliases + pinned full names
- `v2.5.8-mobile-composer-row` — AgentComposerBar mobile fix (dot + refresh as one right-aligned unit, was wrapping); Mode picker icon-only on mobile via `CompactPicker` `iconOnly`. Desktop unchanged
- `v2.5.9-agent-slash-commands` — segmented per-agent slash menu (active agent's commands first, BooCoder skills second; opt-in `groups` prop, BooChat flat path byte-identical); skills now run under the selected external agent (skill body injected into a dispatched task); landing-chat skill-invoke fix
- `v2.5.10-opencode-live-commands` — capture opencode's live ACP `available_commands` (poll for the async `available_commands_update`, was racing to 0); persist to new `available_agents.commands` JSONB; serve merged on the tier-2-probe-skip path
- `v2.5.11-claude-skill-discovery` — surface Claude Code's real enabled commands + plugin skills in the coder slash menu (`claude-command-discovery.ts` reads `~/.claude/commands` + `enabledPlugins` skills/commands); three icon'd groups (agent commands / agent skills / BooCoder skills); `AgentCommand.kind`
- `v2.5.12-provider-lifecycle-phase4` — **(phase 4/5)** HTTP API: `GET`/`PATCH /api/providers/config`, optional-subset `POST /refresh`, `GET /:id/diagnostic`. PATCH ordering validate→save→reload→clear (malformed body → 422 no-write; save-fail → 500 no-divergence); `mergeProviderConfigPatch`; +28 tests
- `v2.5.13-provider-lifecycle-phase5` — **(phase 5/5, closes the v2.3 arc)** Settings → Providers UI (status badge, enable/disable toggle, per-provider refresh, plaintext diagnostic); composer filters to `enabled && ready|loading`; curated ACP catalog + `AddProviderModal`; two mobile fixes (Settings reachable on phones; modal scroll-containment). `docs/DEFERRED-WORK.md` §2 marked addressed
- `v2.5.14-claude-md` — docs-only CLAUDE.md session-learnings (stale boocoder process after build, container `build:.` deploys working tree, wholesale `PATCH /providers/config` merge, one-shot external dispatch has no ctx tracking, `ui/` switch/sheet fallbacks, mobile Dialog scroll recipe); backfills v2.5.7–v2.5.11 doc bullets
- `v2.5.15-acp-path-guard` — security: separator-bounded worktree path guard in `acp-client-fs.ts` (closes a sibling-prefix `<worktree>-evil/` escape; `writeWorktreeTextFile` bypasses `pending_changes`, writes disk directly) via shared `resolveInWorktree` + regression test; stop tracking live `data/coder-providers.json` (gitignore + `data/coder-providers.example.json` reference; loader falls back to built-ins-only)
- `v2.6.0-phase0-foundations` — **(v2.6 Phase 0, no behavior change)** schema + interface scaffold: `session_worktrees` (one shared worktree per session) + `agent_sessions` (one backend session per `(session, agent)`) tables, `pending_changes.agent` attribution column; `AgentBackend`/`AgentSessionHandle` interface + normalized transport-agnostic `AgentEvent` union (types only)
- `v2.6.1-phase1-opencode` — **(Phase 1)** opencode as a **warm HTTP server** (`opencode serve` per BooCoder process, one opencode session per BooCode session resumed across turns via `agent_sessions`); single SSE read loop, Paseo reasoning-dedup, inactivity watchdog, stale-session guard (`config_hash` = `opencode_server|<model>`, excludes the ephemeral port so cross-restart resume survives). Hard-won: opencode streams `session.next.*` (not `message.part.*`), `event.subscribe()` must pass the worktree `directory`, models must be `llama-swap/`-prefixed + in opencode's config. Bundled: dcp-message-id strip, reopen-pane control, `[+]`/split separation, auto-name on session model, `systematic-debugging` slash command. Known limit (closed in v2.6.2): single SSE scoped to the most-recent directory
- `v2.6.2-delete-guard-and-sse` — session-delete work-loss guard (server gates `DELETE /api/sessions/:id`: reads `session_worktrees`, calls BooCoder `/worktree-risk` which runs git on the host; dirty/unpushed/unmerged → 409 + per-worktree `RiskReport[]`, `force` bypasses, fail-closed; sidebar block dialog distinguishes at-risk from couldn't-verify, never auto-commits). **Per-session SSE (P1.5-a):** one `event.subscribe({directory})` per live opencode session, each with an `AbortController`, so sessions in different worktrees stream concurrently (was: second silently dropped); `sessionID` demux guard + zombie-loop fix
- `v2.6.3-chatkey-and-skills` — re-key `agent_sessions` to **`(chat_id, agent)`** (P1.5-b: the tab/chat is the agent-context unit; two opencode tabs in one session = two contexts sharing one worktree); `tasks.chat_id` threaded end-to-end (`runOpenCodeServerTask` resolve-or-creates a chat for session-less creators); first-class `worktrees` table (one-per-session, survives session delete) supersedes the defanged `session_worktrees`; `agent_sessions.chat_id` CASCADEs from `chats`; stateful cross-chunk dcp-message-id stripper; `committing-changes` + `using-worktrees` judgment skills in `data/skills/boocode/` + parser-safe `data/AGENTS.md` preamble
- `v2.6.4-agent-sessions-fk` — converge the live `agent_sessions.session_id` FK `CASCADE → SET NULL` (standalone `confdeltype`-guarded `DO` block, idempotent — the P1.5-b re-key gate skipped already-re-keyed DBs and left it diverged); CLAUDE.md doc-sync (per-session SSE, `(chat_id, agent)` re-key, `data/AGENTS.md` parsing + `data/skills/<vendor>/` conventions)
- `v2.6.5-panes-tabs-composer` — **workspace UX batch (BooChat panes/tabs/composer + the persistence that backs it).** *Panes/tabs:* open a chat in a fresh pane (ChatTabBar "Open in new pane" + fork-beside-original via a new `open_chat_in_new_pane` event), per-pane `[+]` → New BooChat/BooTerm/BooCode menu, closing a chat pane relocates its tabs (in order) to the oldest chat/empty pane (reopen strips restored chatIds from every live pane first → no dup), stable session-scoped tab numbers (assigned on open, retired on close, never reused, map-keyed render), and the empty/landing pane became a real session history (open + separately-fetched archived chats). Removed the per-message "Open in pane" artifact button. *Persistence:* `sessions.workspace_panes` widened from bare `WorkspacePane[]` → a `WorkspaceState` envelope (`panes` + `tabNumbers`/`nextTabNumber` + `closedPaneStack`); PATCH validator zod-unions legacy-array-or-envelope and migrates on write; `session_workspace_updated` WS frame widened (web+server byte-identical, parity test green). *Composer:* morphing **Send → Stop → Queue** button keyed on `sending || activeTaskId` (folds in the standalone Stop pill, adds `cancelTask`); pasted chips trail the typed text so a leading slash stays first. *Tooling:* new read-only `read_tab_by_number` tool + an optional `ToolExecCtx` (`{ sql, sessionId }`) 4th arg on `ToolDef.execute`
- `v2.6.6-claude-md` — docs-only CLAUDE.md session-learnings from the v2.6.5 batch: the `WorkspaceState` envelope migration, the `ToolExecCtx` plumb (`read_tab_by_number` as reference), the two-schema-files-one-DB ownership split + idempotent `confdeltype` FK-action-flip pattern, and React-StrictMode nested-`setState` idempotency
- `v2.6.7-interrupt-guard` — **F.1 fix:** post-interrupt stale-terminal bug in the opencode warm-server backend (one-click reachable since `v2.6.5`'s Stop button). opencode emits one trailing `session.idle`/`session.error` for a cancelled turn (sessionID only, no turn id) that settled the *next* turn early as success. Pure per-session guard (`backends/turn-guard.ts` — arm-on-abort / swallow-one-orphan / self-heal-on-activity) wired into `opencode-server.ts`; 3 regression tests (TDD). First item of the v2.6 openspec "remaining" plan; Phase 1-UX / 2 / 3 still open
- `v2.6.8-agent-attribution` — **v2.6 Phase 1-UX** (U.1–U.6), built by 3 parallel subagents over disjoint files. Backend: `pending_changes.agent` stamped at every queue site + flows through `listPending`; new `GET /api/sessions/:id/agent-sessions` route; opencode warm-server consumes `session.next.step.ended` → accumulates `input_tokens`/`output_tokens`/`cost` on `agent_sessions`. Frontend: DiffPanel per-row agent badges + multi-agent note; AgentComposerBar resumed/history/new-session chip (gated on optional `sessionId`, BooChat unaffected); shared `providerIcons.tsx` + `useAgentSessions` hook. 9 new tests; web+coder tsc clean. Both surfaces deployed (boocoder restart + `boocode` Docker rebuild). Phase 2/3 remain
- `v2.6.9-warm-acp` — **v2.6 Phase 2:** goose/qwen run as **warm ACP backends** (one persistent `goose acp`/`qwen --acp` child + `ClientSideConnection` + ACP session per `(chat,agent)`, `initialize`+`session/new` once, reused across turns) instead of one-shot. New `WarmAcpBackend` (same `AgentBackend` interface as opencode); abort = `session/cancel` the prompt only (never kills the child); dispatcher routes goose/qwen chat-tab tasks via pure `shouldUseWarmBackend` (one-shot fallback kept for arena/MCP/`new_task`); `handleSessionUpdate` extracted to a shared pure `acp-event-map.ts` (one-shot path byte-identical). SDK concern resolved (`@agentclientprotocol/sdk@^0.22.1` has stable resume; moot warm, deferred to Phase 3). 15 new tests, 180 coder tests pass. Backend-only deploy (boocoder restart). **Smoke 2/2b pending live.** Phase 3 (lifecycle hardening) is the last v2.6 phase
- `v2.6.10-lifecycle-hardening` — **v2.6 Phase 3 (final phase — completes v2.6).** Idle TTL eviction (`AGENT_POOL_IDLE_TTL_MS`=30min) + LRU cap (`AGENT_POOL_MAX_LIVE`=10), busy backends never evicted; pure `lifecycle-decisions.ts`. Crash recovery via openchamber's health-monitor + busy-aware-restart + stale-grace state machine in `opencode-server.ts` (+ port reclaim) + `warm-acp.ts` (opencode → fresh sessions; ACP → re-`session/new`; F.1 guard + U.6 usage preserved). Orphan worktree reaper (1h grace, superset-style dirty/unpushed preflight, Paseo soft-delete) + close hooks + re-baseline after apply. 35 new tests + DB-opt-in reconnect test; 215 coder tests pass. Backend-only deploy. **Follow-ups (out of v2.6 scope): apps/server close-hook caller, 3.7 DiffPanel staging hint (frontend), live Smoke 2/2b/3.** With this, **v2.6 persistent agent sessions is complete** (Phase 0–3 + F.1 + Phase 1-UX)
- `v2.6.11-close-hooks-staging` — the two v2.6 follow-ups. **apps/server close-hook caller:** BooChat fire-and-forgets BooCoder's Phase-3 close hooks (new `coder-notify.ts`, never-rejects) on session-delete + chat archive/delete, so warm backends + worktrees tear down immediately (the idle-evict/reaper was the backstop). **Task 3.7 staging hint:** BooCoder DiffPanel shows a muted one-liner when the selected provider can't see another agent's unapplied worktree edits (pure derivation from per-change `agent` + current provider). 6 new server tests; web+server tsc/build clean; deploys via the `boocode` Docker container. **The v2.6 openspec is now fully closed** — only live Smoke 2/2b/3 remain (manual)

-----

## v2.3 — Provider lifecycle (Paseo-style registry)

**Shipped across `v2.5.4`–`v2.5.13` (5 phases, 2026-05-29).** Config-backed provider registry (`data/coder-providers.json`), merged built-ins + overrides, enable/disable toggles in Settings → Providers, two-tier probe (fast binary vs slow ACP session, TTL-gated), generic ACP spawn from config without new code paths, HTTP config/refresh/diagnostic API, curated add-from-catalog. The milestone shipped under v2.5.x patch tags (not "v2.3.x") because patch numbers are assigned at ship time. Openspec: `openspec/changes/v2-3-provider-lifecycle/` (design §2–§6 map to phases 1–4; phase 5 = UI). `docs/DEFERRED-WORK.md` §2 marked addressed; Tier-2 follow-ups (WS `provider_snapshot_updated` frame, `available_agents.enabled` column, shared types package, MCP provider tools) stay deferred.

**Lift source:** Paseo provider docs (design only — no AGPL code lift).

**Dependencies:** v2.2.

-----

## v2.4 — BooCoder as ACP agent (driveable from external editors) — DROPPED

**Status: DROPPED (Sam, 2026-06-02).** Will not be built. Sam only ever drives BooCoder through BooCoder's own surface (CoderPane) — never from an external editor (Zed/JetBrains/Avante/CodeCompanion). The outbound `boocoder acp` exposure solves a problem he doesn't have, so the milestone is closed as won't-do rather than deferred. (The `v2.4.0`/`v2.4.1` *patch tags* shipped unrelated content — Unsloth Studio parser/HTML-to-md lift, llama-sidecar routing — and never had anything to do with this milestone; patch numbers are assigned at ship time.)

The original plan (kept for record): expose `boocoder acp` (JSON-RPC over stdio) so ACP-compatible editors could drive BooCoder's write-tool surface, pending-changes queue still gating writes; lift source `zed-industries/codex-acp` (Apache-2.0), ~400 LoC. If an external-editor use case ever appears, re-open from here.

-----

## v2.6 — Persistent agent sessions (warm processes + OpenCode server)

**Goal:** make a BooCode chat map to a **persistent agent backend + a persistent worktree** that live for the whole conversation, so turns are warm and the agent sees its own accumulating edits. Replaces the one-shot-per-task model (fresh worktree + process spawn + ACP handshake every turn) with Paseo's pattern: OpenCode as a long-lived HTTP server, goose/qwen as warm stdio-ACP processes. Reasoning passthrough was already solved in v2.5.2's Thinking block — this batch is about persistence, not capability.

**Decisions locked:** persistent worktree per session (shared across agents); free agent-switch with per-agent memory (one backend session per `(chat, agent)` pair, re-keyed from `(session, agent)` in P1.5-b); OpenCode → one shared `opencode serve` HTTP server (multi-session, directory-routed); goose/qwen → warm stdio ACP per live session; claude stays one-shot PTY.

**Shipped so far:**

1. `v2.6.0-phase0-foundations` ✅ — schema + `AgentBackend`/`AgentEvent` interface scaffold (no behavior change).
1. `v2.6.1-phase1-opencode` ✅ — OpenCode warm-server backend, per-chat resumable session, SSE demux, reasoning dedup, watchdog, stale-session guard.
1. `v2.6.2-delete-guard-and-sse` ✅ — session-delete work-loss guard + **per-session SSE (P1.5-a)** so concurrent opencode sessions in different worktrees stream independently.
1. `v2.6.3-chatkey-and-skills` ✅ — **P1.5-b** re-key `agent_sessions` to `(chat_id, agent)`; first-class `worktrees` table; `tasks.chat_id` threading; cross-chunk dcp-strip; judgment skills.
1. `v2.6.4-agent-sessions-fk` ✅ — converge `agent_sessions.session_id` FK to `SET NULL`; doc-sync.

**Remaining (per openspec `v2-6-persistent-agent-sessions/tasks.md`):**

- **Phase 1 UX** — DiffPanel per-change agent attribution (`pending_changes.agent` badges), resumed/new-session chip on AgentComposerBar (`GET /api/sessions/:id/agent-sessions`), staging-boundary hint.
- **Phase 2 — warm ACP backend (goose, qwen)** — persistent `SpawnedACPProcess` connection reused across turns (one `session/new`, many prompts); dispatcher routes goose/qwen to the warm backend; switch round-trip smoke (opencode → boocode → opencode resumes the same session). **De-risked (v2 review, 2026-05-31):** `qwen --acp` is a real stdio multi-session agent (`Map<sessionId,Session>`, `loadSession`/`resume`, mid-session model/mode switch) — the old "qwen ACP was HTTP-only → use PTY" premise is **stale**, so wire qwen into the existing `acp-dispatch.ts` stack. Cross-check qwen's `@agentclientprotocol/sdk@^0.14` vs BooCode's `^0.22` handshake before relying on `unstable_resumeSession`. Separately, qwen's one-shot PTY fallback emits Claude-Code-compatible `stream-json` NDJSON (today sliced opaque in `dispatcher.ts`) — **one parser serves both qwen and claude** PTY fallbacks.
- **Phase 3 — lifecycle hardening** — idle TTL eviction per `(chat, agent)`, crash recovery, chat-close/archive worktree cleanup, orphan reaper + max-live-worktrees LRU cap, re-baseline diff after `apply_pending`, reconnect test. **Primary reference (v2 review, 2026-05-31): `openchamber` (MIT, same warm-opencode-server architecture — code-liftable)** — health-monitor + crash auto-restart + busy-aware restart + port reclaim (`killProcessOnPort`/`waitForPortRelease`) + stall-detecting SSE reader; **supersedes the Paseo "re-derive"** (paseo's lazy restart-on-demand has no active supervision). Also confirms the deferred `OPENCODE_SERVER_PASSWORD` scheme = `Authorization: Basic base64("opencode:"+pw)`.

**Lift sources:** `getpaseo/paseo` (design only — OpenCode-as-HTTP-server pattern, `streamedPartKeys` reasoning dedup), `@opencode-ai/sdk` (v2 client), `/opt/forks/opencode`; **`openchamber` (MIT — Phase 3 lifecycle hardening, code-liftable)**; **`QwenLM/qwen-code` (Apache-2.0 — `qwen --acp` reference + `stream-json` schema)**. See `boocode_code_review_v2.md` §5a/§5f for evidence.

**Dependencies:** v2.2 (ACP dispatch) + v2.3 provider lifecycle (registry/snapshot). Openspec: `openspec/changes/v2-6-persistent-agent-sessions/`.

-----

## License-debt — relicense AGPL-3.0 → MIT (shipped 2026-06-01)

**Status: SHIPPED 2026-06-01** (openspec `license-debt-mit`). Recorded 2026-05-31 from the v2 external review (`boocode_code_review_v2.md` §5k) + a direct tree audit. **Decision (Sam, 2026-05-31): relicense the project back to MIT.**

**What was the problem:** the tree was AGPL-3.0 — root `LICENSE` was GNU Affero GPL v3 and all five `package.json` declared `"license": "AGPL-3.0-only"`. Cause: the `v2.4.0`/`v2.4.1` Unsloth-Studio lifts pulled in three AGPL-3.0-only files, making the whole network-served work AGPL-encumbered (AGPL §13 network-copyleft). Clearing those three files made the MIT flip valid.

**The three AGPL-3.0-only files (cleared):**
1. `apps/server/src/services/inference/tool-call-parser.ts` (← `tool_call_parser.py`) — the Unsloth-ported algorithm (`parseToolCallsFromText`/`scanBalancedBraces` + unused nudge constants) was **dead code** (no production import; only the file + its test referenced it). Deleted it. The load-bearing parser (`extractToolCallBlocks` + the BooCode-authored streaming helpers) and `stripToolMarkup` were kept byte-identical and the AGPL header dropped. **No behavior change to the live tool-call path.**
2. `apps/server/src/services/web/html-to-md.ts` (← `_html_to_md.py`, used by `web_fetch`) — **swapped** to the MIT `node-html-markdown` library (a distinct third-party lib, not a rewrite-from-memory); `parse5` dropped. `htmlToMarkdown(html): string` signature preserved.
3. `apps/server/src/services/inference/llama-args-validator.ts` (← `llama_server_args.py`) — **clean-room rewrite** with independent structure; the managed-flag denylist re-derived from the public llama-server flag list (facts, not copyrightable).

**Key correction to the original plan:** the native-llama-server-parsing retirement (which would have needed a live qwen3.6 validation window "behind a flag for one release") was **decoupled** from the relicense and proved unnecessary — the ported parser code was already dead, so the relicense stripped *provenance, not capability*. The native-parsing retirement remains a separate, optional future optimization.

**License flip:** root `LICENSE` AGPL→MIT (`Copyright (c) 2026 indifferentketchup`); the five `package.json` `license` fields → `MIT`; AGPL SPDX headers removed from all three files; a `## License` section added to `README.md`; a guard test asserts no AGPL header / SPDX-AGPL survives. The `boocode_code_review*.md` point-in-time snapshots were left as-is. **No AGPL remains in the tree.**

**Source:** `boocode_code_review_v2.md` §1 #1, §5k; openspec `license-debt-mit`.

-----

## Write/edit robustness (planned)

**Status: planned, not started.** From the v2 review (`boocode_code_review_v2.md` §5b; `cline/cline`, Apache-2.0 — code-liftable). Two lifts that harden BooCoder's write surface where it's weakest for local quantized models:

1. **Fuzzy patch applier for `edit_file`.** BooCoder's `edit_file` is exact-match today (`apps/coder/src/services/pending_changes.ts` — `if (!content.includes(oldStr)) throw`; no whitespace/unicode tolerance, no multi-occurrence guard). Lift cline's tiered match ladder (exact → `trimEnd` → `trim` → Levenshtein ≥0.66) + unicode canonicalization (dashes, curly quotes, nbsp) + multi-occurrence guard; unmatched → warning, not throw. `apply-patch-parser.ts:347-431`.
2. **`git stash create` + private-ref checkpoint.** A per-turn workspace snapshot that captures **all** state — including edits made by dispatched external agents (opencode/claude/qwen/goose), build artifacts, test side-effects — which BooCoder's current `rewind` cannot (it only reverse-applies BooCoder's own queued `pending_changes`). Snapshot stored under a private `refs/…/checkpoints/…` ref, restorable with conversation-trim in sync. `checkpoint-hooks.ts:177-253`.

**Source:** `boocode_code_review_v2.md` §1 #3–#4, §5b.

-----

## Claude provider — SDK transport + native session resume (planned)

**Status: planned, not started.** From the v2 review (`boocode_code_review_v2.md` §5h–§5i) + a direct read of the published SDK `.d.ts` (`@anthropic-ai/claude-agent-sdk@0.3.158`, reviewed 2026-05-31). Today BooCoder dispatches `claude` one-shot via PTY (`claude --output-format stream-json`) with no continuity. Plan:

1. **Adopt the Agent SDK** (`@anthropic-ai/claude-agent-sdk`) over the PTY path. `query({ prompt, options })` yields structured `SDKMessage`s — `SDKSystemMessage` (`subtype:'init'`, carries the session id + tool/skill/mcp lists), `SDKPartialAssistantMessage` (`type:'stream_event'` deltas), `SDKResultMessage` (turn end) — no stdout scraping. `happy` (`slopus/happy`) is the working existence-proof.
2. **Native session resume via a pluggable `SessionStore`.** Implement `PostgresSessionStore implements SessionStore` (5 methods: `append`/`load`/`listSessions`/`delete`/`listSubkeys`) over BooCode's Postgres, keyed by `(chat_id, agent)`; drive turns with `query({ options: { sessionStore, resume } })` and the SDK materializes the stored session for the CLI subprocess. **This supersedes happy's SessionStart-hook + jsonl-watcher** — that was a workaround predating the feature (happy pins SDK `^0.2.96`; the `SessionStore` API is `0.3.x`). `importSessionToStore()` migrates an existing local session; `InMemorySessionStore` is the reference shape.
3. **Permissions:** wire BooCoder's permission cards to the SDK's `canUseTool(toolName, input, opts) → Promise<PermissionResult>` callback (one chokepoint, supports `permissionMode`) instead of parsing PTY permission prompts.

**License posture (reference-only):** `@anthropic-ai/claude-agent-sdk` is under **Anthropic Commercial Terms** (`package.json` `license: "SEE LICENSE IN README.md"`), **not OSS** — acceptable as a runtime **dependency** (same posture as already shelling out to the `claude` CLI), but its source/examples (incl. GitHub `examples/session-stores/postgres`) are **reference-only — do not vendor**, especially mid AGPL→MIT cleanup (relicense batch). Readable references on disk: `/opt/forks/claude-agent-sdk-python` (MIT mirror) + `/opt/forks/claude-agent-sdk-typescript` (examples, commercial terms).

**Source:** `boocode_code_review_v2.md` §1 #9, §5h–§5i. Refines the v2.6 "claude stays one-shot PTY" assumption on the continuity dimension.

-----

## v2.1.0 — Provider picker + model discovery

**Shipped `v2.1.0-provider-picker`.** Provider registry with 5 providers (boocode, opencode, goose, claude, qwen). Model discovery via `LLAMA_SWAP_URL/upstream/<model>/props`. `/api/providers` route returns installed providers with models. v2.1 `ProviderPicker` UI **superseded by `AgentComposerBar` in v2.2.** Agent-probe startup probe discovers installed agents on host, their versions, ACP support, and models. Booterm SSH host configurable via `BOOTERM_SSH_HOST`/`BOOTERM_SSH_USER` env vars.

**Shipped `v2.1.1-roadmap-cleanup`.** Roadmap reconciliation, README updates, openspec archive housekeeping. No runtime behavior changes.

-----

## v2.x — Optional / far future

- **Verify gate above pending-changes** — `augmentcode/augment-swebench-agent` majority-vote ensembler pattern (K candidate diffs → ranker model picks winner). JSONL schema only, no code lift. Combine with zeroshot blind-validation invariant. v2.0+ optional batch.
- **PR-resolver tool** — `qodo-ai/qodo-skills` PR-resolver state machine (fetch issues → batch/interactive fix → inline reply). BooCoder v2.0+.
- **Record/replay LLM harness for tests** — `qodo-ai/qodo-cover` pattern (hashed prompt → fixture YAML). Re-implement in Vitest, don't vendor (AGPL). v1.13+ test infrastructure.
- **HMAC-chained audit log** — `sipyourdrink-ltd/bernstein` pattern. Small lift, adds tamper-evident session history. v1.13+ optional.
- **Tiered tool loading** — `eyaltoledano/claude-task-master` pattern (env var: `core` / `standard` / `all`). ~30 LoC in `agents.ts`. Pattern-only lift (claude-task-master is MIT + Commons Clause; reimplement). **Shipped as `v1.13.11-tools`.**
- **Spec directory structure** — `Fission-AI/OpenSpec` `openspec/changes/<name>/{proposal,specs,design,tasks}.md` shape for BooCode's own batch docs. Zero-dep documentation reformat, replaces ad-hoc `boocode_batchN.md` convention. **Shipped as `v1.13.10-openspec`.**
- **`view_session_history` MCP tool** — `memovai/memov` `snap`/`mem_history`/`validate_commit` shape. Reference design for v1.13+ session-history feature.
- **`taste-skill` anti-slop ban list** — vendor `Leonxlnx/taste-skill` SKILL.md after diff against existing `frontend-design` skill. Real value at v2.0+ when BooCoder generates frontend code (DubDrive, BooLab, Fathom).
- **AgentLint audit pass** — manual review of BooCode's own CLAUDE.md/AGENTS.md/BOOCHAT.md/BOOCODER.md using `0xmariowu/AgentLint`'s 31 evidence-backed checks. Trim emphasis-keyword density, hit 60–120 line sweet spot, SHA-pin Actions, ensure `.env`/`CLAUDE.local.md` are gitignored. One-evening pass, immediate ROI. **Shipped as `v1.13.9-agentlint`.**
- **`budi` install (Sam's host)** — `siropkin/budi` Claude Code 5-hook observer (`SessionStart`/`UserPromptSubmit`/`PostToolUse`/`SubagentStart`/`Stop`). Local SQLite, sub-ms hook latency, dashboard at `localhost:7878`. Not a BooCode lift — install globally for Claude Code session observability.
- **Multi-provider LLM** (pi-ai pattern): Only if a concrete need for Anthropic / OpenAI / Mistral direct surfaces. llama-swap covers everything today.
- **Workflow graphs** (microsoft/agent-framework concepts): Multi-agent coordination. Conceptual reference only. Realistically a v3.x topic.
- **Secret storage primitive (prerequisite for remote OAuth MCP servers).** Pick between: sops-encrypted entries in PostgreSQL, HashiCorp Vault sidecar, or OS-level keyring on `ubuntu-homelab` accessed via a thin service. Unblocks remote OAuth MCP servers in BooCode generally. v2.x or earlier if a remote OAuth server (Sentry, Atlassian, etc.) becomes urgent.

-----

## Architecture target state

### Containers (post-v2.0)

|Container                      |Port                 |Mount                        |Purpose                                                                 |Status                |
|-------------------------------|---------------------|-----------------------------|------------------------------------------------------------------------|----------------------|
|`boochat` (was `boocode`)      |`100.114.205.53:9500`|`/opt:/opt:ro`               |Read-only chat + SPA host + MCP client                                  |Live (renames at v2.0)|
|`booterm`                      |`100.114.205.53:9501`|`/opt:/opt`                  |PTY/tmux terminal sessions                                              |**Live (May 2026)**   |
|`boocoder` (host systemd)       |`100.114.205.53:9502`|full host FS (policy-gated)  |Write tools + ACP client + MCP client + MCP server + external-CLI dispatch + warm opencode server|**Shipped v2.0.0–v2.6.6** (systemd since v2.1.0) |
|**`boochat`** (Docker service `boocode_db`)|`127.0.0.1:5500`     |`boocode_pgdata` volume      |Postgres 16-alpine (shared by all three)                                |**Live** (DB renamed from `boocode` at v2.0)|
|`codecontext`                  |`:8080` (internal, Docker network)   |`/opt:/opt:ro`|Go HTTP sidecar for code graph tools                                          |**Live (v1.12.0)**    |

### Caddy routing target (post-v2.0)

```
code.indifferentketchup.com         → boochat   :9500   (SPA + chat API + MCP client)
coder.indifferentketchup.com        → boocoder  :9502   (SPA + write API + MCP client + MCP server HTTP)
coder.indifferentketchup.com/mcp    → boocoder  :9502   (BooCoder MCP server endpoint, when remote-MCP unlocked)
term.indifferentketchup.com         → booterm   :9501   (or routed under code.*/term/)
```

### Schema additions by version

- **v1.11.0:** `messages.compacted_at`, `messages.summary`, `messages.tail_start_id`, `chats.needs_compaction`
- **v1.11.7:** none (pathGuard logic, no DB)
- **v1.12.0:** none (codecontext stateless; truncation in-memory id-map with TTL cleanup)
- **v1.12.1:** `sessions.workspace_panes jsonb` (workspace sync); drop deprecated `session_panes` table; drop stale `messages_status_check` constraint
- **v1.13.0-ai-sdk-v6:** `message_parts (id, message_id, sequence, kind, payload jsonb, created_at)` + unique `(message_id, sequence)` + `kind` CHECK; `messages_with_parts` view with COALESCE fallbacks; `ToolDef.category` field (TS type, not DB)
- **v1.13.1-cleanup-bundle:** `ALTER DATABASE boocode SET statement_timeout = '30s'` (op step, documented in schema.sql; doesn't survive volume reset)
- **v1.13.2-compaction-prune:** `message_parts.hidden_at TIMESTAMPTZ` column + partial index `(message_id) WHERE hidden_at IS NULL`; `messages_with_parts` view filters hidden parts
- **v1.13.3-truncate:** none (tmpfs id-map stored on disk under `BOOCODE_TRUNCATION_DIR`; no schema)
- **v1.13.4-reasoning-fix:** none (compaction read-side change; `CompactionMessage` extended in TS, not DB)
- **v1.13.5-stability-bundle:** none (provider config + 4 frontend/payload guards + budget constant, no schema change)
- **v1.13.6-prefix-stability:** none — verify-and-measure batch, instrumentation only; drops the originally-planned `system_prompt_cache` table since recon proved input-layer mtime caches already achieve prefix stability
- **v1.13.7-compaction-trigger:** none (compaction overflow trigger is a constant change in `services/compaction.ts`, no DB)
- **v1.13.8-tool-cost:** `tool_cost_stats` SQL view over `messages_with_parts` (no new table — view + LATERAL `jsonb_array_elements` on `tool_calls`); rolling 100-call window
- **v1.13.9-agentlint:** none (instruction-file audit + `.gitignore` add of `CLAUDE.local.md`, no DB)
- **v1.13.10-openspec:** none (docs reorganization, `git mv` only)
- **v1.13.11-tools:** none (env-var tier filter at request time, no DB)
- **v1.13.12-ws-schemas:** none (Zod schemas + wrappers in TS, no DB)
- **v1.13.13-ws-publish:** none (publish-site conversion + protocol-drift fix in `compaction.ts`, no DB)
- **v1.13.14-skills-audit:** none (skills + AGENTS.md migration into git via `.gitignore` negation patterns; no DB)
- **v1.13.15-codecontext-synth:** `message_parts.kind` CHECK constraint extended with `'synthesis'` value (DROP + DO $$ pg_constraint idempotency-guarded re-add)
- **v1.13.16-xml-parser:** none (parser change + new `tool-suggestions.ts` helper in TS, no DB)
- **v1.13.17-cross-repo-reads:** `sessions.allowed_read_paths text[] NOT NULL DEFAULT ARRAY[]::text[]` (per-session cross-repo read grants)
- **v1.13.18-codecontext-file-path:** none (path resolver in `codecontext_client.ts`, no DB)
- **v1.13.19-html-artifact-panes:** `message_parts.kind` CHECK constraint extended with `'html_artifact'` value (same v1.13.15 pattern)
- **v1.13.20-drop-legacy-cols:** `ALTER TABLE messages DROP COLUMN tool_calls, DROP COLUMN tool_results` (the strangler-fig's final phase). `messages_with_parts` view rewritten to parts-only subselects via `CREATE OR REPLACE VIEW` BEFORE the drops (Postgres ordering constraint). v1.12.1 `messages_status_check`/`messages_role_check` cleanup block removed (one-shot effective long ago)
- **v1.14:** `agents.steps` column (or AGENTS.md parser extension; no DB if file-only)
- **v1.14.x-mcp:** none — single-server MCP-client PoC is config-only at first, no schema change
- **v1.14.x-html:** `message_parts.kind` CHECK constraint extended with `'html_artifact'` value
- **v1.15:** `permissions` table, `agent_permissions` join, `session_permissions` join, `mcp_servers (name, type, transport, url_or_command, enabled, config_hash, last_probed_at)` registry
- **v1.16:** `repo_health_cache (project_id, file_hashes_sig, payload JSONB, created_at)`
- **v2.0 (shipped):** `pending_changes`, `tasks`, `available_agents`, `human_inbox` view; database renamed `boocode` → `boochat`
- **v2.2 (shipped):** none (provider snapshot + ACP dispatch are runtime/services; pane chat scoping uses existing `sessions.workspace_panes` + `chats`)
- **v2.5.0 (shipped):** `sessions.tags` column (task-model tagging)
- **v2.5.10 (shipped):** `available_agents.commands jsonb` column (persisted ACP `available_commands`)
- **v2.6.0 (shipped):** `session_worktrees` (one shared worktree per session) + `agent_sessions` (one backend session per `(session, agent)`, `backend`/`status` CHECKs) tables; `pending_changes.agent` attribution column. All idempotent (`IF NOT EXISTS`)
- **v2.6.3 (shipped):** re-key `agent_sessions` to `(chat_id, agent)` (`chat_id` FK CASCADEs from `chats`; `session_id`/`worktree_id` informational); new first-class `worktrees` table (one-per-session, `session_id` `SET NULL`) supersedes the defanged `session_worktrees`; `tasks.chat_id` column
- **v2.6.4 (shipped):** `agent_sessions.session_id` FK converged `CASCADE → SET NULL` (standalone `confdeltype`-guarded `DO` block; idempotent)
- **v2.4 (planned, not shipped):** none (`boocoder acp` is a new entry point, not a schema change)

-----

## Lift sources (headline table)

Full inventory and rationale in `boocode_code_review.md`. Headline items below; `anomalyco/opencode` is canonical (not `sst/opencode` — correction 2026-05-22).

|Source                                                                          |License                                 |Used for                                                                                                                  |Where                                         |
|--------------------------------------------------------------------------------|----------------------------------------|--------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|
|`anomalyco/opencode`                                                            |MIT, TS                                 |Compaction algorithms (`session/compaction.ts` + `session/overflow.ts`)                                                   |v1.11.0 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |Doom-loop guard (`session/processor.ts` `DOOM_LOOP_THRESHOLD=3`)                                                          |v1.11.6 ✅                                     |
|`continuedev/continue`                                                          |Apache-2.0                              |`DEFAULT_SECURITY_IGNORE_FILETYPES`                                                                                       |v1.11.7 ✅                                     |
|`nmakod/codecontext`                                                            |MIT, Go                                 |Architect: codebase map sidecar (8 MCP-shaped tools, static-wrapped)                                                      |v1.12.0 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |AI SDK v6 adoption + `streamText` swap + ReasoningPart shape                                                              |v1.13.1 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |Parts-message taxonomy (text/tool_call/tool_result/reasoning/step_start)                                                  |v1.13.0 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |`experimental_repairToolCall` via AI SDK v6                                                                               |v1.13.3 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |Two-tier compaction prune (`message_parts.hidden_at` + tier logic)                                                        |v1.13.4 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |`tool/truncate.ts` truncation + outputPath pattern (adapted: opaque id)                                                   |v1.13.5 ✅                                     |
|`anomalyco/opencode`                                                            |MIT, TS                                 |0.85×ctx_max overflow trigger formula                                                                                     |v1.13.7-compaction-trigger ✅                |
|`anomalyco/opencode`                                                            |MIT, TS                                 |`session/prompt.ts` `runLoop()` outer agent loop + `agent.steps` cap                                                      |v1.14.0-outer-loop ✅                         |
|**Anthropic MCP SDK (TypeScript)**                                              |**MIT**                                 |**MCP client, single-server PoC**                                                                                         |**v1.14.1-mcp-poc ✅**                        |
|**`claude.com/blog/using-claude-code-the-unreasonable-effectiveness-of-html`**  |**(blog, pattern only)**                |**HTML-output bias rule + use-case taxonomy**                                                                             |**v1.14.x-html**                              |
|**`anthropics/skills/web-artifacts-builder`**                                   |**MIT (design-principle reference)**    |**"Avoid AI slop" conventions inline in AGENTS.md**                                                                       |**v1.14.x-html**                              |
|**`mgechev/skills-best-practices`**                                             |**MIT (pattern)**                       |**4-step skill validation protocol with paste-ready prompts**                                                             |**v1.13.12 (skills audit)**                   |
|**`mgechev/skillgrade`**                                                        |**MIT**                                 |**Agent-agnostic skill eval framework (eval.yaml + smoke/reliable/regression presets)**                                   |**v1.13.12 (skills audit) + ongoing**         |
|**`blog.codeminer42.com/stop-putting-best-practices-in-skills/`**               |**(blog, pattern only)**                |**Rules→recipes split: skills 6% invoke vs AGENTS.md 100% present**                                                       |**v1.13.12 (skills audit)**                   |
|**`platform.claude.com/docs/.../agent-skills/best-practices`**                  |**(docs, canonical)**                   |**500-line ceiling, gerund naming, progressive-disclosure patterns, MCP `ServerName:tool_name` format**                   |**v1.13.12 + all future skills**              |
|`anomalyco/opencode`                                                            |MIT, TS                                 |`permission/evaluate.ts` wildcard ruleset                                                                                 |v1.15.0-mcp-multi (planned, not shipped)      |
|`anomalyco/opencode`                                                            |MIT, TS                                 |`mcp/index.ts` MCP client (stdio + SSE, tools/list, tools/call, OAuth RFC 7591)                                           |v1.15.0-mcp-multi ✅                          |
|`Aider-AI/aider`                                                                |Apache-2.0                              |Fallback `aider/queries/tree-sitter-*.scm` grammars                                                                       |v1.12 (fallback)                              |
|`cline/cline`                                                                   |Apache-2.0                              |Plan/Act invariant (absorbed into v1.15 permissions)                                                                      |v1.15                                         |
|`spirituslab/codesight`                                                         |MIT-ish                                 |Repo health analyzer (`analyze.mjs`)                                                                                      |v1.16                                         |
|`plandex-ai/plandex`                                                            |MIT                                     |Pending-changes data model + diff/apply/rewind UX                                                                         |v2.0                                          |
|`Dominic789654/agent-hub`                                                       |Apache-2.0                              |**Task DAG schema, dispatcher worker, project registry, human inbox** — primary architectural template for v2.0 dispatcher|v2.0                                          |
|`getpaseo/paseo`                                                                |AGPL-3.0 (**design only, no code lift**)|Daemon+clients arch, CLI verb shape, –worktree flag, provider snapshot/dispatch, OpenCode-as-HTTP-server + reasoning dedup |**v2.2, v2.6 (shipped)** / v2.x              |
|**`@opencode-ai/sdk`**                                                          |**MIT**                                 |**OpenCode warm HTTP server client (`opencode serve`, SSE `session.next.*`, multi-session)**                              |**v2.6.1 (shipped)**                          |
|**`agentclientprotocol.com` spec + `@agentclientprotocol/sdk@^0.22.1`**|**Apache-2.0**                          |**ACP client (host) — replaces raw-PTY dispatch for opencode/goose (cursor retired v2.5.3)**                              |**v2.0 → v2.2**                                      |
|**anthropics/skills `mcp-builder`**                                             |**MIT**                                 |**MCP server build workflow + 10-question evaluation framework**                                                          |**v2.0 (BooCoder MCP server)**                |
|**`zed-industries/codex-acp`**                                                  |**Apache-2.0**                          |**ACP server-side reference for `boocoder acp`**                                                                          |**v2.4**                                      |
|Roo Code: Boomerang Tasks                                                       |Apache-2.0 (pattern only)               |Orchestrator capability restriction + down-pass/up-pass context discipline                                                |v1.14 (AGENTS.md) → v2.0 (real delegation)    |
|`covibes/zeroshot`                                                              |MIT (pattern only)                      |Blind-validation invariant + complexity-classification conductor                                                          |v1.14 (AGENTS.md) → v2.0 (verify gate)        |
|`OpenHands/OpenHands`                                                           |MIT                                     |Sandbox runtime contract                                                                                                  |v2.1                                          |
|`qodo-ai/agents`                                                                |MIT                                     |`agent.toml` schema (output_schema, exit_expression, execution_strategy)                                                  |v1.14                                         |
|`qodo-ai/qodo-cover`                                                            |AGPL-3.0 (re-implement, don't vendor)   |Record/replay LLM response harness                                                                                        |v1.13+ tests                                  |
|`qodo-ai/qodo-skills`                                                           |MIT                                     |PR-resolver state machine + provider-CLI adapter pattern                                                                  |v2.0+                                         |
|`augmentcode/augment-swebench-agent`                                            |MIT                                     |Majority-vote ensembler (K diffs → ranker → winner) + JSONL schema                                                        |v2.0+ optional                                |
|`eyaltoledano/claude-task-master`                                               |MIT+Commons Clause (pattern only)       |Tiered tool loading via env var + three model roles                                                                       |v1.13.x / v1.14                               |
|`Fission-AI/OpenSpec`                                                           |permissive (verify)                     |`openspec/changes/<name>/{proposal,specs,design,tasks}.md` structure for batch docs                                       |v1.13.x / v1.14                               |
|`0xmariowu/AgentLint`                                                           |MIT                                     |31 evidence-backed checks for CLAUDE.md/AGENTS.md quality                                                                 |Immediate manual pass; v1.12.x optional plugin|
|`Leonxlnx/taste-skill`                                                          |MIT                                     |Anti-slop ban list + 3-dial parameterization pattern                                                                      |v2.0+ (BooCoder frontend output)              |
|`RA.Aid` (ai-christianson)                                                      |Apache-2.0 (pattern only)               |Three-stage Research/Planning/Implementation + expert-tool escape hatch                                                   |v1.14 (AGENTS.md)                             |
|`memovai/memov`                                                                 |MIT (pattern only)                      |`.mem` shadow timeline + `snap`/`validate_commit` MCP tool shape                                                          |v1.13+ history tool design; v2.0+ drift gate  |
|`sipyourdrink-ltd/bernstein`                                                    |(verify)                                |HMAC-chained audit log primitive                                                                                          |v1.13+ optional                               |
|`aimasteracc/tree-sitter-analyzer`                                              |MIT                                     |Outline-first patterns (`trace_impact` tool)                                                                              |v1.12 (alt) / unscheduled                     |
|`earendil-works/pi`                                                             |MIT                                     |Multi-provider LLM (`pi-ai`)                                                                                              |v2.x (optional)                               |
|`siropkin/budi` (tooling, not lift)                                             |MIT                                     |Claude Code 5-hook observer for Sam's host workflow                                                                       |Immediate (install globally)                  |
|**`aaif-goose/goose`**                                                          |**Apache-2.0**                          |**ACP agent (`goose acp`) — dispatched alongside opencode in v2.0 Path B**                                                |**v2.0 (host install)**                       |

-----

## Decisions log

- **v1.13.7 stability bundle (2026-05-22, uncommitted).** Five-fix sweep during the cosmetic-revert investigation surfaced two production-affecting regressions latent since v1.13.1-A. (1) **`@ai-sdk/openai-compatible` `includeUsage` defaults to false** — `provider.ts` never asked llama-swap to emit usage, so `tokens_used`/`ctx_used` had been NULL in every assistant row since v1.13.1-A. The fix is one line at `provider.ts:18`. No backfill for historical rows. (2) **AI SDK v6 streaming emits a stray `\n` text-delta on tool-call-only turns**, which passed `content.length > 0` and rendered an empty bubble + ActionRow between each tool call. Trim in `MessageList.flatten` (`hasText`) and defensively in `MessageBubble` (`hasContent`). (3) **`buildMessagesPayload` did not filter trailing empty or failed assistant rows** — combined with (2), a Continue retry produced `…summary-assistant, empty-assistant, failed-assistant` payloads and the upstream rejected with "Cannot have 2 or more assistant messages at the end of the list." Skip rules added at `payload.ts:64`. (4) **`BUDGET_NO_AGENT` bumped 15→30.** Every tool in `ALL_TOOLS` is read-only today; the cautious 15-cap was forward-looking for write tools that haven't landed. No-agent mode now matches `BUDGET_READ_ONLY`. None of the five changes touch schema or compaction — they're cleanup against a "v1.13.1-A regression that hadn't been caught yet" surface.
- **Skills taxonomy locked: AGENTS.md = rules, skills = recipes (2026-05-22).** Codeminer42's multi-turn eval showed plain skills invoke 6% in clean runs vs `CLAUDE.md`/`AGENTS.md` 100% present. **General workflow rules (TDD, paraphrase-before-quote, security gotchas, "never git pull/commit/push", alpha-tool-ordering, codecontext-not-RAG) belong in `AGENTS.md`; specific on-demand procedures (`/skill scaffold-component`, `/skill run-release-checklist`) belong in skills.** Hooks are for automation, not instruction delivery. The 7 vendored v1.12 skills get an audit pass in **v1.13.12** to sort each into the 4-way split (move to AGENTS.md / keep as recipe / move bulky context to `references/` / delete). Validation via `mgechev/skills-best-practices` 4-step protocol + `mgechev/skillgrade --smoke` per skill. Anthropic's `agent-skills/best-practices` page becomes the canonical convention reference (500-line ceiling, gerund naming, MCP `ServerName:tool_name` format, progressive disclosure one level deep, etc.). Documented in `BOOCHAT.md` / `BOOCODER.md` to future-proof against re-adding workflow rules as skills.
- **HTML artifacts in BooChat locked (2026-05-22).** Adopt Thariq Shihipar's "HTML > Markdown for outputs >100 lines" pattern. AGENTS.md gets the HTML-bias rule. Backend detection emits new `html_artifact` part kind. Frontend renders in three places: inline iframe preview in chat stream, "open in pane" workspace splitter integration, and download to `/opt/<project>/.boocode/artifacts/<slug>-<timestamp>.html`. Security: `sandbox="allow-scripts allow-clipboard-write allow-downloads"` with no `allow-same-origin`, CSP `connect-src 'none'`, `srcdoc=` inline (not `src=`). All of Thariq's interactive examples (sliders/knobs/SVG diagrams/copy-as-JSON) work under this sandbox because they're entirely client-side. Don't vendor `anthropics/skills/web-artifacts-builder` — its Vite + Parcel toolchain can't run in BooChat (no shell). Treat the skill's "avoid AI slop" rules as design conventions inlined in AGENTS.md.

### MCP and ACP protocol roles per surface (2026-05-22, locked)

- **BooChat = MCP client only.** Read-only tool consumer. Per-server `enabled` flag. **Hard rule: never enable a write-capable MCP server** — the read-only invariant overrides protocol convenience. Defense-in-depth: client must reject any tool whose `annotations.readOnly` is false or absent.
- **BooCoder = MCP client + MCP server + ACP client (host) + ACP agent (driveable).** Full matrix.
  - **MCP client role:** inherits v1.15 client; write-capable servers allowed but writes route through `pending_changes` queue.
  - **MCP server role:** BooCoder exposes its own task primitives (`boocoder.create_task` etc.) so external `opencode` sessions in Termius become BooCoder-aware. Stdio for local, HTTP gated on OAuth+secret storage.
  - **ACP client (host) role:** replaces raw-PTY dispatch for ACP-capable agents (opencode, goose). PTY retained as fallback for claude/pi/smallcode. Critical pattern: ACP clients auto-forward MCP `context_servers` to the dispatched agent (per goose docs) — one MCP config drives every dispatched agent.
  - **ACP agent role:** `boocoder acp` exposes BooCoder to Zed/JetBrains/Avante.nvim. Deferred to **v2.4**.
- **Why BooChat doesn't get ACP:** ACP standardizes the editor→agent direction. BooChat doesn't drive agents; it *is* the chat. Adding ACP-agent to BooChat would convert it into an opencode-equivalent — different product. Skip.
- **MCP/ACP integration phasing:** v1.14.x (single-server MCP-client PoC against Context7) → v1.15 (full MCP client + permissions) → v2.0 (BooCoder full matrix: write-capable MCP client + MCP server + ACP client) → **v2.2 (Paseo provider snapshot + ACP dispatch — shipped)** → v2.4 (BooCoder ACP agent for external editor drive).
- **Reference materials:** anthropics `mcp-builder` skill (4-phase build workflow + 10-question eval framework — required for BooCoder's MCP server before shipping), opencode MCP/ACP docs as JSON-schema interop reference, goose ACP docs for the `context_servers` auto-forward pattern, `agentclientprotocol.com` spec (note: remote ACP via HTTP/WS still WIP, v2.0 uses stdio only).
- **v1 MCP scope limit (security):** local-stdio MCP servers + Context7-style API-key remote only. Remote OAuth MCP servers (Sentry, Atlassian, etc.) deferred until BooCode has a real secret-storage primitive — token leakage from a PostgreSQL dump or Authelia bypass is a real attack surface that doesn't exist with local-stdio MCP.

### Monorepo / multi-app structure (2026-05-22, locked)

- **BooCode is a 3-surface monorepo** at `/opt/boocode/`: BooChat (`apps/server` + `apps/web`, :9500), BooCoder (`apps/coder`, :9502, **shipped v2.0–v2.2.1**, host systemd), BooTerm (`apps/booterm`, :9501, live since May 2026). One React SPA hosts chat, coder, and terminal panes.
- **Single shared database `boochat`.** Docker service `boocode_db`, all three surfaces connect to the same Postgres. Cross-surface joins are valuable (coder task → originating chat → term debugging session).
- **Mount strategy: blanket `/opt:rw`, policy enforcement at the write-tool layer.** Per-project scoping is logic, not mount. Path-guard correctness becomes the highest-priority test target for v2.0 — fuzz it, property-test it, every traversal-attack pattern (including MCP-served filesystem writes).
- **External CLI agents on the host, not in containers.** BooCoder shells out via local-exec PTY or ACP subprocess (`node-pty`, host shell, or `child_process.spawn('opencode', ['acp'])`). Host install inherits Sam's existing `~/.opencode/`, `~/.claude/`, `~/.config/goose/` configs without re-mounting. Containerize later only if a concrete reason emerges.

### Strategic pivot: Paseo-equivalent dispatcher (2026-05-22, **shipped v2.2**)

Sam wanted BooCode to function like Paseo without using Paseo itself. **Paseo is AGPL-3.0** — incompatible with BooCode's **target** MIT license and its network-served deployment at `code.indifferentketchup.com`. Solution: **reproduce the architecture in BooCode's existing Fastify + TS + PostgreSQL + React stack, using only license-clean patterns**. **Reality check (2026-05-31):** the no-AGPL-code-lift rule was later broken by the `v2.4.0`/`v2.4.1` Unsloth-Studio lifts, which put the whole tree under AGPL-3.0 (root `LICENSE` + all five `package.json` are `AGPL-3.0-only` today). The **License-debt → relicense to MIT** batch restores the MIT target.

- **Primary architectural template:** `Dominic789654/agent-hub` (Apache-2.0) — three-process model (board server + dispatcher + assistant terminal) and schema (tasks/projects/templates/pipelines/human_inbox).
- **Critical context-management primitive:** Roo Code Boomerang Tasks pattern — orchestrator with intentional capability restriction, down-pass/up-pass context discipline, no implicit inheritance.
- **Observation pattern:** Claude Code hooks (siropkin/budi reference) — register BooCode as the hook receiver for `SessionStart`/`UserPromptSubmit`/`PostToolUse`/`SubagentStart`/`Stop`.
- **Protocol-level Paseo equivalence (shipped v2.2):** the ACP client + MCP server combination in BooCoder is the protocol-spelled version of Paseo's daemon. ACP gives multi-agent dispatch with structured events instead of free-form PTY output. MCP server gives BooCoder-as-task-board, callable from any MCP client (Termius-based opencode, future editors). One MCP config feeds every dispatched agent (via `context_servers` auto-forward). v2.2 added provider snapshot, mode/thinking, permission prompts, and Paseo-style stream/persist.

**Next on this track:** v2.3 provider lifecycle shipped (`v2.5.4`–`v2.5.13`); the live frontier is **v2.6 persistent agent sessions** — Phase 0/1 + P1.5-a/b shipped (`v2.6.0`–`v2.6.4`), Phase 2 (warm ACP for goose/qwen) + Phase 3 (lifecycle hardening) remain. See openspec `v2-6-persistent-agent-sessions`.

### BooCoder execution: both Option A AND Option B, full-featured (2026-05-22)

Earlier May 18 chat recommended Option A (thin orchestration shell over OpenCode) but explicitly called the choice not-locked. Sam's call this session: ship **both** paths in the same BooCoder surface. **Option B / in-process loop** handles interactive write work with native tools + pending-changes UI (v2.0 plandex pattern). **Option A / PTY-or-ACP dispatch** handles parallel/batch work where Sam wants to A/B opencode vs claude vs goose vs pi against the same task in separate worktrees. User picks per task. **ACP replaces raw PTY wherever the agent supports it** (opencode, goose); PTY fallback retained for claude/pi/smallcode.

### v1.13.x cleanup line locked (2026-05-22)

The v1.13.x cleanup line shipped 21 batches over a single intense window in `vMAJOR.MINOR.PATCH-slug` form: **v1.13.0-ai-sdk-v6 ✅ → v1.13.1-cleanup-bundle ✅ → v1.13.2-compaction-prune ✅ → v1.13.3-truncate ✅ → v1.13.4-reasoning-fix ✅ → v1.13.5-stability-bundle ✅ → v1.13.6-prefix-stability ✅ → v1.13.7-compaction-trigger ✅ → v1.13.8-tool-cost ✅ → v1.13.9-agentlint ✅ → v1.13.10-openspec ✅ → v1.13.11-tools ✅ → v1.13.12-ws-schemas ✅ → v1.13.13-ws-publish ✅ → v1.13.14-skills-audit ✅ → v1.13.15-codecontext-synth ✅ → v1.13.16-xml-parser ✅ → v1.13.17-cross-repo-reads ✅ → v1.13.18-codecontext-file-path ✅ → v1.13.19-html-artifact-panes ✅ → v1.13.20-drop-legacy-cols ✅** → umbrella `v1.13` ✅. **Do not fold** was the discipline — each batch has a distinct rollback surface, and bisecting a 750-LoC merge across four unrelated changes is worse than four separate dispatches. Held throughout; CHANGELOG.md is the per-tag canonical record.

### v1.14–v2.2 shipped (2026-05-26)

- **v1.14.0-outer-loop** ✅ — explicit `while` loop, per-agent `steps:` cap, doom-loop migration
- **v1.14.1-mcp-poc** ✅ — Context7 MCP client validated
- **v1.15.0-mcp-multi** ✅ — multi-server MCP client, stdio transport, per-agent tool globs
- **v2.0.0-alpha through v2.0.5** ✅ — full BooCoder line: write tools, dispatcher (ACP/PTY), MCP server (6 tools, stdio, 10-question eval passed), CLI client, human inbox, Boomerang `new_task` orchestration, path-guard fuzz suite (34 traversal-attack tests), Arena, FAST_MODEL
- **v2.1.0-provider-picker** ✅ — 5-provider registry, model discovery, `/api/providers` route, agent-probe startup probe, BooCoder host systemd migration
- **v2.1.1-roadmap-cleanup** ✅ — roadmap/README/openspec archive housekeeping
- **v2.2-paseo-providers** ✅ — 7-provider snapshot, `AgentComposerBar`, ACP dispatch rewrite, permission prompts, agent commands, cursor/copilot providers
- **v2.2.1-pane-scoped-chats** ✅ — pane-scoped chat resolution, `CoderMessageList` tool UI, WS user-delta fix, inference orphan tool_call stripping

### v2.2.2–v2.6 shipped (2026-05-26 → 2026-05-31)

Full per-tag detail in the **Shipped (v2.2.2–v2.6.6)** section above and in `CHANGELOG.md`. Threads:

- **Interactive ACP** (`v2.2.2`–`v2.3.2`) ✅ — placeholder-XML reject; per-agent sampling params; `ask_user_input` cards in both BooCoder frontends; enriched `permission_requested` frame (question/plan/elicitation) with interactive PermissionCard; coder `answer_user_input` endpoint fix.
- **Unsloth lift + sidecar + task model** (`v2.4.0`–`v2.5.1`) ✅ — Unsloth Studio `tool-call-parser.ts` (replaces `xml-parser.ts`) + parse5 `html-to-md.ts` + `llama-args-validator.ts` (**all three AGPL-3.0-only — this is what relicensed the whole tree to AGPL-3.0**); llama-sidecar per-agent-flags routing; dedicated task-model services; tool budgets → 100/100/100. **→ removal tracked in the License-debt → relicense to MIT batch.**
- **Provider lifecycle = the planned "v2.3"** (`v2.5.3`–`v2.5.15`) ✅ — cursor/copilot retired; config-backed registry + snapshot lifecycle + two-tier probe (phases 1–5); empty-picker fix; claude model list; mobile composer; per-agent + claude/opencode slash-command discovery; ACP path-guard security fix.
- **v2.6 persistent agent sessions** (`v2.6.0`–`v2.6.4`) ✅ Phase 0/1 + P1.5-a/b — foundations scaffold; opencode warm HTTP server with per-chat resumable sessions; session-delete work-loss guard; per-session SSE; `(chat_id, agent)` re-key + `worktrees` table; FK convergence.
- **Workspace UX + composer** (`v2.6.5`–`v2.6.6`) ✅ — BooChat panes/tabs overhaul (open-in-new-pane, `[+]` New BooChat/BooTerm/BooCode menu, tab relocation, stable tab numbers, session-history landing pane); `workspace_panes` → `WorkspaceState` envelope; morphing Send→Stop→Queue composer + `cancelTask`; `read_tab_by_number` tool + `ToolExecCtx`; CLAUDE.md doc-sync. (This is the work the earlier draft listed as "uncommitted frontend UX" — now shipped.)

### In flight

- **v2.6 persistent agent sessions — Phase 2/3** — warm ACP backend for goose/qwen (persistent process reused across turns) + lifecycle hardening (idle eviction, crash recovery, worktree cleanup/reaper, post-apply re-baseline) + the Phase-1 UX attribution work (DiffPanel agent badges, resumed/new-session chip). See openspec `v2-6-persistent-agent-sessions/tasks.md`.

### Numbering and scope-revision discipline during v1.13.x (2026-05-23)

The v1.13.x line ran 21 batches; planned-vs-shipped numbering diverged for half of them, and three batches had material scope revisions mid-design. Pattern that emerged and is worth carrying forward:

- **Patch numbers are assigned at ship time, not in planning.** The proposal/openspec folder uses a planning slug (e.g. `v1.14.x-html-artifact-panes`); the final tag uses a concrete patch monotonic-per-minor (e.g. `v1.13.19-html-artifact-panes`). Avoids the "we said v1.13.8 but actually shipped seventh" confusion that ate two retrospective passes on the roadmap.
- **Scope-revise the proposal before dispatching.** v1.13.19-html-artifact-panes flipped mid-design from "auto-bias to HTML for >100 lines" to "Markdown default, HTML on request" — the proposal got rewritten before recon. Far cheaper than discovering the wrong approach in implementation. The "brainstorm before code" discipline.
- **Recon-first dispatch finds 25–30% more sites than the roadmap inventory.** v1.13.20 recon caught 2 extra dual-write sites (chats.ts fork-clone + 2 in tool-phase.ts) and an extra fixture file. v1.13.19 recon corrected which `Pane` type to extend. Skipping recon to save a step doesn't.
- **Adversarial reviews catch what test suites miss.** v1.13.19 reviewer caught silent error-promotion in `openInPane`; v1.13.20 reviewer caught a `RETURNING tool_calls, tool_results` clause that crashes in production but slips past green tests. Both are routine code-reviewer dispatches; both saved a same-day hotfix. **Two-stage review (spec then quality) is non-negotiable when shipping fast.**
- **Calendar-gated waits are production-safety hedges that don't apply here.** v1.13.20 originally said "wait one week of production traffic on v1.13.1 before dropping columns." Sam called it out: single-user self-hosted, no rollback constraint, code-level audit + DB COUNT query is the actual safety check. Dropped the wait. Don't ritualize production-grade hedges in a single-user codebase.

### v1.13 retrospective (what shipped)

- **v1.13.0** — `message_parts` table + dual-write at every JSON-write site. Old columns authoritative for reads. Reversible.
- **v1.13.1-A** — AI SDK v6 (`ai@^6`, `@ai-sdk/openai-compatible@^2`). `streamCompletion` rewritten as `streamText` adapter. Silent-abort bug caught and patched (explicit `if (signal?.aborted) throw`). Known regression: mid-stream tps gone — TODO for delta-cadence interpolation against `result.usage`. **Latent regression discovered v1.13.7:** `includeUsage` defaults false on `@ai-sdk/openai-compatible`, so `result.usage` resolved empty all along; tokens_used/ctx_used NULL in every row since this version. Fixed in v1.13.7.
- **v1.13.1-B** — `messages_with_parts` view with COALESCE fallbacks. Read sites switched. 1ms for 42-message chat verified.
- **v1.13.1-C** — `ask_user_input` correlation ported to parts; reasoning end-to-end (361 chars reasoning at seq 0, 429 chars text at seq 1 in smoke). `v1.13.1` tagged on `ac1a71f`. **Latent regression discovered v1.13.6:** reasoning was wired into the inference payload but NOT into compaction's head-assembly payload — summarizer model couldn't see reasoning for tool-bearing turns, degrading qwen3.6 summary quality. Fixed in v1.13.6.
- **v1.13.3** — bundle: statement_timeout=30s, alpha tool ordering, periodic stuck-row sweeper, repairToolCall wiring. Tagged on `a08d809`.
- **v1.13.4** — two-tier compaction prune. Tagged on `ec8593c`.
- **v1.13.5** — opencode truncate.ts port + view_truncated_output tool. Tagged on `f8fc5db`.
- **v1.13.6** — compaction head-assembly audit + reasoning fix. Closed the Q3 reasoning gap from v1.13.1-C. Tagged on `81d837c`.
- **v1.13.7** — stability bundle: includeUsage fix + trim guards + payload filter + budget bump. Surfaces tokens (closes a v1.13.1-A latent regression where `result.usage` resolved empty), kills the empty-bubble + ActionRow noise between tool calls on single-tool-call turns, and unblocks Continue after cap-hit on chats that have trailing empty/failed assistants.
- **v1.13.6-prefix-stability** — system-prompt prefix verify-and-measure batch (originally numbered v1.13.8 in the planning doc). Reframed mid-design from "add a `system_prompt_cache` table" to "instrument-and-prove" after recon showed input-layer mtime caches already achieve byte-stable prefixes. Smoke confirmed zero drift across 5 turns; dropped the planned DB table. Tagged on `81d837c`.
- **v1.13.7-compaction-trigger** — 0.85×ctx_max early trigger (planned as v1.13.8 / v1.13.9).
- **v1.13.8-tool-cost** — `tool_cost_stats` SQL view + AgentPicker tooltip surfacing (planned as v1.13.9 / v1.13.10).
- **v1.13.9-agentlint** — instruction-file AgentLint pass (planned as part of v1.13.11 skills audit; split into its own batch when it grew larger than fitting).
- **v1.13.10-openspec** — `openspec/changes/<slug>/{proposal,tasks,design}.md` batch-doc structure adoption.
- **v1.13.11-tools** — tiered tool loading via `BOOCODE_TOOLS=core|standard|all` env (~30 LoC; was a far-future optional item, slotted in).
- **v1.13.12-ws-schemas** + **v1.13.13-ws-publish** — Zod schemas for all 27 wire-format frames, `publishFrame`/`publishUserFrame` wrappers, ~80 publish sites converted (planned as v1.13.10 / v1.13.11).
- **v1.13.14-skills-audit** — 26 skills vendored + audited via 5 parallel agent teams; 14 kept, 11 dropped, 1 migrated to BOOCHAT.md/BOOCODER.md. Codeminer42 rules-vs-recipes framing applied.
- **v1.13.15-codecontext-synth** — forced second-inference synthesis pass for codecontext overview tools (truncation-aware extraction; auto-fetched top-N files + project docs under 32k payload budget).
- **v1.13.16-xml-parser** — Anthropic `<invoke>` parser support + Levenshtein unknown-tool recovery hints (qwen3.6 drift to Claude Code-style tool names).
- **v1.13.17-cross-repo-reads** — `request_read_access` tool + per-session `allowed_read_paths` grants; `pathGuard` extraRoots; reuses the `ask_user_input` pause/resume mechanism.
- **v1.13.18-codecontext-file-path** — `resolveProjectPath` in `codecontext_client.ts` realpath-resolves `file_path` the same way `target_dir` was already resolved.
- **v1.13.19-html-artifact-panes** — pane-based artifact viewer (Markdown default + HTML on request). Scope-revised mid-design from auto-bias-HTML to Markdown-default. `<!DOCTYPE html>` detection adds `message_parts.kind='html_artifact'` row; iframe sandbox `allow-scripts allow-clipboard-write allow-downloads` (no `allow-same-origin`); CSP `connect-src 'none'` + `X-Content-Type-Options: nosniff` + `Content-Security-Policy: sandbox` defense-in-depth. Pane state is reference-only — content fetched on mount to keep jsonb small.
- **v1.13.20-drop-legacy-cols** — final strangler-fig step. 10 dual-write sites stripped (recon caught 2 beyond the original v1.13.2 inventory). `messages_with_parts` simplified to parts-only via `CREATE OR REPLACE` before column DROPs (Postgres ordering constraint). Adversarial-review catch: `discard_stale` had `RETURNING tool_calls, tool_results` — fixed via two-step UPDATE-then-SELECT-from-view. `Message` type retains the fields, populated by the view. v1.12.1 cleanup DO block removed.
- **`v1.13` umbrella** — tagged on the same commit as v1.13.20 (`211e903`). AI SDK v6 + parts-table migration complete.

### Pre-v1.13 architectural decisions (still load-bearing)

- **Embeddings dropped from BooCode** (May 2026). Replaced RAG with file-view tools + sidecar analyzers.
- **opencode promoted to Tier A** (2026-05-20). Five algorithms identified for lift (compaction, doom-loop, repairToolCall, runLoop, permission evaluate) plus truncate.ts and MCP client.
- **OpenCode canonical repo: `anomalyco/opencode`, NOT `sst/opencode`** (correction 2026-05-22). Development moved to anomalyco; sst/opencode is the predecessor lineage. All 15 catalog references rewritten.
- **Original Batch 11 (aider PageRank port) replaced** by codecontext sidecar approach.
- **Original Batch 12 (codebase indexer w/ Harrier) removed.** No embedding infrastructure.
- **Original Batch 13 (OpenHands event log) replaced** by v1.13 parts table (opencode pattern).
- **Original Batch 12 (cline plan/act mode) absorbed into v1.15** (opencode permission ruleset).
- **Aider's `repomap.py` port dropped.** Codecontext supersedes it. Aider contribution narrows to the `.scm` query files only.
- **Globstar parked** — not an architect tool. Future verify-before-commit candidate only.
- **codeprysm rejected** — embedding-based. Node/edge taxonomy noted as reference if we ever build our own graph.
- **Batch 9 decoupled from Batch 7 (2026-05-16); shipped in `92bd3b1`.** Builtin defaults: six agents (Code Reviewer, Debugger, Refactorer, Architect, Security Auditor, Prompt Builder) with no `model` field. Session model wins by default.
- **AI SDK adoption deferred to v1.13** — and shipped as v1.13.1-A. v6 chosen (not v5) for native typed parts model and top-level `experimental_repairToolCall`.
- **`tool_choice='required'` confirmed supported** by llama-swap (qwen3.6-35b-a3b-mxfp4, 2026-05-20).
- **v1.12.0 shipped 2026-05-21.** codecontext sidecar Track B + container guidance Track A. v1.12 truncation and repairToolCall deferred into v1.13.
- **v1.12.1 workspace pane sync** (2026-05-21). Moved pane state from per-device localStorage to `sessions.workspace_panes jsonb` with WS broadcast for cross-device sync. Deprecated `session_panes` table dropped. Legacy localStorage migrates on first load.
- **v1.12.1 status indicator overhaul** (2026-05-21). ChatStatusFrame expanded from `working|idle|error` to `streaming|tool_running|waiting_for_input|idle|error`. StatusDot rewritten with distinct animations per state.
- **detectSameNameLoop reverted in v1.12.1.** Added during the 2026-05-21 debugging spike, never fired in any real run. Dead code.
- **The 2026-05-21 "freeze" debugging spike taught one lesson**: BooCode had no UI signal for the difference between a slow stream and a dead stream. v1.12.2 (live tok/s) and v1.12.3 (stale-stream banner) directly closed that gap. **v1.13's typed parts table made the inference state machine visible by construction** — the structural fix the spike pointed to.
- **v1.12.4 refactor shipped 2026-05-21/22.** `inference.ts` (1700 LoC) split into `inference/` directory before v1.13 so the AI SDK migration had clean seams. `stream-phase.ts` became the swap target for `streamText`, `tool-phase.ts` got the per-tool `category` tag (added in v1.13.0). Pure structural move, no behavior change.
- **AI SDK v6 silent-abort patched (v1.13.1-A).** `fullStream` returns normally on abort instead of throwing. Without explicit `if (signal?.aborted) throw` after the stream drain, stop button writes `complete` instead of `cancelled`. One-liner comment at the site so it survives future refactors.

### Catalog growth (2026-05-22 deep review pass)

The session-of-the-day catalog review added 50+ new entries to `boocode_code_review.md`. Decisions worth carrying into roadmap planning:

- **Tier A active lifts unchanged:** opencode, codecontext, tree-sitter-analyzer, codesight, aider.
- **Tier B / Tier C reviewed and triaged.** Most consequential additions: agent-hub (#48, primary v2.0 architectural template), Roo Boomerang Tasks (#46, v1.14 AGENTS.md pattern), zeroshot (#37, blind-validation invariant), AgentLint (#39, immediate manual audit pass), RA.Aid (#44, three-stage routing), OpenSpec (#36, batch-doc structure), bernstein (#49, HMAC audit log), memov (#42, session-history tool design), siropkin/budi (#51, install for Claude Code observability).
- **Rejected as code sources:** kilocode, costrict, prompt-tower, mycoder, reviewcerberus (closed Docker), Junie (closed), Cody (parked), VS Code extensions broadly, all Web Builders, LynxPrompt (GPL-3.0), claude-task-master code (Commons Clause), Paseo source (AGPL).
- **No additional code lifts promoted to a current version.** All catalog adds are either patterns (license-clean), references (for v2.0+), or one-off audit-pass items (AgentLint, budi install).

-----

## Workflow

Each batch:

1. Verify previous batch merged. `git log --oneline main -5`.
1. Cut branch from main. Single-branch-per-dispatch convention.
1. Dispatch via Paseo to Claude Code at `/opt/boocode`.
1. Claude Code recon → blocking questions → implement → hand back.
1. Compliance review in separate Claude chat (paste handback).
1. Build: `docker compose build --no-cache <surface>` where surface is `boocode` (chat) / `booterm` / `boocoder` (v2.0+). No-cache avoids the v1.11.2 stale-bundle trap.
1. Restart: `docker compose up -d <surface>`.
1. Smoke test in browser (hard refresh).
1. Sam commits and pushes. **Never** `git pull` / `git push` / `git commit` on his behalf.

Sam reviews all diffs. Backups before any destructive step: `cp file file.bak-$(date +%Y%m%d-%H%M%S)`.
