chore+docs: config, agent registry, codecontext, v2.6 spec, changelog

Working-tree config/doc changes (.gitignore, CLAUDE.md, AGENTS.md removal + data/AGENTS.md, codecontext Dockerfile/shim — pre-existing) plus this session's v2-6 persistent-agent-sessions openspec proposal/design/tasks (planning only; feature unimplemented, reserves the v2.6.0 tag) and the v2.5.2 CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 03:12:31 +00:00
parent 547fd70650
commit a8c84ecfe4
10 changed files with 578 additions and 115 deletions

View File

@@ -0,0 +1,114 @@
# v2.6 Persistent agent sessions (warm processes + OpenCode server)
**Status:** Planned
**Depends on:** v2.2 Paseo providers (ACP dispatch), v2.3 provider lifecycle (registry/snapshot)
**Reference fork:** `/opt/forks/paseo`, `/opt/forks/opencode`
**Pairs with:** the v2.5.x MessageBubble "Thinking" render fix — reasoning already flows; this batch is about persistence, not capability.
## Why
BooCode dispatches external agents (opencode, goose, qwen) **one-shot per task**:
per task the dispatcher cuts a fresh worktree (`createWorktree(projectPath, taskId)`),
spawns `opencode acp` / `goose acp` / `qwen --acp`, runs **one** turn, then tears
down the process *and* the worktree (`dispatcher.ts:runExternalAgent`). Consequences:
- **No session continuity.** A follow-up message in the same chat creates a new
task with a new worktree and a new agent process. The agent has no memory of
the prior turn beyond what BooCode replays as chat history, and it cannot see
the files it edited last turn (fresh worktree every time).
- **Cold start every turn.** Each turn pays the process spawn + ACP `initialize`
handshake (and, for some agents, model load) before any work happens.
- **Diverges from Paseo.** Paseo runs **OpenCode as a long-lived HTTP server**
(`opencode serve` + `@opencode-ai/sdk`, SSE `/event` stream) and keeps **goose /
qwen as warm stdio-ACP processes** (`SpawnedACPProcess`: one ACP connection,
`newSession()` once, many `prompt()`s). BooCode rebuilds the world per turn.
This batch makes 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. Reasoning passthrough is **already solved** (ACP
`agent_thought_chunk``reasoning_delta` → the new MessageBubble Thinking block);
this batch does not touch it beyond porting OpenCode's reasoning-dedup.
## Decisions locked (from design review)
- **Worktree model:** *Persistent worktree per session.* A chat owns one worktree
for the whole conversation; each turn the agent sees prior edits; pending_changes
accumulate; worktree is cleaned on session close, not per turn.
- **Agent switching:** *Free switch, per-agent memory.* The picker stays per-turn
(not locked to a chat). The worktree is shared across agents; each agent keeps its
own backend session, resumed when you switch back to it. Native boocode reconstructs
from chat history (so it sees every agent's turns); a resumed agent does not auto-
ingest the gap turns. Data model: one shared worktree per chat + one backend session
per `(chat, agent)` pair. Caveat: unapplied edits don't cross the worktree↔project
boundary between external agents and native boocode (a v2.5 review-model consequence).
- **Transport per agent (matches Paseo exactly):**
- **OpenCode** → one shared `opencode serve` HTTP server, driven via
`@opencode-ai/sdk`; one opencode *session* per BooCode chat (multi-session,
directory-routed via `x-opencode-directory`).
- **Goose / Qwen** → warm **stdio** ACP process per live session. Their HTTP
"server" modes are just ACP-over-HTTP wrappers (goose: undocumented/internal;
qwen `serve`: an HTTP bridge around a single `qwen --acp` child) — no gain over
stdio, so we keep stdio ACP like Paseo does.
## Scope
### In scope
1. **Agent process pool** (`apps/coder/src/services/agent-pool.ts`) — owns long-lived
backends, lazy spawn, idle eviction, crash restart, shutdown drain.
2. **OpenCode server backend** — spawn `opencode serve`, hold SDK client + single
SSE subscription demuxed by opencode `sessionID` → BooCode session; port +
`OPENCODE_SERVER_PASSWORD` managed at boot.
3. **Warm ACP backend** — persistent `SpawnedACPProcess`-style connection for
goose/qwen reused across turns (one `newSession()`, many prompts).
4. **Persistent worktree lifecycle** — worktree created on first turn of a session,
reused, diffed incrementally into `pending_changes`, cleaned on session close.
5. **Session ↔ backend ↔ worktree mapping** — new `agent_sessions` table.
6. **Per-session concurrency** — replace the dispatcher's global single-flight
`running` guard with per-session serialization (different sessions run
concurrently; one turn at a time within a session).
7. **OpenCode reasoning dedup** — port Paseo's `streamedPartKeys` partID dedup so
reasoning isn't double-emitted (delta + final part).
8. **Switch-aware UI** (design §9) — per-change agent attribution in the DiffPanel
(`pending_changes.agent` column + badges), a resumed/new-session chip on the
AgentComposerBar (chat-scoped `agent-sessions` endpoint), and a staging-boundary
hint so the worktree↔project gap is legible.
9. **Tests + smoke** — pool lifecycle unit tests; multi-turn opencode smoke; switch
round-trip smoke; attribution/indicator smoke.
### Out of scope (this batch)
- Claude PTY→structured transport (separate deferred work — claude stays PTY here).
- Goose/qwen HTTP server modes (intentionally not used).
- Frontend redesign — existing CoderPane multi-turn chat UI already supports
follow-ups; only backend continuity changes.
- Replacing `acp-dispatch.ts` wholesale — warm backend reuses its event handlers.
- Cross-host agent servers (opencode server stays local to the BooCoder host).
## Non-goals
- Multi-user session sharing (single-user homelab).
- Multiple concurrent turns within one agent session (the agent holds conversational
state; turns within a session are serialized).
## Success criteria
- Send two messages in one external-agent chat → second turn reuses the same agent
session **and** the same worktree (verified: no second `createWorktree`, agent
references files it edited in turn 1).
- Warm-start latency for turn 2 materially below turn 1 (no spawn/handshake).
- opencode reasoning shows once per thought (no dupes) in the Thinking block.
- Killing the opencode server mid-session → pool restarts it and the next turn
recovers (opencode persists sessions on disk).
- Switch opencode → boocode → opencode in one chat → opencode resumes its *same*
session (its memory intact), boocode saw opencode's turns as history, and all three
shared the one worktree. No agent is locked to the chat.
- Closing/archiving a session removes its worktree; BooCoder restart drains cleanly.
- Existing one-shot paths (arena, `new_task` tool, MCP create-task) still work.
## Deliverables
| Doc | Purpose |
|-----|---------|
| [`design.md`](./design.md) | Architecture, backends, data model, worktree/diff strategy, lifecycle, risks |
| [`tasks.md`](./tasks.md) | Phased implementation checklist |