From f2974d6887ae67d57b6f022e583e069193e5ad57 Mon Sep 17 00:00:00 2001 From: indifferentketchup Date: Sun, 24 May 2026 15:11:16 +0000 Subject: [PATCH] =?UTF-8?q?v2.0=20proposal:=20BooCoder=20=E2=80=94=20write?= =?UTF-8?q?=20tools,=20pending=20changes,=20ACP=20dispatch,=20MCP=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive roadmap for the v2.0 major version bump. Covers: - Schema: pending_changes, tasks, available_agents tables + human_inbox view - Path A: native write tools (edit_file, create_file, delete_file) queuing through pending_changes before /apply flushes to disk - Path B: external agent dispatch via ACP (opencode, goose) or PTY fallback (claude, pi) with per-task git worktrees and automatic diff-on-completion - BooCoder MCP server: 6 tools exposing task primitives over stdio - Code lifts: agent-hub (Apache-2.0, task DAG), plandex (MIT, diff UX), ACP SDK (Apache-2.0, subprocess protocol), Paseo (AGPL, design-only) - Sub-versions: v2.0.0 (Path A), v2.0.1 (Path B), v2.0.2 (MCP server), v2.0.3 (CLI + polish) - Estimate: ~2200 LoC total All v1.x dependencies shipped (v1.13 parts, v1.14 outer loop, v1.15 MCP client, v1.16 codesight). v2.0 is unblocked. Co-Authored-By: Claude Opus 4.7 (1M context) --- openspec/changes/v2.0-boocoder/proposal.md | 274 +++++++++++++++++++++ openspec/changes/v2.0-boocoder/tasks.md | 130 ++++++++++ 2 files changed, 404 insertions(+) create mode 100644 openspec/changes/v2.0-boocoder/proposal.md create mode 100644 openspec/changes/v2.0-boocoder/tasks.md diff --git a/openspec/changes/v2.0-boocoder/proposal.md b/openspec/changes/v2.0-boocoder/proposal.md new file mode 100644 index 0000000..a9177b0 --- /dev/null +++ b/openspec/changes/v2.0-boocoder/proposal.md @@ -0,0 +1,274 @@ +# v2.0 — BooCoder + +Major version bump. New app `apps/coder/` inside the existing monorepo. Lands together with the `boocode_db` → `boochat_db` DB rename and the per-app subdomain split (`code.indifferentketchup.com` → BooChat, `coder.indifferentketchup.com` → BooCoder). + +## What BooCoder is + +A write-capable coding agent surface. Two execution paths, same UI: + +- **Path A (native):** BooCode's own inference loop with write tools (`edit_file`, `create_file`, `delete_file`). Edits queue in `pending_changes` — nothing touches disk until user approves via `/apply`. +- **Path B (dispatch):** Shells out to external CLI agents (`opencode`, `goose`, `claude`, `pi`) via ACP (preferred) or raw PTY (fallback). One git worktree per dispatch. Captures events into the same parts taxonomy. + +Both paths feed the same task DAG, same project registry, same pending-changes queue, same UI. + +## Why now + +v1.x proved the read-only loop works end-to-end: inference, tool dispatch, streaming, compaction, MCP client, outer loop, step caps, artifact rendering. The infrastructure is stable. The jump from "read-only chat" to "write-capable agent orchestrator" is the remaining gap between BooCode and having a real development environment. + +## Architecture + +### Three protocol roles (locked 2026-05-22) + +1. **MCP client (write-capable allowed).** Inherits v1.15 client. Write-capable MCP servers (e.g. `@modelcontextprotocol/server-filesystem`) route writes through `pending_changes`. Per-task allow/deny means dispatched tasks can have a different MCP roster. +2. **MCP server (BooCoder's own primitives).** Exposes `boocoder.create_task`, `boocoder.list_pending_changes`, `boocoder.apply`, `boocoder.reject`, `boocoder.dispatch_external_agent`, `boocoder.list_worktrees` as MCP tools. Stdio transport for local consumers (Sam's `opencode` in Termius); HTTP deferred until OAuth + secret storage. +3. **ACP client (host).** Spawns `opencode acp` and `goose acp` as JSON-RPC stdio subprocesses. Maps ACP events (file operations, tool calls, terminal output) to BooCode's parts taxonomy. MCP servers configured in BooCoder are auto-forwarded to the dispatched agent (per goose docs — `context_servers` is the field). + +### Container layout (post-v2.0) + +| Container | Port | Mount | Purpose | +|---|---|---|---| +| `boochat` (was `boocode`) | `100.114.205.53:9500` | `/opt:/opt:ro` | Read-only chat + MCP client | +| `booterm` | `100.114.205.53:9501` | `/opt:/opt:rw` | PTY/tmux terminal | +| `boocoder` | `100.114.205.53:9502` | `/opt:/opt:rw` (policy-gated) | Write tools + ACP host + MCP client + MCP server | +| `boochat_db` (was `boocode_db`) | `127.0.0.1:5500` | `boocode_pgdata` | Shared Postgres 16 | +| `codecontext` | internal `:8080` | `/opt:/opt:ro` | Analysis sidecar (shared) | + +### Caddy routing + +``` +code.indifferentketchup.com → boochat:9500 +coder.indifferentketchup.com → boocoder:9502 +term.indifferentketchup.com → booterm:9501 (or routed under code.*/term/) +``` + +## Schema (new tables) + +```sql +-- Pending changes: queued writes before /apply +CREATE TABLE IF NOT EXISTS pending_changes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID NOT NULL REFERENCES sessions(id), + task_id UUID REFERENCES tasks(id), + file_path TEXT NOT NULL, + operation TEXT NOT NULL CHECK (operation IN ('create', 'edit', 'delete')), + diff TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'applied', 'rejected', 'reverted')), + created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp() +); + +-- Tasks: the dispatch DAG +CREATE TABLE IF NOT EXISTS tasks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES projects(id), + parent_task_id UUID REFERENCES tasks(id), + state TEXT NOT NULL DEFAULT 'pending' + CHECK (state IN ('pending', 'running', 'completed', 'failed', 'blocked', 'cancelled')), + input TEXT NOT NULL, + output_summary TEXT, + agent TEXT, + model TEXT, + execution_path TEXT CHECK (execution_path IN ('native', 'acp', 'pty')), + worktree_path TEXT, + cost_tokens INTEGER, + started_at TIMESTAMPTZ, + ended_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp() +); + +-- Available agents: probed at startup +CREATE TABLE IF NOT EXISTS available_agents ( + name TEXT PRIMARY KEY, + install_path TEXT, + version TEXT, + supports_acp BOOLEAN NOT NULL DEFAULT false, + supports_mcp_client BOOLEAN NOT NULL DEFAULT false, + last_probed_at TIMESTAMPTZ +); + +-- Human inbox: tasks needing attention +CREATE VIEW human_inbox AS + SELECT * FROM tasks WHERE state IN ('blocked', 'failed'); +``` + +`task_templates` and `pipelines` deferred to v2.1 — overhead for single-user. The core is `tasks` + `pending_changes` + `available_agents`. + +## Path A — Native write tools + +### Tools + +| Tool | Description | +|---|---| +| `edit_file` | Apply a diff to an existing file. Input: `{file_path, old_string, new_string}`. Queues in `pending_changes` with `operation='edit'`. | +| `create_file` | Create a new file. Input: `{file_path, content}`. Queues as `operation='create'`. | +| `delete_file` | Delete a file. Input: `{file_path}`. Queues as `operation='delete'`. | +| `apply_pending` | Flush all pending changes for the current session to disk. Path-guarded. | +| `rewind` | Revert a specific applied change or all changes since a checkpoint. | + +### Path guard for writes + +Same `pathGuard()` function from BooChat, but with a write-path variant: +- `resolveWritePath(projectRoot, requested)` — uses `resolve()` (not `realpath()`, since the file may not exist yet for creates), then verifies the result starts with `projectRoot + sep`. +- Deny list: everything in `secret_guard.ts` (`.env`, `*.pem`, etc.) — can't write to those either. +- Defense-in-depth: the `pending_changes` queue means even a path-guard bypass only queues; it doesn't hit disk until `/apply` (which re-validates). + +### Diff format + +Standard unified diff (what `git diff` produces). The `edit_file` tool takes `old_string` / `new_string` (same as Claude Code's edit tool — the model is trained on this shape). Server computes the unified diff for storage in `pending_changes.diff`. + +### UI: per-pane diff viewer + +Frontend pane type `pending_changes` in BooCoder's workspace. Shows: +- List of queued changes with file path + operation +- Per-change diff view (syntax-highlighted, side-by-side or unified) +- Approve / Reject per change, or Approve All / Reject All + +## Path B — External agent dispatch + +### dispatch_external_agent tool + +```typescript +{ + agent: 'opencode' | 'claude' | 'goose' | 'pi', + model: string, // e.g. 'claude-opus-4-7' + task: string, // natural-language task description + worktree?: string, // optional — auto-creates if not specified +} +``` + +### Transport selection + +Dispatcher checks `available_agents.supports_acp` at runtime: +- **ACP** (preferred): `opencode acp`, `goose acp` — JSON-RPC stdio. Native session lifecycle, file-operation events, terminal events, permission prompts. +- **PTY** (fallback): `claude`, `pi`, `smallcode` — raw terminal capture via `node-pty`. Captures stdout/stderr/exit-code into PostgreSQL. Less structured than ACP. + +### Worktree management + +Each dispatched task gets its own git worktree: +```bash +git worktree add /tmp/booworktrees/ -b task- HEAD +``` + +On completion: diff the worktree against HEAD, queue the diff into `pending_changes` for the same task, clean up the worktree. User approves/rejects the diff the same way as Path A. + +### ACP event mapping + +ACP events → BooCode parts taxonomy: +- `file_operation` → `tool_call` part (name: `acp_edit_file`) + `tool_result` part +- `tool_call` → `tool_call` part (preserves name) +- `terminal_output` → routes into BooTerm pane +- `permission_request` → pause inference (same mechanism as `ask_user_input`) +- `session_end` → task state → `completed` or `failed` + +### MCP server auto-forward + +Per goose docs, `context_servers` field in the ACP session config auto-forwards BooCoder's configured MCP servers to the dispatched agent. One MCP config drives every agent. + +## Dispatcher worker + +Background process (or in-process `setInterval` for v2.0 simplicity) that: +1. Queries `tasks` WHERE `state = 'pending'` ORDER BY `created_at` +2. For each ready task (no unmet dependencies): + - Mark `state = 'running'` + - Resolve execution path (Path A if no agent specified, Path B if agent specified) + - Path A: run the inference loop with write tools enabled + - Path B: spawn ACP/PTY subprocess, stream events into parts + - On completion: mark `state = 'completed'` or `'failed'` + - Queue output diff into `pending_changes` +3. On failure: mark `state = 'failed'`, surface in `human_inbox` view + +## BooCoder MCP server + +Exposes BooCoder's primitives as MCP tools so external agents (Sam's opencode in Termius) can drive the task queue: + +| MCP Tool | Description | +|---|---| +| `boocoder.create_task` | Create a new task in the queue | +| `boocoder.list_pending_changes` | List queued changes awaiting approval | +| `boocoder.apply` | Apply a specific pending change | +| `boocoder.reject` | Reject a pending change | +| `boocoder.dispatch_external_agent` | Dispatch a task to an external agent | +| `boocoder.list_worktrees` | List active git worktrees | + +Stdio transport for local consumers. HTTP transport deferred until OAuth + secret storage. + +**Eval requirement:** run through `anthropics/skills mcp-builder` 10-question evaluation framework before shipping. + +## Code lifts + +### Primary architectural template + +**`Dominic789654/agent-hub`** (Apache-2.0) — task DAG schema, dispatcher worker, project registry, human inbox. Three-process model (board server + dispatcher + assistant terminal). BooCode adapts this into a single-process Fastify app (v2.0.0) with the dispatcher as an in-process worker. + +### Pending-changes UX + +**`plandex-ai/plandex`** (MIT) — diff/apply/rewind vocabulary. The `pending_changes` queue concept, per-file diff view, approve/reject UI pattern. No code lifted — schema and UX design only. + +### ACP client + +**`agentclientprotocol.com` spec + `@zed-industries/agent-client-protocol` SDK** (Apache-2.0) — local-subprocess ACP via stdio JSON-RPC. The SDK handles framing; BooCode maps events to its parts taxonomy. + +**`goose` docs** (`goose-docs.ai/docs/guides/acp-clients/`) — `context_servers` auto-forward pattern. Critical: one MCP config drives every dispatched agent. + +### MCP server + +**`anthropics/skills/mcp-builder`** (MIT) — 4-phase build workflow + 10-question evaluation framework for validating the MCP server before shipping. + +### Dispatcher pattern + +**Paseo (`getpaseo/paseo`)** — AGPL-3.0, **design only, no code lift**. Daemon+clients architecture, `--worktree` flag, CLI verb shape (`run/ls/attach/send`). BooCode reproduces the architecture using only license-clean patterns. + +**Roo Code Boomerang Tasks** — orchestrator with intentional capability restriction. Down-pass/up-pass context discipline (`new_task` message, `attempt_completion` result, no implicit inheritance). Explicit precedence override clause. + +### Write-tool security + +**opencode `permission/evaluate.ts`** — wildcard permission ruleset (already lifted in v1.15). Extended in v2.0 to gate write tools. + +**`covibes/zeroshot`** — blind-validation invariant. Verify gate runs in a separate agent context that only sees the diff and acceptance criteria, not the producing conversation. v2.0+ optional batch. + +## Sub-versions + +| Version | Scope | +|---|---| +| **v2.0.0** | Schema + Path A (native write tools + pending-changes queue + diff UI) + basic dispatcher | +| **v2.0.1** | Path B (ACP client for opencode/goose + PTY fallback for claude/pi + worktree management) | +| **v2.0.2** | BooCoder MCP server (stdio transport, `boocoder.*` tools, eval framework) | +| **v2.0.3** | Polish: `boocode` CLI (`run/ls/attach/send`), human_inbox UI, cost tracking | + +## Dependencies + +- v1.13 ✅ (parts table — the event taxonomy for everything) +- v1.14 ✅ (outer loop + step boundaries for future revert snapshots) +- v1.14.x-mcp ✅ (MCP client PoC — proves the protocol) +- v1.15 ✅ (full MCP client + tool globs — write-capable MCP servers route through pending_changes) +- v1.16 ✅ (codesight merge — codecontext now has blast-radius for impact analysis) + +All dependencies shipped. v2.0 is unblocked. + +## Estimate + +- v2.0.0: ~800 LoC (schema + write tools + pending-changes service + diff pane + dispatcher skeleton) +- v2.0.1: ~600 LoC (ACP client + PTY dispatch + worktree management + event mapping) +- v2.0.2: ~400 LoC (MCP server + 6 tool handlers + stdio transport + eval) +- v2.0.3: ~400 LoC (CLI client + inbox UI + cost aggregation) +- **Total: ~2200 LoC** across 4 sub-versions + +## Hard rules + +- BooChat stays read-only. BooCoder is the only surface with write tools. +- Path-guard correctness is the #1 test target. Fuzz against every traversal pattern. +- Pending-changes queue gates ALL writes (native + MCP). Nothing touches disk without user approval (or explicit auto-apply flag per task). +- One shared database. Cross-surface joins are valuable (task → chat → terminal debugging session). +- External CLI agents on the host, not in containers. BooCoder shells out via local-exec. +- No OAuth in v2.0. MCP server is stdio-only until secret storage lands. +- DB rename `boocode_db` → `boochat_db` lands with v2.0.0 (one-time migration). + +## Repos to clone before starting + +```bash +cd /opt/forks +git clone https://github.com/Dominic789654/agent-hub.git # Apache-2.0, task DAG reference +git clone https://github.com/plandex-ai/plandex.git # MIT, pending-changes UX +git clone https://github.com/anomalyco/opencode.git # MIT, permission evaluate.ts + MCP client reference +``` + +ACP SDK is an npm package (`@zed-industries/agent-client-protocol`), installed at implementation time. Paseo is design-only (AGPL, no code lift). diff --git a/openspec/changes/v2.0-boocoder/tasks.md b/openspec/changes/v2.0-boocoder/tasks.md new file mode 100644 index 0000000..4e2295b --- /dev/null +++ b/openspec/changes/v2.0-boocoder/tasks.md @@ -0,0 +1,130 @@ +# v2.0 — BooCoder task breakdown + +## Phase 0 — Prep (before any code) + +- [ ] Clone lift sources: `agent-hub`, `plandex`, `opencode` to `/opt/forks/` +- [ ] Read agent-hub's schema + dispatcher pattern (Apache-2.0) +- [ ] Read plandex's pending-changes + diff/apply/rewind flow (MIT) +- [ ] Read opencode's `permission/evaluate.ts` for write-gate patterns (MIT) +- [ ] Install ACP SDK: `pnpm add @zed-industries/agent-client-protocol` +- [ ] Verify `opencode acp` and `goose acp` are available on the host +- [ ] Write `openspec/changes/v2.0-boocoder/design.md` with finalized decisions + +## v2.0.0 — Schema + Path A (native write tools + pending-changes + diff UI) + +### Infra + +- [ ] Create `apps/coder/` directory skeleton (Fastify server, mirroring `apps/server/` structure) +- [ ] Create `apps/coder/Dockerfile` (Node 20 bookworm-slim, `/opt:/opt:rw` mount) +- [ ] Add `boocoder` service to `docker-compose.yml` (port 9502, boocode_net) +- [ ] Add Caddy route: `coder.indifferentketchup.com` → boocoder:9502 +- [ ] DB rename: `boocode_db` → `boochat_db` (one-time ALTER DATABASE + docker-compose volume rename) +- [ ] Schema migration: CREATE TABLE `pending_changes`, `tasks`, `available_agents`; CREATE VIEW `human_inbox` +- [ ] Container guidance: `BOOCODER.md` (bind-mounted at `/app/BOOCODER.md`) + +### Write tools + +- [ ] `apps/coder/src/services/write_guard.ts` — `resolveWritePath(projectRoot, filePath)` (resolve + prefix-check, no realpath since file may not exist) +- [ ] `apps/coder/src/services/pending_changes.ts` — queue, apply, reject, revert operations +- [ ] Tool: `edit_file` — takes `{file_path, old_string, new_string}`, computes unified diff, queues in `pending_changes` +- [ ] Tool: `create_file` — takes `{file_path, content}`, queues as `operation='create'` +- [ ] Tool: `delete_file` — takes `{file_path}`, queues as `operation='delete'` +- [ ] Tool: `apply_pending` — flushes pending changes to disk (re-validates write_guard before each write) +- [ ] Tool: `rewind` — reverts applied changes by inverse-diff + +### Inference loop + +- [ ] Port the v1.14 outer loop from `apps/server/` into `apps/coder/` (or share via workspace package) +- [ ] Register write tools in the coder's tool registry (alongside all read tools from BooChat) +- [ ] Permission gate: write tools require `pending_changes` queue (can't bypass to direct disk write) + +### Frontend (diff pane) + +- [ ] Create `apps/coder/web/` SPA (React + Vite, same stack as BooChat's `apps/web/`) +- [ ] Diff pane component: shows pending changes with syntax-highlighted diffs +- [ ] Approve / Reject per change, Approve All / Reject All buttons +- [ ] Workspace splitter integration (chat pane + diff pane side by side) + +### Verification + +- [ ] `pnpm -C apps/coder build` clean +- [ ] Write path-guard fuzz tests (traversal patterns, symlinks, non-existent paths, `.env` deny) +- [ ] `docker compose up --build -d` — boocoder container starts, healthcheck passes +- [ ] Smoke: send a chat requesting a file edit → see it queued in diff pane → approve → file written + +## v2.0.1 — Path B (ACP dispatch + PTY fallback + worktrees) + +### ACP client + +- [ ] `apps/coder/src/services/acp-client.ts` — spawn `opencode acp` / `goose acp` via `@zed-industries/agent-client-protocol` StdioTransport +- [ ] Event mapping: ACP `file_operation` → `tool_call` part, `terminal_output` → BooTerm route, `permission_request` → pause +- [ ] Session lifecycle: start, mid-session model switch, end +- [ ] MCP auto-forward: pass BooCoder's `context_servers` config to the ACP session + +### PTY fallback + +- [ ] `apps/coder/src/services/pty-dispatch.ts` — spawn `claude` / `pi` / `smallcode` via `node-pty` +- [ ] Capture stdout/stderr/exit-code into parts (less structured than ACP) +- [ ] Worktree setup: `git worktree add /tmp/booworktrees/ -b task- HEAD` +- [ ] On completion: diff worktree vs HEAD → queue into `pending_changes` + +### Dispatcher + +- [ ] `apps/coder/src/services/dispatcher.ts` — polls `tasks` WHERE `state='pending'`, picks by priority + creation order +- [ ] Transport selection: check `available_agents.supports_acp` at dispatch time +- [ ] On failure: mark `state='failed'`, surface in `human_inbox` +- [ ] On completion: mark `state='completed'`, queue diff if Path B + +### Agent probing + +- [ ] Startup probe: `which opencode && opencode --version`, `which goose`, `which claude`, `which pi` +- [ ] Populate `available_agents` table with version + ACP support + +### Verification + +- [ ] Smoke: dispatch a task to `opencode` via ACP → task completes → diff queued +- [ ] Smoke: dispatch to `claude` via PTY fallback → captures output → diff from worktree +- [ ] Worktree cleanup after task completion + +## v2.0.2 — BooCoder MCP server + +### Implementation + +- [ ] `apps/coder/src/services/mcp-server.ts` — register 6 tools as MCP tool handlers +- [ ] Stdio transport (use `@modelcontextprotocol/sdk` server-side, same SDK as client) +- [ ] Tools: `boocoder.create_task`, `boocoder.list_pending_changes`, `boocoder.apply`, `boocoder.reject`, `boocoder.dispatch_external_agent`, `boocoder.list_worktrees` +- [ ] Each tool maps to a DB operation or service call + +### Eval + +- [ ] Write 10-question eval per `anthropics/skills/mcp-builder` framework +- [ ] Run eval against the MCP server — all 10 must pass before shipping +- [ ] Document eval results in openspec + +### Verification + +- [ ] From a terminal: `echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | boocoder --mcp` → returns 6 tools +- [ ] From opencode: configure BooCoder as an MCP server in `~/.opencode/config.json`, verify tool calls work + +## v2.0.3 — Polish + +### CLI client + +- [ ] `apps/coder/src/cli.ts` — thin WebSocket/HTTP client against BooCoder API +- [ ] Verbs: `boocode run `, `boocode ls`, `boocode attach `, `boocode send ` +- [ ] Mirrors Paseo's UX, license-clean implementation + +### Human inbox UI + +- [ ] Frontend route showing tasks in `blocked`/`failed` state +- [ ] Per-task: view output, retry, cancel, reassign to different agent + +### Cost tracking + +- [ ] `tasks.cost_tokens` populated from inference usage +- [ ] Summary view: per-project, per-agent, per-day token spend + +### Verification + +- [ ] `boocode run "add a health endpoint"` from terminal → task appears in UI → completes → diff in pane +- [ ] `boocode ls` shows running/completed/failed tasks