# BooCode v1.x — Roadmap Last updated: 2026-05-21 ## Overview BooCode is a standalone code-chat tool at `/opt/boocode/`. Read-only by design — pick a project, chat with a local LLM that has file-inspection tools, get streaming responses over WebSocket. Live at `https://code.indifferentketchup.com` (Caddy → Authelia → Tailscale → `100.114.205.53:9500`). **Architectural commitments:** - No embeddings. Model uses file-view tools (`view_file`, `list_dir`, `grep`, `find_files`) + sidecar analyzers (codecontext, codesight) + codecontext MCP tools. Walked away from the RAG pipeline May 2026. - Read-only in v1.x. Write tools land in BooCoder (separate container, post-v1.x). - One Postgres (`boocode_db`), one frontend SPA, container-per-service for new capabilities. External code lifted from / referenced in: see `boocode_code_review.md` for full inventory. ----- ## Shipped (status as of 2026-05-21) | 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//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` | ----- ## In flight (uncommitted on disk, 2026-05-21) v1.12.1 work — landed today, not yet committed: | Item | Status | Notes | |---|---|---| | Server-side workspace pane sync | Done | `sessions.workspace_panes jsonb` column; PATCH endpoint; `session_workspace_updated` WS frame; localStorage migration on first load; deprecated `session_panes` table dropped | | Richer status indicators | Done | Five states (`streaming` / `tool_running` / `waiting_for_input` / `idle` / `error`) with distinct visuals: amber orbiting dots for streaming, amber spinning ring for tool execution, blue static for waiting on user, emerald/gray/red for idle/error | | Startup hung-row sweep | Done | `UPDATE messages SET status='failed' WHERE status='streaming' AND created_at < NOW() - INTERVAL '5 minutes'` on server boot | | One stuck row from v1.12.0 smoke | Cleared | Manual UPDATE (`d63c25b1`) | | `detectSameNameLoop` code path | Added, never fired | Candidate for revert in next batch — dead code | | Diagnostic logging in inference.ts | Added for debugging | Must come out before commit | ----- ## v1.12.x cleanup (NEXT — small, immediate) Five items. Group them or split them — your call. ### v1.12.1 — commit consolidation **Action items, in order:** 1. **Remove diagnostic logging** from `apps/server/src/services/inference.ts`. The 12 `ctx.log.info` calls added today proved the inference loop was functioning correctly; the prompts were just slow. Verbose for production. Strip them, keep the file clean. 2. **Revert `detectSameNameLoop`.** Three additions in inference.ts: - `DOOM_LOOP_SAME_NAME_THRESHOLD = 5` constant - `detectSameNameLoop()` function - Call site in `runAssistantTurn` immediately after the existing `detectDoomLoop` check Never fired in any real run today. Dead code. The existing `detectDoomLoop` (identical args, threshold 3) is sufficient. 3. **Drop the stale `messages_status_check` CHECK constraint** in `apps/server/src/schema.sql`. Two constraints exist on the table: - `messages_status_check` allows `streaming|complete|failed` (old, stale) - `messages_status_chk` allows `streaming|complete|failed|cancelled` (new) The old one prevents `cancelled` from being written. Drop it with `ALTER TABLE messages DROP CONSTRAINT IF EXISTS messages_status_check;`. 4. **Stop-handler writes terminal status.** When user clicks stop mid-stream, the abort path must `UPDATE messages SET status='cancelled' WHERE id = $assistantMessageId AND status='streaming'`. Currently rows just sit `streaming` forever. The startup sweep catches them on restart, but they should be written immediately. Edit `apps/server/src/services/inference.ts` `handleAbortOrError` to add the UPDATE. 5. **Commit + tag v1.12.1.** Include the workspace pane sync, status indicator overhaul, startup sweep, and items 1–4 above. Single commit per item is fine; tag at end. **Estimated:** ~150 LoC net (deletions dominate). ### v1.12.2 — live throughput display (small UX win) Surface `tokens_per_second` and `ctx_used` next to the status indicator while streaming. Backend already emits these in the `usage` frame; just consume them in the StatusDot wrapper or a sibling component. ~80 LoC, frontend-only. ### v1.12.3 — stale-stream frontend banner When a chat has a `streaming` row older than ~60s with no new tokens, the UI should surface a "Previous response didn't complete. [Retry] [Discard]" banner instead of silently queueing new sends. Today's debugging spent four hours misreading slow streams as dead; this is the UX fix that prevents that. ~150 LoC, frontend + small backend endpoint for the discard action. ----- ## v1.13 — Phase B: parts table + AI SDK + per-tool tagging **Goal:** typed message parts replace JSON blobs on `messages.tool_calls` / `tool_results`. Adopt Vercel AI SDK `streamText`. Tag tools as `read_only` or `write` at definition time. **Scope:** 1. Schema: new `message_parts` table (`id, message_id, kind, payload JSONB, sequence`). Kinds: `text`, `tool_call`, `tool_result`, `reasoning`, `step_start`. The `messages` table becomes header-only. 2. Inference loop rewritten on AI SDK `streamText`. `streamCompletion` becomes a thin wrapper. Native AI SDK `experimental_repairToolCall` replaces v1.12's hand-rolled version. 3. Tool registry: `ToolDef` gains `category: 'read_only' | 'write'` field. BooCode v1.x rejects any `write` tool at registry time (defense in depth for the BooCoder split). Alpha-sort tool list before sending to model (prompt-cache stability). 4. Reasoning content (`reasoning_content` from Qwen3.6) captured as its own part type instead of dropped or inlined. **Migration risk:** non-trivial. `inference.ts` is ~1700 lines with custom XML fallback, SSE parsing, compaction integration. Plan dedicated cutover window. `compaction.ts` must update to assemble head from parts. **Replaces:** Original Batch 13 (append-only event log) — same outcome, different vocabulary. **Today's debugging spike validates this work.** Four hours of confusion came from JSON-blob `tool_calls` / `tool_results` columns hiding state from logs and from the inference state machine being invisible. Typed parts + per-part status would have shown the slow-stream-vs-dead distinction in seconds. **Dependencies:** v1.12.x cleanup merged. **Estimated:** ~1500 LoC. ----- ## 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. 2. `agent.steps ?? Infinity` per-agent step cap. AGENTS.md gains `steps:` field. Refactorer `steps: 5`, Architect `steps: 20`, etc. 3. 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). 4. Doom-loop guards (v1.11.6) migrate from "abort recursion" to "raise within loop iteration." Same predicate, different control flow. **Dependencies:** v1.13 merged. **Estimated:** ~800 LoC. ----- ## v1.15 — Phase D: permission ruleset + 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. 2. MCP client implementation: SSE transport, `tools/list` discovery, `tools/call` invocation. codecontext sidecar gets re-pointed from static wrappers (v1.12) to real MCP. New connectors become a config-only addition. 3. UI: permission-ask flow when a tool requires `ask` action. Modal or inline card with Allow once / Allow always / Deny. 4. v1.x stays read-only by default (no `write` tools in the registry yet). **Absorbs:** Original Batch 12 (tool approval + plan/act mode) — same outcome via permission rules instead of mode enum. **Dependencies:** v1.13 merged (parts table for permission events). Independent of v1.14. **Estimated:** ~600 LoC. ----- ## v1.16 — Batch 11b: 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)`. **Dependencies:** v1.12 merged (can reuse codecontext parse output where overlapping). **Estimated:** ~400 LoC. ----- ## v2.0 — BooCoder pending changes New container `boocoder` at `100.114.205.53:9502`. Owns write tools (`edit_file`, `create_file`, `delete_file`, `apply_pending`, `rewind`). Edits queue in `pending_changes` table; nothing touches disk until `/apply`. Per-pane diff UI with Approve/Reject. BooCode chat stays read-only (`/opt:/opt:ro`). **Lift source:** plandex pending-changes data model. **Dependencies:** v1.13 (parts) + v1.15 (permissions). **Estimated:** ~1200 LoC. ----- ## v2.1 — BooCoder runtime isolation 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. **Lift source:** OpenHands V1 runtime pattern. **Dependencies:** v2.0. **Estimated:** ~600 LoC. ----- ## v2.x — Optional / far future - **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. ----- ## Architecture target state ### Containers | Container | Port | Mount | Purpose | Status | |---|---|---|---|---| | `boocode` | `100.114.205.53:9500` | `/opt:/opt` | Chat + read-only tools + SPA | Live | | `boocode_db` | `127.0.0.1:5500` | `boocode_pgdata` volume | Postgres 16-alpine | Live | | `booterm` | `100.114.205.53:9501` | `/opt/repos:/opt/repos:rw` | Terminals (tmux + node-pty) | Live (v1.10.0) | | **`codecontext`** | **`:8765` (internal)** | **`/opt/projects:/workspace:ro`** | **MCP server for architect tools** | **Live (v1.12.0)** | | `boocoder` | `100.114.205.53:9502` | per-session sandbox | Write tools | v2.0 | ### 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:** `message_parts` table; `messages` becomes header-only - **v1.14:** `agents.steps` column (or AGENTS.md parser extension; no DB if file-only) - **v1.15:** `permissions` table, `agent_permissions` join, `session_permissions` join - **v1.16:** `repo_health_cache (project_id, file_hashes_sig, payload JSONB, created_at)` - **v2.0:** `pending_changes (id, session_id, file_path, diff TEXT, status, created_at)` ----- ## Lift sources (summary) Full inventory in `boocode_code_review.md`. Headline items: | Source | Used for | Where | |---|---|---| | `sst/opencode` (MIT, TS) | Compaction algorithms | v1.11.0 (shipped) | | `sst/opencode` (MIT, TS) | Doom-loop guard | v1.11.6 (shipped) | | `sst/opencode` (MIT, TS) | `repairToolCall`, truncate.ts, MCP client, permission evaluate, runLoop | v1.12 (shipped) / v1.13 / v1.14 / v1.15 | | `continuedev/continue` (Apache-2.0) | `DEFAULT_SECURITY_IGNORE_FILETYPES` | v1.11.7 (shipped) | | `nmakod/codecontext` (MIT, Go) | Architect: codebase map sidecar | v1.12.0 (shipped) | | `spirituslab/codesight` (MIT-ish, TS) | Architect: repo health analyzer | v1.16 | | `Aider-AI/aider` (Apache-2.0) | Fallback `.scm` grammars | v1.12 (fallback) | | `cline/cline` (Apache-2.0) | Plan/Act pattern (absorbed into v1.15 permissions) | v1.15 | | `plandex-ai/plandex` (MIT) | Pending-changes data model | v2.0 | | `OpenHands/OpenHands` (MIT) | Sandbox runtime contract | v2.1 | | `aimasteracc/tree-sitter-analyzer` (MIT) | Outline-first patterns | v1.12 (alt) | | `earendil-works/pi` (MIT) | Multi-provider LLM | v2.x (optional) | ----- ## Decisions log - **Embeddings dropped from BooCode** (May 2026). Replaced RAG with file-view tools + sidecar analyzers. - **Original Batch 11 (aider PageRank port) replaced** by codecontext sidecar approach. - **Original Batch 12 (codebase indexer w/ Harrier) removed.** No embedding infrastructure in BooCode v1.x. - **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. - **opencode lift opened** (2026-05-20). Started with compaction (v1.11.0). Continuing through v1.15. Five distinct algorithms: compaction, doom-loop guard, repairToolCall, runLoop, permission evaluate. Plus `truncate.ts` and MCP client. Each lifts the algorithm, not the Effect-TS plumbing. - **AI SDK adoption deferred to v1.13.** Hand-roll repairToolCall in v1.12 — not actually done in v1.12.0; truncation also deferred. v1.12.0 shipped codecontext + container guidance + skills only. - **`tool_choice='required'` confirmed supported** by llama-swap (qwen3.6-35b-a3b-mxfp4, 2026-05-20). - **v1.11.4 cancelled** (2026-05-20). Per-turn budget reset + Continue affordance + CapHitSentinel were already shipped in v1.8.2. - **v1.12.0 shipped** (2026-05-21). codecontext sidecar Track B + container guidance Track A. v1.12 truncation and repairToolCall were deferred into v1.13's AI SDK migration where they get for-free. - **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. Added `executeToolPhase`-entry `tool_running` publish. - **detectSameNameLoop reverted** (planned v1.12.1). Added during the 2026-05-21 debugging spike to catch same-tool-name-with-different-args loops. Never fired in any real run because the existing `detectDoomLoop` covers the actual failure modes. Dead code, reverting. - **The 2026-05-21 "freeze" debugging spike taught one lesson**: BooCode has no UI signal for the difference between a slow stream and a dead stream. Diagnostic logging (added today, reverted in v1.12.1) revealed the inference loop was working correctly throughout — what looked like four hours of deterministic hang was multiple instances of qwen3.6 generating 8k tokens of self-doubt at temperature 0.2 on a "find the bug" prompt with no real bug. v1.12.2 (live tok/s display) and v1.12.3 (stale-stream banner) directly address this gap. ----- ## Workflow Each batch: 1. Verify previous batch merged. `git log --oneline main -5`. 2. Cut branch from main. Single-branch-per-dispatch convention. 3. Dispatch via Paseo to Claude Code at `/opt/boocode`. 4. Claude Code recon → blocking questions → implement → hand back. 5. Compliance review in separate Claude chat (paste handback). 6. Build: `docker compose build --no-cache boocode` (no-cache avoids the v1.11.2 stale-bundle trap). 7. Restart: `docker compose up -d boocode`. 8. Smoke test in browser (hard refresh). 9. Sam commits and pushes. **Never** `git pull` / `git push` / `git commit` on his behalf. Sam reviews all diffs.