Files
boocode/openspec/changes/v2-6-persistent-agent-sessions/tasks.md
indifferentketchup a8c84ecfe4 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>
2026-05-29 03:12:31 +00:00

5.9 KiB

v2.6 Tasks — Persistent agent sessions

Phased so each phase is independently shippable and smoke-testable. Phase 1 (OpenCode server) delivers the most value on the cleanest API; goose/qwen warm ACP follows; hardening last.

Phase 0 — Foundations (no behavior change)

  • 0.1 Add session_worktrees + agent_sessions tables (per (session_id, agent)) to apps/coder/src/schema.sql (idempotent; see design §3).
  • 0.2 Define AgentBackend / AgentSessionHandle interface + normalized onEvent event union (reuse shapes from acp-dispatch.ts).
  • 0.3 Scaffold agent-pool.ts with lazy get-or-create keyed by (chat, agent), health, dispose(); wire app.addHook('onClose') to dispose alongside dispatcher stop().

Phase 1 — OpenCode server backend (multi-turn, warm)

  • 1.1 Add @opencode-ai/sdk to apps/coder/package.json; pin to installed opencode major.
  • 1.2 backends/opencode-server.ts: spawn opencode serve once (random OPENCODE_SERVER_PASSWORD, allocated port), createOpencodeClient, wait for ready line.
  • 1.3 Single /event SSE read loop; demux by properties.sessionID; map message.part.delta/updated (text + reasoning) + tool parts to onEvent.
  • 1.4 Port Paseo streamedPartKeys reasoning dedup (delta vs final part).
  • 1.5 ensureSession: reuse the (chat, opencode) agent_sessions row if present (resume on switch-back), else client.session.create() → store agent_session_id.
  • 1.6 prompt: send via SDK with x-opencode-directory = session worktree + model.
  • 1.7 Dispatcher: when agent==='opencode', route to pool backend instead of dispatchViaAcp; keep broker frames + persistExternalAgentTurn identical.
  • 1.8 Persistent worktree: chat-keyed createWorktree (shared across agents); capture base commit in session_worktrees; reuse across turns and agents.
  • 1.9 Per-session concurrency: replace global running with Map<sessionId,Promise>; poll() skips sessions with an in-flight turn.
  • 1.10 Per-turn diff → supersede prior pending_changes row for the session (latest-wins).
  • Smoke 1: two messages in one opencode chat → same agent_session_id, same worktree, no second createWorktree; agent references turn-1 edits; reasoning shows once; turn-2 faster.

Phase 1 (UX) — Attribution & switch affordances (design §9)

  • U.1 Stamp pending_changes.agent at queue time (worktree path → task agent; native write tools → 'boocode'; manual RightRail create → NULL).
  • U.2 Add agent to listPending response + frontend PendingChange type.
  • U.3 Extract providerIcon() to a shared helper; DiffPanel renders an agent badge per row + a "Changes from X, Y" note when the pending set spans >1 agent (§9a).
  • U.4 GET /api/sessions/:id/agent-sessions route + api.coder.agentSessions + useAgentSessions(sessionId) (refetch on message_complete) (§9b).
  • U.5 AgentComposerBar optional sessionId prop → resumed / history / new-session chip beside the Provider picker; hidden on fresh chats and other callers (§9b).
  • Smoke U: stage edits with opencode then boocode → DiffPanel badges each row to the right agent; composer shows "resumed" when re-selecting opencode, "new session" for goose.

Phase 2 — Warm ACP backend (goose, qwen)

  • 2.1 backends/warm-acp.ts: persistent spawn + ClientSideConnection; initialize + session/new once; reuse acp-dispatch.ts handleSessionUpdate.
  • 2.2 prompt: session/prompt on the warm connection per turn; per-turn abort signal only.
  • 2.3 Child supervision: detached lifetime, exit handler marks status='crashed'.
  • 2.4 Dispatcher routes goose/qwen to warm backend; keep one-shot fallback for arena/MCP (or opt those into pool too — decide in review).
  • Smoke 2: two messages in a goose chat reuse the same process + ACP session + worktree; reasoning still renders; no per-turn respawn.
  • Smoke 2b (switch round-trip): opencode → boocode → opencode in one chat — opencode resumes the SAME agent_session_id (memory intact), boocode saw opencode's turns as history, all three shared the one worktree, and no agent was locked to the chat.

Phase 3 — Lifecycle hardening

  • 3.1 Idle TTL eviction keyed per (chat, agent); reattach-on-next-turn from agent_sessions.
  • 3.2 Crash recovery: opencode server restart recreates sessions; ACP re-session/new.
  • 3.3 Chat close/archive hook → closeSession for every (chat, agent) + remove the shared session_worktrees row + worktree; mark agent rows status='closed'.
  • 3.4 Orphan worktree reaper (extend periodic sweeper) + max-live-worktrees LRU cap.
  • 3.5 Re-baseline worktree diff after apply_pending.
  • 3.6 Reconnect test: restart BooCoder mid-session → next turn reattaches/recreates cleanly.
  • 3.7 Staging-boundary hint in DiffPanel (§9c): muted one-liner when the selected provider can't see another agent's unapplied worktree edits (derived from per-change agent + current provider; no new state).

Tests

  • T.1 agent-pool unit: get-or-create, idle evict, dispose drains in-flight (DB-opt-in pattern).
  • T.2 opencode SSE demux + reasoning dedup unit (fixture event stream).
  • T.3 per-session concurrency: two sessions run concurrently, one session serializes.

Docs

  • D.1 Update CLAUDE.md (BooCoder dispatch section) + BOOCODER.md health/contract.
  • D.2 Note opencode @opencode-ai/sdk dep + OPENCODE_SERVER_PASSWORD env in env docs.
  • D.3 CHANGELOG.md entry on tag (v2.6.0-persistent-agent-sessions).

Build / deploy gate

  • B.1 pnpm -C apps/server build && pnpm -C apps/coder build clean.
  • B.2 pnpm -C apps/server test (+ DB-opt-in) green.
  • B.3 Deploy: sudo systemctl restart boocoder; curl :9502/api/health reports tool count.