Status indicator (StatusDot): drops the flat amber pulse for a richer set of states — orbiting amber for streaming, spinning sky ring for tool_running, static violet for waiting_for_input, plus the existing idle/error. Backend chat_status frame widens from 'working|idle|error' to discriminate streaming vs tool execution vs paused for user input. Workspace pane sync: pane layout moves from per-device localStorage to server-side sessions.workspace_panes jsonb. PATCH /api/sessions/:id/workspace broadcasts session_workspace_updated on the user channel for cross-device live sync. Echo dedup via JSON comparison so the round-trip frame doesn't loop. Legacy localStorage seeds the server on first hydrate, then is deleted. Deprecated session_panes table dropped. Resilience: startup sweep marks any stale 'streaming' message older than 5 minutes as 'failed' so v1.12.0-style hung rows clear on container restart. useWorkspacePanes gains validatePanes() to prune dead chatId references from saved pane state when the chat list lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
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/<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 |
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:
-
Remove diagnostic logging from
apps/server/src/services/inference.ts. The 12ctx.log.infocalls added today proved the inference loop was functioning correctly; the prompts were just slow. Verbose for production. Strip them, keep the file clean. -
Revert
detectSameNameLoop. Three additions in inference.ts:DOOM_LOOP_SAME_NAME_THRESHOLD = 5constantdetectSameNameLoop()function- Call site in
runAssistantTurnimmediately after the existingdetectDoomLoopcheck
Never fired in any real run today. Dead code. The existing
detectDoomLoop(identical args, threshold 3) is sufficient. -
Drop the stale
messages_status_checkCHECK constraint inapps/server/src/schema.sql. Two constraints exist on the table:messages_status_checkallowsstreaming|complete|failed(old, stale)messages_status_chkallowsstreaming|complete|failed|cancelled(new)
The old one prevents
cancelledfrom being written. Drop it withALTER TABLE messages DROP CONSTRAINT IF EXISTS messages_status_check;. -
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 sitstreamingforever. The startup sweep catches them on restart, but they should be written immediately. Editapps/server/src/services/inference.tshandleAbortOrErrorto add the UPDATE. -
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:
- Schema: new
message_partstable (id, message_id, kind, payload JSONB, sequence). Kinds:text,tool_call,tool_result,reasoning,step_start. Themessagestable becomes header-only. - Inference loop rewritten on AI SDK
streamText.streamCompletionbecomes a thin wrapper. Native AI SDKexperimental_repairToolCallreplaces v1.12's hand-rolled version. - Tool registry:
ToolDef<T>gainscategory: 'read_only' | 'write'field. BooCode v1.x rejects anywritetool at registry time (defense in depth for the BooCoder split). Alpha-sort tool list before sending to model (prompt-cache stability). - Reasoning content (
reasoning_contentfrom 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:
- 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.
agent.steps ?? Infinityper-agent step cap. AGENTS.md gainssteps:field. Refactorersteps: 5, Architectsteps: 20, etc.- 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). - 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:
- Wildcard rule matcher:
{ permission, pattern, action: 'allow' | 'deny' | 'ask' }. Last-match-wins. Per-agent rulesets layer under per-session rulesets. - MCP client implementation: SSE transport,
tools/listdiscovery,tools/callinvocation. codecontext sidecar gets re-pointed from static wrappers (v1.12) to real MCP. New connectors become a config-only addition. - UI: permission-ask flow when a tool requires
askaction. Modal or inline card with Allow once / Allow always / Deny. - v1.x stays read-only by default (no
writetools 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 deprecatedsession_panestable; drop stalemessages_status_checkconstraint - v1.13:
message_partstable;messagesbecomes header-only - v1.14:
agents.stepscolumn (or AGENTS.md parser extension; no DB if file-only) - v1.15:
permissionstable,agent_permissionsjoin,session_permissionsjoin - 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 nomodelfield. 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.tsand 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 jsonbwith WS broadcast for cross-device sync. Deprecatedsession_panestable dropped. Legacy localStorage migrates on first load. - v1.12.1 status indicator overhaul (2026-05-21). ChatStatusFrame expanded from
working|idle|errortostreaming|tool_running|waiting_for_input|idle|error. StatusDot rewritten with distinct animations per state. AddedexecuteToolPhase-entrytool_runningpublish. - 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
detectDoomLoopcovers 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:
- Verify previous batch merged.
git log --oneline main -5. - Cut branch from main. Single-branch-per-dispatch convention.
- Dispatch via Paseo to Claude Code at
/opt/boocode. - Claude Code recon → blocking questions → implement → hand back.
- Compliance review in separate Claude chat (paste handback).
- Build:
docker compose build --no-cache boocode(no-cache avoids the v1.11.2 stale-bundle trap). - Restart:
docker compose up -d boocode. - Smoke test in browser (hard refresh).
- Sam commits and pushes. Never
git pull/git push/git commiton his behalf.
Sam reviews all diffs.