Update boocode_roadmap.md's shipped section through v2.6.4 (provider lifecycle, persistent agent sessions, cursor/copilot retirement) and add boocode_code_review_v2.md — a point-in-time external-fork lift/cross-check findings doc (Paseo + opencode + llama.cpp + the second fork sweep), companion to the standing boocode_code_review.md inventory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
40 KiB
BooCode — External Code Review v2 (lift findings)
Last updated: 2026-05-31
A point-in-time findings doc, not a standing inventory. It consolidates two reconnaissance passes against the upstream forks at /opt/forks/ and decides, per area, what BooCode should do about it. Pin it so the same upstreams aren't re-evaluated from scratch next month.
Companion docs:
boocode_code_review.mdis the standing external-repo inventory (every repo BooCode references, why each earned its row, license analysis).boocode_roadmap.mdis the canonical shipping-state / version-ordering source. This v2 doc is the action layer on top of both: "given what's upstream as of 2026-05-31, here's the lift/cross-check/re-derive/n-a call." Reconcile shipping state via the roadmap when in doubt; fold durable rows back intoboocode_code_review.md.
Sources feeding this doc
- Paseo recon (Sam) — two passes: a Phase 2/3 server-manager recon and a claude-transport recon. Conclusions consolidated by area below (§2a). AGPL-3.0 — pattern-only, no code lift, ever.
- Three-fork agent sweep (this session, 2026-05-31) — read-only general-purpose agents over
anomalyco/opencode(MIT, code-liftable),getpaseo/paseo(AGPL, pattern-only),ggml-org/llama.cpp(consumed via llama-swap/sidecar — adopt features/flags, not C++). Detail in §2–§4. - Second fork sweep (this session, 2026-05-31) — 8 read-only agents over the remaining 11 repos in
/opt/forks/(conductor, superset, openchamber, happy, cline, qwen-code, amp-acp, pi-acp, claude-code, goose, unsloth). Detail in §5; high-value items folded into §1.
Caveats
/opt/forks/llama.cppis a shallow clone (90 commits, ~5 days visible). llama.cpp findings are read from source as it stands today; "what changed when" attribution is limited.git fetch --unshallowbefore the next review./opt/forks/opencodearrived shallow (rooted 2026-05-25); the agent rangit fetch --unshallowand re-surveyed the real 6-week window. opencode also did a v2 Effect/event-sourced rewrite (packages/core/,packages/llm/) — most of that churn is architecturally divergent and ruled out.- HEADs at review time: paseo
41cb1af(main, v0.1.87), opencode1afa9e3(dev, ~v1.15.13), llama.cppaa46bda8(detached).
Verdict legend
| Verdict | Meaning |
|---|---|
| LIFT | Take it. Flavor noted: code-lift (MIT), pattern-lift (AGPL/clean-room re-impl), config-adopt (new upstream flag), drop-our-code (upstream now does it → delete ours). |
| RE-DERIVE | Idea is right, their impl is insufficient/divergent for our needs — write fresh, don't adapt theirs. |
| CROSS-CHECK | We already have it; confirmed current vs upstream. No action. |
| TRACK | Behavioral/external change to be aware of. No code action now. |
| N-A | Not liftable into our architecture, or reduces to a separate decision. |
1. Net actionables (priority roll-up)
Updated after the second fork sweep (2026-05-31, §5). New items from that sweep are tagged ⁺.
| # | Item | Source | Verdict | Maps to | Effort |
|---|---|---|---|---|---|
| 1 | Retire the AGPL tool-call parser + html-to-md — llama-server parses qwen3.x <tool_call>/<function=> server-side; both lifted files are confirmed AGPL-3.0-only (§5k); swap html-to-md to a permissive lib |
llama.cpp + unsloth⁺ | LIFT · drop-our-code | license-debt / inference; new batch | M, staged + gated on jinja |
| 2 | Warm-ACP backend (goose/qwen) — one spawn, one session/new, many prompts; validated by qwen's own qwen --acp reference impl (the "qwen ACP was HTTP-only" premise is stale) |
Paseo recon + qwen-code⁺ | LIFT · pattern | v2.6 Phase 2 | M |
| 3 ⁺ | Fuzzy patch applier for edit_file — exact→whitespace→Levenshtein match ladder + unicode canon + multi-occurrence guard; BooCoder's edit_file is exact-.includes-or-throw today |
cline⁺ | LIFT · code | edit/diff robustness (local-model drift) | M |
| 4 ⁺ | git stash create + private-ref checkpoint — per-turn workspace snapshot capturing all state incl. external-agent edits (BooCode rewind only undoes its own queued edits) |
cline⁺ | LIFT · code | checkpoint/restore UX | M |
| 5 ⁺ | opencode lifecycle hardening — health monitor + crash auto-restart + busy-aware restart + port reclaim + stall-detecting SSE; MIT, same warm-server architecture (supersedes the paseo RE-DERIVE — better source) | openchamber⁺ | LIFT · pattern/code | v2.6 Phase 3 | M |
| 6 | Post-interrupt stale-terminal guard — confirmed correctness bug in opencode-server.ts |
opencode/paseo (verified) | LIFT · pattern (bugfix) | v2.6 Phase 1/2 | S (~½ day) |
| 7 ⁺ | Parse qwen/claude stream-json NDJSON in PTY fallback — today stdout is sliced opaque; one parser serves both (Claude-Code-compatible schema) |
qwen-code⁺ | LIFT · pattern | v2.6 Phase 2 / dispatch parsing | S |
| 8 | ctx/token usage for opencode sessions — session.next.step.ended already on the wire |
opencode + paseo (converged) | LIFT · code | v2.6 Phase 1 UX | S–M (~80–150 LoC) |
| 9 | Claude continuity + transport — --resume via hook/jsonl-watcher → claudeSessionId; happy proves the @anthropic-ai/claude-agent-sdk path (resolves the SDK-vs-PTY decision — lean SDK) |
Paseo recon + happy⁺ | LIFT · code + decision | claude-provider batch | M |
| 10 ⁺ | Universal-agent notify-hook → normalized status — inject a hook into each PTY agent's config, normalize ~30 event names → 5 states; gives goose/qwen/claude real working/blocked/done signals | superset⁺ (clean-room, ELv2) | RE-DERIVE | v2.6 Phase 2/3 status | M–H |
| 11 | New sampling knobs top_n_sigma, dry_* family; --reasoning-budget |
llama.cpp | LIFT · config-adopt | AGENTS.md frontmatter + validator allowlist | S |
| 12 ⁺ | File-provenance compaction ledger (## Files Read/Modified) + MistakeTracker (heterogeneous-failure recovery) |
cline⁺ | LIFT · pattern | context-mgmt / recovery | S–M |
| 13 | Bundle/watch: stall-timeout + retry/backoff (opencode); worktree-archive cascade (paseo); LRU-bound caches; subagent permission demux; tool-pair-atomic prune cross-check (cline)⁺; diff-line→agent re-prompt (superset)⁺ | mixed | WATCH | Phase 2/3, review UX, resilience | varies |
Headline: #1 stays the strategic win and is now settled, not speculative — the unsloth recon confirmed both lifted files are AGPL-3.0-only (§5k); the only gate is the jinja config check (§6). The second sweep added four genuinely-new code lifts: #3 fuzzy patch applier and #4 git-stash checkpoint (both cline, both directly fix where BooCoder's write/edit surface is weakest for local models), #5 openchamber lifecycle hardening (the concrete, MIT, same-architecture answer to v2.6 Phase 3 — supersedes the weaker paseo re-derive), and #7 stream-json parsing (cheap, shared by qwen+claude PTY). #2 Phase-2 warm-ACP is now de-risked by qwen's own reference impl. #9 resolves the claude direction (lean SDK).
2. Paseo (AGPL-3.0 — pattern-only)
2a. Consolidated recon, by area (Sam's two passes)
| Area | Verdict | One-line |
|---|---|---|
| OpenCode server lifecycle | CROSS-CHECK | Paseo hand-rolls the spawn (not createOpencodeServer), waits for "listening on" on stdout, port-0 allocation, concurrent callers wait on one startPromise, no OPENCODE_SERVER_PASSWORD. Same shape BooCode shipped in v2.6.1 — nothing to lift. |
| OpenCode crash recovery + reconnect | RE-DERIVE → superseded | Lazy restart-on-demand (exit handler nulls the server, next getCurrentServer() respawns), no active supervision; resumeSession does not verify the session exists on disk before resuming. Insufficient for Phase 3. Update (2nd sweep): openchamber (§5c) has a better, MIT, same-architecture version — health-monitor state machine + crash auto-restart + busy-aware restart. Lift from openchamber, not paseo. |
| Warm-ACP supervision (goose/qwen) | LIFT · pattern | SpawnedACPProcess: one spawn, one session/new, many prompts; child lives for the session not the turn; per-turn abort = connection.cancel({sessionId}) without killing the child; child-exit fires turn_failed (no restart). Clean signal split; integrates against BooCode's existing acp-dispatch.ts. This is the Phase 2 lift — and qwen-code (§5f) ships its own qwen --acp reference impl that validates the whole approach. |
| OpenCode reasoning dedup | CROSS-CHECK | streamedPartKeys keyed reasoning:${partID}; delta adds the key, final part skips if present, cleared per turn. Identical to v2.6.1. |
| Claude transport | N-A | Paseo uses @anthropic-ai/claude-agent-sdk in stream-json mode, not PTY. Getting Paseo's transport means adopting the SDK — net-new integration, not a lift. |
| Claude continuity | LIFT · code | claude --resume <sessionId> across turns: capture the session id from claude's output, store it, pass --resume next turn; claude re-reads its transcript and continues. Small change to BooCode's PTY dispatch (run with --output-format stream-json, parse the id, persist, resume). The actionable claude finding. |
| Claude streaming/parsing | N-A | Structured events (tool calls, reasoning, partials) come from the SDK; PTY degrades to scraping. Adopting structured claude streaming = adopting the SDK — separate decision. |
| Claude session persistence | CROSS-CHECK | Same describePersistence/resumeSession shape BooCode already has for opencode; claude slots in. Neither Paseo nor BooCode verifies the transcript exists on disk before resume (shared open question — see §5). |
Recon's net: LIFT = warm-ACP supervision (Phase 2) + claude --resume continuity (standalone batch). RE-DERIVE = OpenCode crash recovery (Phase 3). Everything else cross-check or n/a. The two n/a claude items both reduce to one deferred decision: adopt @anthropic-ai/claude-agent-sdk or stay PTY.
2b. Additional findings (this session's Paseo agent sweep)
These came from the broader agent pass, not the targeted Phase 2/3 recon. Where they touch the same code as §2a, the §2a recon is authoritative.
| Finding | Verdict | Notes |
|---|---|---|
Post-interrupt stale-terminal suppression (paseo 1d38aac) |
LIFT · pattern (bugfix) | See §3 #3 — verified to be a live bug in BooCode. Highest-confidence paseo item. |
Provider-agnostic AgentUsage normalized usage/cost frame |
LIFT · pattern | Converges with opencode's session.next.step.ended (§3 #4). Paseo's {inputTokens, cachedInputTokens, outputTokens, totalCostUsd, contextWindowMax/Used} is the target shape for normalizing across providers; do the opencode slice first. |
Worktree-archive → cascade-archive agents + schedule cleanup (paseo b6103a5) |
WATCH → adopt in Phase 3 | Soft-delete (keep archivedAt), single archive event fans out to children + downstream rows, Promise.allSettled so one failed delete doesn't abandon the rest. Right shape for the v2.6 Phase 3 worktree reaper. |
Server retire/refcount + LRU-bound caches (paseo server-manager.ts, leak-fix f20393d) |
WATCH (low confidence) | The agent read a retire-set/refcount mechanism; the §2a server-manager recon concluded "nothing to lift." Treat the lifecycle as cross-check (§2a wins). The one durable takeaway: bound the per-session/per-worktree Maps in the warm opencode server (long-lived daemon → unbounded caches leak). Confirm against §2a before acting. |
Subagent permission forwarding (paseo 44863ec) |
WATCH (gated) | opencode task tool spawns child sessions; forward permission.asked from tracked children by parentID demux. Blocked: BooCode's opencode-SSE path has zero permission handling today (runs auto-approve). Reachable only after BooCoder builds opencode-SSE permission cards at all. Ties to v2.4. |
3. OpenCode (MIT — code-liftable)
| # | Finding | Evidence | Verdict | Notes |
|---|---|---|---|---|
| 1 | Consume the fuller session.next.* event set in opencode-server.ts |
packages/core/src/session/event.ts:105-365; BooCode handles only ~5 arms (opencode-server.ts:215-311) |
LIFT · code | Events already in the installed @opencode-ai/sdk — no dep bump. High-value arms: step.ended ({tokens{input,output,reasoning,cache},cost} → #4 below); compaction.{started,delta,ended} (warm server auto-compacts mid-conversation; today shows as a silent context gap); tool.progress, tool.input.{started,delta}, retried, step.failed. |
| 4 | ctx/token usage for opencode (the high-value slice of #1) | event.ts:117-135 |
LIFT · code | Closes the roadmap-named gap: "opencode/goose/qwen/claude dispatch with no ctx/token usage; only native boocode tracks ctx." Mirror BooChat's existing 'usage' WS frame on the coder side; accumulate per (chat, agent). Converges with paseo AgentUsage (§2b). |
| 2 | Stalled-stream chunk-timeout (wrapSSE + header timeout) |
provider/provider.ts:40-96 (f965db9, c7e1fc5) |
WATCH · pattern | BooChat's stream-phase.ts has no server-side stall timeout — a hung llama-swap stream relies entirely on the frontend 60s discard_stale watchdog. ~40-60 LoC to wrap the fullStream loop with a per-chunk timeout firing the existing abort path. Low incidence on a single local instance; do it if stuck rows recur. |
| 3 | Retry-with-backoff + retryability classifier (session/retry.ts) |
session/retry.ts, message-v2.ts:1155 (14e0b9b) |
WATCH · pattern | BooChat has zero retry logic. delay() parses retry-after[-ms] headers w/ exp-backoff fallback; retryable() classifies transient-5xx / rate-limit / context-overflow-exclusion. Strip the Go-billing arms. Pairs naturally with #2. llama-swap rarely emits retry-after, so value is mostly transient-5xx/stall retry. |
| — | MCP auth file-lock (mcp/auth.ts, fa73ec4) |
— | N-A (deferred) | Serializes concurrent OAuth token refreshes. Can't trigger — BooCode's config schema rejects OAuth MCP servers until secret storage lands (roadmap). Note for when OAuth MCP is un-deferred. |
Confirmed current (cross-check, no refresh needed): compaction algorithm (incl. tail_start_id/splitTurn post-fix — verified identical), two-tier prune, truncate, run-loop (BooCode drives off live result.toolCalls, not a history scan — not vulnerable to opencode's interrupted-tool re-prompt bug), doom-loop guard, MCP client, permission ruleset. Ruled out: v2 Effect/event-sourced core, packages/llm/ native runtime (diverges from the AI SDK v6 BooCode just adopted), adaptive-reasoning (cloud-Anthropic only), acp-next (BooCoder is the ACP client).
4. llama.cpp (consumed via llama-swap / llama-sidecar — adopt features, not C++)
4a. ⭐ Retire the AGPL tool-call parser — LIFT · drop-our-code
llama-server moved to a template-learning PEG auto-parser + lazy grammar that parses qwen3.5/3.6's tool markup server-side into OpenAI tool_calls.
- Evidence:
common/chat-auto-parser-generator.cpp,common/chat-diff-analyzer.cpp(1570 lines),common/chat-peg-parser.cpp; shippedmodels/templates/Qwen3.5-4B.jinja(uses BooCode's exact Pattern-2<tool_call><function=…><parameter=…>+<think>); server emits structuredtool_callsin both non-streaming and streaming (tools/server/server-chat.cpp:421-577), reasoning split intoreasoning_content/reasoning_content_delta.tool_choice=required+ grammar-constrained calls exist (common/chat.cpp:290-300). - Gate (the one open question): only fires if llama-server runs with
--jinja+ a qwen3.x template. BooCode already treats--jinja/--chat-template*as managed flags (llama-args-validator.ts:92-102) and sendstools/toolChoice:'auto'through the AI SDK (stream-phase.ts:202,438) — the path is wired; the unknown is whether the live llama-swap/sidecar model config passes--jinja(§5). - What's missing: no qwen3.x-named native handler — qwen3.6 rides the generic template-driven path. The template teaches Patterns 1 (
<tool_call>{json}) and 2 (<function=…>) but not Pattern 3 (<invoke name=…>), the Anthropic-shape residue qwen drifts into. - Staged plan (do not delete blind — CLAUDE.md notes qwen3.6 was unreliable):
- Confirm
--jinja+ Qwen3.5 template are live (add the flags if not). - Validate native
tool_callsagainst real qwen3.6 streaming for one release, behind a feature flag. - Trim
tool-call-parser.tsto a clean-room<invoke>-only fallback (~250 of 427 lines deletable; rewrite the remainder without Unsloth/AGPL provenance). Net: AGPL-3.0 liability eliminated even if a thin fallback stays.
- Confirm
4b. Config-level adopts — LIFT · config-adopt (pass straight through llama-swap as OpenAI-compat body fields; no binary upgrade)
- New sampling params (
server-task.cpp:279-290):top_n_sigma,xtc_probability/threshold,typical_p, thedry_*repetition family (dry_multiplier/base/allowed_length/penalty_last_n/sequence_breakers),frequency_penalty,repeat_penalty.top_n_sigma+dry_*are the high-value pair for an agentic model prone to loops — ties to the doom-loop sentinel. Surface in AGENTS.md frontmatter + the validator allowlist. --reasoning-budget N(LLAMA_ARG_THINK_BUDGET) +--reasoning on|off|auto, defaultreasoning_format=auto: server-side cap on qwen3.6 thinking (cheaper turns) without prompt hacks, andreasoning_contentarrives as a separate field — BooCode could consume it directly instead of scraping<think>.
4c. Behavioral changes — TRACK (no code action; awareness)
- SSE headers sent at slot-start (
0821c5fcf): in stream mode, HTTP 200 + headers flush when prompt processing begins, before the first token. BooCode keys its stale-stream timer on token activity, not header arrival → safe, but time-to-headers semantics shift. Alsotask_params.streamdefault flippedtrue → false— harmless for BooCode (always setsstream), but any llama-swap/sidecar code omittingstreamnow defaults to non-streaming. /propsrouter-mode dummyn_ctx:0(server-models.cpp:1170-1173): llama.cpp gained a native multi-model router; its bare/props(no?model=) returnsn_ctx:0. BooCode reads/upstream/<model>/propswhich resolves to a specific model → still correct today. Silent failure mode only if a bare router/propsis ever hit:ctx_max=0→ rejected → negative-cache masks the misconfig → compaction budget degrades. (Aside: the native router could eventually replace llama-swap — separate evaluation.)LLAMA_ARG_env-prefix unification (6b4e4bd58): confirm the sidecar'sLLAMA_*env vars use theLLAMA_ARG_prefix.
4d. SKIP
- Native Anthropic Messages API in llama-server (
test_compat_anthropic.py) — BooCode is OpenAI-compat via the AI SDK; switching wire formats buys nothing. (Minor TRACK: could in principle back a local "claude-compatible" provider — net-new feature, not a lift.) - Qwen 3.5/3.6 TP granularity fix (
8b0e0db60) — only relevant if running qwen3.6 across 3 GPUs with tensor-parallel; then it's a binary-upgrade correctness fix, not an API change. - HTTP ETags /
--api-key-file/ timeout bump — irrelevant behind Authelia + llama-swap.
5. Second fork sweep (2026-05-31) — 11 repos
Read-only agent review of everything else in /opt/forks/ except the three already covered (paseo/opencode/llama.cpp), BooCode's own llama-sidecar, and codecontext/codesight (skipped on request). Repos: conductor, superset, openchamber, happy, cline, qwen-code, amp-acp, pi-acp, claude-code, goose, unsloth. Shallow clones (history-limited but source intact): cline, qwen-code, amp-acp, pi-acp, claude-code, goose, unsloth. Full: conductor, superset, openchamber, happy.
5a. openchamber (openchamber/openchamber, MIT — code-liftable) ⭐
Multi-runtime (web/PWA/Electron/VS Code) GUI for opencode-as-warm-server — the closest architectural sibling to BooCoder's backend. Stronger than BooCode in exactly one dimension: opencode process-lifecycle hardening (BooCode's v2.6 Phase 3 frontier). Divergence shaping every lift: openchamber runs one global opencode server + one /global/event stream; BooCode runs per-(chat,agent) sessions with per-session event.subscribe({directory}) — so these are pattern/code-adaptation lifts, not drop-ins.
| # | Finding | Evidence (HEAD a394a877) |
Verdict | Maps to |
|---|---|---|---|---|
| 5c | Lifecycle hardening: health monitor + crash auto-restart + busy-aware restart | packages/web/server/lib/opencode/lifecycle.js — runHealthCheckCycle (L896), HEALTH_CHECK_MAX_CONSECUTIVE_FAILURES=20, shouldSkipRestartForBusySessions+STALE_BUSY_GRACE_MS (L872/838), startHealthMonitoring 15s (L938), triggerHealthCheck (L930). BooCode's opencode-server.ts:143 literally comments "recovery is Phase 3" |
LIFT · pattern | v2.6 Phase 3 (#5) |
Port reclaim before respawn (killProcessOnPort lsof+kill, waitForPortRelease net.connect poll) |
lifecycle.js:44,101, used in restartOpenCode L595 |
LIFT · code (S) | Phase 3 | |
Stall-detecting SSE reader + Last-Event-ID replay (2048-event ring, 20s stall-abort) |
lib/event-stream/upstream-reader.js:110-131, global-hub.js:88-149 |
LIFT · pattern (the stall-timer half is S, high-value) | hardens runSessionEventLoop |
|
OPENCODE_SERVER_PASSWORD scheme confirmed = Authorization: Basic base64("opencode:"+pw), rotate-on-restart |
packages/vscode/src/opencode.ts:55-65,786; lifecycle.js:458 |
CROSS-CHECK → LIFT · config | closes a known unknown (BooCode runs the warm server unsecured on loopback) | |
Worktree layout/reaper mirrors opencode's <data>/worktree/<projectID>/; removeWorktree saga |
packages/vscode/src/gitService.ts:1062,1874 |
CROSS-CHECK | Phase 3 reaper; check BooCode's worktree paths align with opencode's expected layout |
Ruled out: warm-ACP/goose/qwen/claude (openchamber is opencode-only), SSE part-translation/reasoning-dedup (BooCode's is more complete), Arena-equivalent, permission cards — all already-better-in-BooCode or N-A.
5b. cline (cline/cline, Apache-2.0 — code-liftable) ⭐
Re-architected into a layered SDK. Two strong code lifts that hit exactly where BooCoder's write/edit surface is weakest for local quantized models.
| # | Finding | Evidence (HEAD 31a118f) |
Verdict | Maps to |
|---|---|---|---|---|
| 5d | git stash create + private-ref checkpoint — per-turn snapshot of full dirty worktree, GC-safe, invisible to git stash list, restorable with conversation-trim in sync |
sdk/packages/core/src/hooks/checkpoint-hooks.ts:177-253; session/checkpoint-restore.ts:161-189 |
LIFT · code+pattern (#4) | checkpoint/restore — captures external-agent edits BooCode's rewind can't |
| 5e | Fuzzy patch applier — exact→trimEnd→trim→Levenshtein≥0.66 ladder + unicode canon (dashes/curly-quotes/nbsp) + multi-occurrence guard; unmatched→warning not throw |
extensions/tools/executors/apply-patch-parser.ts:347-431,58-83; editor.ts:133-143 |
LIFT · code (#3) | BooCoder edit_file is exact .includes-or-throw (pending_changes.ts:111) |
File-provenance carry-forward — ## Files {Read,Modified} ledger merged across compactions, deterministic |
extensions/context/compaction-shared.ts:351-410 |
LIFT · pattern (#12) | context-mgmt | |
MistakeTracker — counts heterogeneous consecutive failures (api/invalid-tool/exec), injects recovery guidance + resets vs hard-stop |
runtime/safety/mistake-tracker.ts:82-142 |
LIFT · pattern (#12) | complements doom-loop (which only catches identical repeats) | |
Tool-pair-atomic compaction eviction (BFS over tool_use_id, turn-boundary cut) |
extensions/context/basic-compaction.ts:181-205 |
CROSS-CHECK | verify selectPruneTargets never orphans a tool_result |
Ruled out: prompt-caching (Anthropic cache_control markers — N-A, llama.cpp auto-prefix-caches), stream retry (delegated to AI SDK — same as BooCode), MCP marketplace, hub/daemon (multi-client — BooCode is single-process).
5f. qwen-code (QwenLM/qwen-code v0.17.0, Apache-2.0 — code-liftable) ⭐
The "qwen = one-shot PTY because ACP was HTTP-only" premise is obsolete. qwen now ships a full stdio-ACP agent, a qwen serve HTTP+SSE daemon, and a Claude-Code-compatible stream-json protocol.
| # | Finding | Evidence | Verdict | Maps to |
|---|---|---|---|---|
Warm qwen --acp is real — multi-session Map<sessionId,Session>, loadSession/unstable_resumeSession, setSessionMode/unstable_setSessionModel, stdio NDJSON via @agentclientprotocol/sdk |
packages/cli/src/acp-integration/acpAgent.ts:308,322-351,384-568 |
CROSS-CHECK → LIFT · pattern (#2) | v2.6 Phase 2 — validates the openspec plan; wire goose/qwen to acp-dispatch.ts |
|
| 5g | stream-json = Claude-compatible NDJSON (system/assistant/result/stream_event with content_block_delta text/thinking/tool deltas, usage, session_id) — BooCode parses none of it (dispatcher.ts:406 slices stdout opaque) |
nonInteractive/types.ts:88-262, StreamJsonOutputAdapter.ts |
LIFT · pattern (#7) | one parser serves qwen and claude PTY fallbacks |
Resume primitives --resume <uuid|title> / --continue / --session-id <uuid> / --fork-session |
config/config.ts:825-985,1668-1721 |
LIFT · config | mint a stable per-(chat,agent) UUID; parity with claude --resume |
|
qwen serve daemon + @qwen-code/sdk (HTTP+SSE, Last-Event-ID replay ring, better than opencode's SSE) |
commands/serve.ts:51-266; packages/sdk-typescript/src/daemon/* |
TRACK | stdio-ACP is cheaper now; mine its SSE-reconnect design when hardening opencode SSE (converges w/ openchamber 5c) |
Note: BooCode ships @agentclientprotocol/sdk@^0.22.1 (newer than qwen's ^0.14.1) — same package family, BooCode ahead; cross-check the v0.14↔v0.22 initialize/capability handshake before relying on unstable_resumeSession (the unstable_ prefix signals churn). Ruled out: the rewind commit (c699738) is a qwen-TUI history-count fix, not a wire event — N-A.
5h. happy (slopus/happy, MIT — code-liftable) ⭐
Mobile/remote client that drives Claude Code via the @anthropic-ai/claude-agent-sdk (NOT PTY). A working existence-proof for BooCode's claude SDK-vs-PTY decision.
| # | Finding | Evidence (HEAD 21c6ced) |
Verdict | Maps to |
|---|---|---|---|---|
Claude Agent SDK in streaming-input mode — one persistent query() fed a PushableAsyncIterable<SDKUserMessage>; structured system/init (tools/skills/mcp), assistant, result, tool parts — no stdout scraping |
claude/sdk/query.ts, claude/claudeRemote.ts:152-259; dep @anthropic-ai/claude-agent-sdk@^0.2.96 |
LIFT · pattern + resolves the decision → lean SDK (#9) | claude-provider direction | |
| 5i | --resume continuity via SessionStart-hook + JSONL watcher → captures Claude's UUID as claudeSessionId, fed back as SDK resume:; reconnect-safe (treatExistingAsProcessed) |
claude/utils/generateHookSettings.ts, sessionScanner.ts, claude/session.ts:113-127 |
LIFT · code (#9) | cleanly separates Claude's UUID from BooCode's (chat_id,agent) key; transport-independent — pays off even on PTY |
canUseTool permission callback — single chokepoint, live setPermissionMode, bash-prefix allow-cache |
claude/claudeRemote.ts:134,169, permissionHandler.ts |
CROSS-CHECK | cleaner integration point than parsing PTY permission prompts | |
| Local↔remote single-session handoff (TTY ⇄ SDK share one Claude UUID); E2E socket.io relay | claude/loop.ts:77-115; api/encryption.ts |
TRACK / N-A | relay N-A (Authelia owns auth); handoff only if BooTerm⇄CoderPane session-continue is ever wanted |
5j. superset (superset-sh/superset, Elastic License 2.0 — source-available, PATTERN-ONLY)
Electron macOS "code editor for AI agents"; runs every agent as a raw PTY process and learns state purely from hooks the agents POST back (no editor↔agent protocol, tracks zero tokens/cost). All items clean-room only.
| # | Finding | Evidence (HEAD 7f3e5b3) |
Verdict | Maps to |
|---|---|---|---|---|
| 5j | Universal-agent lifecycle hooks → normalized status — inject a notify hook into each agent's native config (~/.claude/settings.json, ~/.codex/hooks.json, opencode plugin), POST {terminalId,eventType,agent}; server collapses ~30 vendor event names → 5 states |
apps/desktop/.../agent-setup/*, templates/notify-hook.template.sh, host-service/.../map-event-type.ts |
RE-DERIVE (#10) | gives BooCode's PTY agents (goose/qwen/claude) real working/blocked/done state it lacks today |
Worktree destroy saga — preflight inspect (dirty/unpushed) + ordered failure semantics + in-flight guard |
host-service/.../workspace-cleanup.ts |
RE-DERIVE | Phase 3 worktree reaper | |
| Out-of-process PTY daemon w/ crash supervision + adoption (circuit-breaker, adopted-PID liveness poll) | host-service/.../DaemonSupervisor.ts |
RE-DERIVE / TRACK | Phase 3 (BooTerm tmux already does some) | |
| Diff-line → agent-comment re-prompt loop (select lines → send to existing session or new agent) | apps/desktop/.../DiffPane/AgentCommentComposer/* |
RE-DERIVE | review/diff UX frontier |
Ruled out: token/cost (superset tracks none — BooCode ahead), permission cards (BooCode's intercept-and-render is richer; superset just chimes + bypass-flags the agent), editor↔agent protocol (there is none), all SaaS/cloud/billing plumbing.
5k. unsloth (unslothai/unsloth) — decision-settling: CONFIRMED AGPL-3.0-only
The lifted parser + HTML→MD converter ARE AGPL-3.0; the v2 clean-room recommendation stands. Unsloth is dual-licensed: core unsloth/ lib = Apache-2.0 (LICENSE, pyproject.toml), but the studio/ subtree = AGPL-3.0-only — dedicated studio/LICENSE.AGPL-3.0, studio/package.json "license":"AGPL-3.0-only", README §line 262 carves Studio out explicitly, and both lifted files carry per-file SPDX headers (studio/backend/core/inference/{tool_call_parser.py,_html_to_md.py} → # SPDX-License-Identifier: AGPL-3.0-only). BooCode's ports already carry the AGPL SPDX header (obligation on-record). Network-served ⇒ AGPL §13 network-copyleft is the live liability. HTML→MD can be replaced outright by a permissive lib (turndown / node-html-markdown); the tool-call parser needs a clean-room rewrite from spec (the <tool_call>/<function=> grammar is short and re-derivable).
5l. conductor (conductor-oss/conductor, Apache-2.0, Java) — LOW / near-NONE
Confirmed Netflix/Orkes Conductor — enterprise distributed workflow engine (5600 commits, Spring/Flyway/Cassandra), not the Mac Claude-Code app. Wrong scale + wrong substrate (polling workers + Redis queues vs BooCode's single-user Postgres LISTEN/NOTIFY), and BooCode already sourced its task-DAG/dispatcher/pipelines/human_inbox from agent-hub + Roo Boomerang. One worth-a-glance reference: the retry/backoff/timeout taxonomy (TaskDef.java RetryLogic{FIXED,LINEAR,EXP} + TimeoutPolicy, delay formula in DeciderService.java:634-680, with jitter + total-time-budget guard) — BooCode has no retries today; copy the field set + three formulas when retries land. Everything else (decider-replay engine, 24 task mappers, fork-join, sub-workflow, human-task) = N-A, already-covered or wrong-scale.
5m. ACP provider candidates — amp-acp SKIP, pi-acp WATCH
Both are config-only adds to BooCode's v2.3 catalog ({extends:'acp', label, command, env}) and both use @agentclientprotocol/sdk@~0.22/0.12, proto v1 — wire-compatible with BooCode's own @agentclientprotocol/sdk@0.22.1 (see correction in §6).
- amp-acp (
tao12345666333/amp-acp, Apache-2.0): adapter for Sourcegraph Amp.npx -y amp-acp+AMP_API_KEY. SKIP — Amp is a paid cloud product with no self-host / no BYO-key / no local-model path; can't point at llama-swap. Keep only as the canonical "does add-from-catalog work" smoke entry (lowest-risk Apache-2.0 ACP adapter). - pi-acp (
svkozak/pi-acp, MIT): bridge for pi (spawnspi --mode rpc).npx -y pi-acp, pi free + self-hostable, dynamic model discovery. WATCH — but found no evidence pi supports an OpenAI-compatible/llama-swap base URL (cloud BYO-keys only today) + v0.0.27 maturity ("MVP", MCP not wired). Re-evaluate if pi adds a local provider — then it's a strong config-only ADD.
5n. claude-code & goose — low/cosmetic
- claude-code (
anthropics/claude-code, depth-1): the public issue-tracker/docs repo, not source. Thin. No stream-json schema doc (keep relying on observed output). Notables:CLAUDE_CODE_SESSION_IDenv injected into Bash-tool subprocesses (hook↔session correlation);examples/settings/*.jsonpermission/sandbox shapes;SKILL.mdfrontmatter is simpler (name/description/version) than BooCode'seval.yaml. The one example hook (bash_command_validator) is the same family BooCode already vendored. Nothing net-new liftable. - goose (
block/goose, depth-1, Apache-2.0 Rust → pattern-only): the AAIF/Linux-Foundation move is cosmetic — binarygoose,goose acpinvocation, and~/.config/goose/config path all UNCHANGED; only org/URLs changed (block/goose→aaif-goose/goose). Watch: grep BooCode install docs forblock/gooseURLs (will eventually 404). For v2.6 Phase 2: goose ACP supports multi-session + mid-session model/mode switch + session persistence, but noloadSession/resume method surfaced → cross-restart resume looks thinner than opencode's; don't assume opencode-styleagent_sessionsresume works identically for goose.
6. Open decisions / things to think about
- The jinja gate (blocks #1, the top item). Is
--jinja+ a qwen3.x template live in the llama-swap/sidecar model config? Read-only to answer, but the config may live with the sidecar on sam-desktop (100.101.41.16) — needs Sam's OK before any SSH. This single check decides whether the AGPL-parser retirement is actionable now or needs a config change first. - Claude transport: SDK vs PTY — now evidenced, leaning SDK.
happy(§5h) is a working existence-proof that@anthropic-ai/claude-agent-sdkin streaming-input mode drives Claude Code with structured events (tool calls, reasoning,system/inittool/skill/mcp lists, usage) and clean continuity — richer than PTY stdout-scraping. Decision narrowed to: adopt the SDK (net-new integration, ~100-line streaming-input pump) vs. stay PTY + just add--resume. Independent of warm-ACP Phase 2. Note the continuity mechanism (§5i hook + jsonl-watcher →claudeSessionId) is transport-independent, so ship it either way. stream-jsonparser is shared infrastructure, not a per-agent chore. qwen-code (§5g) and claude-code emit the same Claude-Code-compatible NDJSON. One parser keyed ontype/stream_event.event.typeunlocks tool/reasoning/usage surfacing for both qwen and claude PTY fallbacks (today both are sliced opaque). Decide whether to build it as a shared module now (cheap) rather than twice later.- Transcript/session verification before resume (shared gap). Neither Paseo nor BooCode (nor openchamber, nor goose's ACP) verifies the session/transcript exists on disk before resuming — true for opencode, claude, qwen. Folds into v2.6 Phase 3 (crash recovery + active supervision, now lifting from openchamber §5c). Decide whether "resume blindly, recover on failure" is good enough for single-user, or worth a pre-resume existence check. Caveat: goose ACP exposes no
loadSession/resume (§5n) → its cross-restart resume needs a different design than opencode's. - Usage and status normalization scope. Two converging gaps: (a) tokens/cost — the opencode token slice (#8) converges with paseo
AgentUsage; (b) liveness/status — superset's notify-hook pattern (§5j, #10) is the only way to know whether a one-shot PTY agent (goose/qwen/claude) is working / blocked-on-permission / done. Decide whether to design one normalized per-(chat,agent)"agent telemetry" shape (tokens + status) up front so all providers slot in, or ship opencode-token-only and generalize at Phase 2. - Correction — ACP SDK package. This doc and the roadmap state BooCode uses
@zed-industries/agent-client-protocol; the liveapps/coder/package.jsonactually declares@agentclientprotocol/sdk@^0.22.1(verified installed). Both amp-acp and pi-acp use the same package, so the "version-drift" worry is moot. Worth correcting inboocode_roadmap.md's lift table on the next pass.
7. Housekeeping
- Stale
.bakin the working tree:apps/server/src/services/inference/tool-phase.ts.bak-20260531(today, 15.5 KB). Violates CLAUDE.md's "don't accumulate.bak-*". Dated today andtool-phase.tsis on the active path — may be an in-progress safety copy. Confirm before removing. - Unshallow
/opt/forks/llama.cpp(git fetch --unshallow) before the next review so commit-level attribution is possible. (opencode was unshallowed mid-review; cline/qwen-code/amp-acp/pi-acp/claude-code/goose/unsloth remain shallow but their source was intact.) - Grep BooCode install docs/scripts for
block/gooseURLs — goose moved toaaif-goose/goose(§5n); old release URLs will eventually 404. - Correct the ACP-SDK package name in
boocode_roadmap.md's lift table →@agentclientprotocol/sdk@0.22.1(§6.6).
8. Roadmap mapping (where each actionable lands)
| Roadmap slot | Items from this review |
|---|---|
| v2.6 Phase 2 (warm ACP goose/qwen) | #2 warm-ACP backend — validated by qwen's own qwen --acp (§5f); #7 parse qwen/claude stream-json in the one-shot fallback |
| v2.6 Phase 3 (lifecycle hardening) | #5 openchamber lifecycle hardening (health monitor + crash restart + port reclaim + stall-SSE — §5c, supersedes the paseo re-derive); worktree-archive cascade (paseo) + superset destroy-saga (§5j); LRU-bound caches; pre-resume session verification |
| v2.6 Phase 1 UX | #6 interrupt-bug fix; #8 opencode token/ctx usage; richer SSE arms (compaction surfacing) |
| Write/edit robustness (NEW batch) | #3 fuzzy patch applier + #4 git-stash checkpoint (cline §5b) — both directly harden BooCoder's edit/rewind surface for local models |
| Cross-agent telemetry (NEW) | #10 superset notify-hook → normalized status for PTY agents (§5j); pairs with #8 token usage |
| Standalone claude-provider batch | #9 --resume via hook/jsonl-watcher (§5i) + the SDK-vs-PTY decision (now lean-SDK, §6.2); #12 MistakeTracker + file-provenance ledger (cline) |
| Inference / license-debt batch | #1 AGPL parser retirement (AGPL confirmed §5k; gated on the jinja check §6.1); #11 sampling/reasoning-budget config adopts |
| BooChat resilience (opportunistic) | stall-timeout + retry/backoff (opencode); tool-pair-atomic prune cross-check (cline §5b) |
| Provider catalog | amp-acp = keep as add-from-catalog smoke test only (§5m); pi-acp = WATCH for a local-provider mode |
| Deferred / gated | subagent permission demux (needs opencode-SSE permission cards first); MCP auth lock (needs OAuth MCP un-deferred); qwen serve HTTP backend (stdio-ACP cheaper) |
| Not actionable | conductor (wrong scale — only the retry-taxonomy reference §5l); claude-code public repo (docs only §5n) |