- MCP secrets: substituteEnvVars recursively resolves {env:NAME} in mcp.json string values from process.env before Zod (opencode-compatible); unset -> '' + boot warning, and invalid-config log names the unset vars (an empty {env:VAR} in a strict url/command field invalidates the whole config)
- data/mcp.json now untracked (.gitignore flips !data/mcp.json -> !data/mcp.example.json); tracked template data/mcp.example.json carries "{env:CONTEXT7_API_KEY}"; .env.example documents the key (9 mcp-config tests)
- Coder fix: message_complete frame model widened string -> string|null (server+web ws-frames parity); dispatcher publishes model: task.model at all 4 external completion points — a null model otherwise fail-closed in publishFrame and dropped the whole frame incl. status:'complete' (regression test)
- Coder fix: claude-sdk mapUserToolResults maps user-message tool_result blocks -> terminal tool_update events (completed/failed w/ output) so tool snapshots resolve instead of spinning forever
- Composer: AgentComposerBar drops §9b resumed/history/new chip + token readout, loses flex-wrap so the row stays one line; CoderPane gains a per-chat localStorage agent-config cache (restores last model on reopen) + threads model into the timeline/chip
- Docs: root CLAUDE.md slimmed (~190 lines), per-app refs split to apps/{coder,server,web}/CLAUDE.md; new docs/coder-backends.md, docs/project-discovery.md, docs/coding-standards/ (cross-app-contract-parity); ARCHITECTURE.md links the backends doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.3 KiB
4.3 KiB
BooCode architecture
Last updated: 2026-05-25. Navigation: AGENTS.md. Deep reference: CLAUDE.md.
System overview
flowchart TB
subgraph client [Browser]
SPA["apps/web React SPA"]
end
subgraph edge [Edge]
Caddy --> Authelia
end
subgraph host ["Host 100.114.205.53"]
subgraph docker [Docker boocode_net]
BooChat["boocode container<br/>apps/server + built web<br/>:9500"]
BooTerm["booterm container<br/>apps/booterm<br/>:9501"]
PG[("boocode_db<br/>Postgres 16<br/>database: boochat<br/>host :5500")]
CC["codecontext sidecar<br/>:8080 internal"]
end
BooCoder["boocoder.service<br/>apps/coder<br/>:9502"]
Agents["Host CLI agents<br/>opencode goose claude qwen"]
LLM["llama-swap<br/>100.101.41.16:8401"]
end
Authelia --> SPA
SPA -->|"HTTP /api WS /api/ws"| BooChat
SPA -->|"WS /ws/term"| BooTerm
SPA -->|"HTTP /api/coder proxy<br/>WS direct"| BooCoder
BooChat --> PG
BooTerm --> PG
BooCoder --> PG
BooChat -->|"HTTP tools"| CC
BooChat -->|"streamText"| LLM
BooCoder -->|"native inference"| LLM
BooCoder -->|"ACP or PTY spawn"| Agents
Agents --> LLM
Three surfaces, one database
| Surface | Code | Runtime | Primary role |
|---|---|---|---|
| BooChat | apps/server + apps/web |
Docker | Read-only chat, file tools, MCP client, skills |
| BooTerm | apps/booterm + terminal panes in apps/web |
Docker | tmux + xterm.js PTY panes |
| BooCoder | apps/coder + CoderPane in apps/web |
Host systemd | Write tools, task queue, ACP/PTY agent dispatch |
All surfaces share Postgres (boochat DB). Cross-surface joins link chats, tasks, and sessions.
BooChat request path
sequenceDiagram
participant U as User
participant W as apps/web
participant S as apps/server
participant DB as Postgres
participant L as llama-swap
U->>W: POST message
W->>S: /api/sessions/:id/messages
S->>DB: user + assistant streaming rows
S->>S: inference.enqueue()
loop outer step loop
S->>L: streamText
L-->>S: deltas / tool calls
S->>W: WS frames via broker
opt tool calls
S->>S: executeToolPhase
S->>DB: message_parts
end
end
S->>DB: finalize message
S->>W: session_updated user frame
Key modules: services/inference/turn.ts (outer loop), stream-phase.ts (AI SDK adapter), tool-phase.ts, services/broker.ts, hooks/useSessionStream.ts.
BooCoder execution paths
flowchart LR
Msg["User message<br/>CoderPane"] --> Route{"provider?"}
Route -->|boocode| Inf["In-process inference<br/>pending_changes queue"]
Route -->|external| Task["tasks row<br/>dispatcher poll"]
Task --> ACP["ACP dispatch<br/>opencode goose"]
Task --> PTY["PTY dispatch<br/>claude qwen"]
ACP --> Host["spawn install_path<br/>on host"]
PTY --> Host
Inf --> Apply["apply_pending → disk"]
Since v2.1.0, BooCoder runs on the host (not Docker). Agent binaries spawn directly — no SSH tunnel.
See coder-backends.md for the full dispatch-backend reference: routing predicates, the warm vs. one-shot lifecycle, agent-session resume, and the provider-discovery pipeline.
Supporting services
| Service | Reachability | Purpose |
|---|---|---|
| codecontext | http://codecontext:8080 from Docker network |
Code graph / symbol analysis (Go sidecar) |
| llama-swap | LLAMA_SWAP_URL env |
Local LLM inference + model props |
| SearXNG | SEARXNG_URL (Tailscale Fathom) |
web_search / web_fetch when enabled |
| MCP servers | /data/mcp.json config |
Optional tools (e.g. Context7), read-only in BooChat |
Config and data files
| Path | Role |
|---|---|
data/AGENTS.md |
Global agent registry (bind-mounted /data/AGENTS.md) |
data/mcp.json |
MCP server config (opencode-compatible shape) |
data/skills/ |
On-demand skill library |
BOOCHAT.md / BOOCODER.md |
Container guidance (mtime-cached into system prompt) |
apps/server/src/schema.sql |
Canonical DB schema |
Deploy topology
- BooChat + BooTerm + Postgres + codecontext:
docker compose up --build -dfrom/opt/boocode - BooCoder:
pnpm -C apps/server build && pnpm -C apps/coder build && sudo systemctl restart boocoder - Ports bind to Tailscale IP
100.114.205.53, not0.0.0.0— use that IP for host smoke curls