sandbox-agent/research/acp/simplify-server.md
2026-02-11 07:57:02 -08:00

242 lines
7.8 KiB
Markdown

# ACP Simplified Server Spec
## 1) Scope and Intent
This spec replaces the current ACP runtime model with a simple stdio proxy model:
- Sandbox Agent becomes a **dumb ACP HTTP <-> stdio proxy**.
- ACP transport moves from `/v1/rpc` to `/v1/acp/{server_id}`.
- `server_id` is client-provided and is the only ACP transport identity.
- No server-side ACP extensions and no custom metadata processing.
- Session metadata/state moves to clients.
- Non-ACP functionality is exposed as HTTP endpoints.
Backwards compatibility is explicitly out of scope.
## 2) Hard Breaking Changes
- Remove `/v1/rpc` (`POST`, `GET`, `DELETE`) completely.
- Remove ACP extension support (`_sandboxagent/*`) completely.
- Remove ACP metadata contract (`params._meta["sandboxagent.dev"]`) from runtime behavior.
- Disable OpenCode compatibility completely (`/opencode/*` not mounted).
- Remove server-side session registry semantics tied to ACP transport.
## 3) ACP Transport
### 3.1 Endpoints
For each client-defined `{server_id}`:
- `POST /v1/acp/{server_id}`
- `GET /v1/acp/{server_id}` (SSE)
- `DELETE /v1/acp/{server_id}`
Control-plane ACP transport endpoint:
- `GET /v1/acp` (list active ACP transport instances)
No connection-id header is used.
### 3.2 Bootstrap / Creation
A transport instance is created lazily on first `POST`.
- First `POST` to a new `{server_id}` **must include `agent`** as query parameter:
- `POST /v1/acp/{server_id}?agent=claude`
- Server behavior on first `POST`:
1. Validate agent id.
2. Lazy-install binaries if missing (current install policy retained).
3. Lazy-start one ACP stdio process for this `{server_id}`.
4. Forward JSON-RPC payload to that process.
Behavior for existing `{server_id}`:
- `agent` query param is optional.
- If provided and mismatched with existing bound agent, return `409 Conflict`.
If `{server_id}` does not exist and no `agent` is provided, return `400 Bad Request`.
### 3.3 Message Semantics
Sandbox Agent does not inspect ACP method semantics.
- `POST` accepts one JSON-RPC envelope.
- If envelope is request (`method` + `id`): wait for matching stdio response and return `200` + JSON body.
- If envelope is notification or response without `method`: forward and return `202` empty.
- `GET` streams agent->client messages as SSE.
- Replay semantics remain: `Last-Event-ID` supported using in-memory ring buffer per `{server_id}`.
- `DELETE` closes `{server_id}` transport instance and terminates subprocess.
### 3.4 SSE Framing
Same framing as current transport profile:
- `event: message`
- `id: <monotonic sequence per server_id>`
- `data: <single JSON-RPC object>`
- keepalive comment heartbeat every 15s
### 3.5 Process Model
- One ACP process per `{server_id}`.
- Multiple `{server_id}` can target the same agent type.
- Each `{server_id}` has isolated pending requests and replay buffer.
### 3.6 Error Mapping
- Invalid JSON envelope: `400 application/problem+json`
- Missing/invalid content type: `415`
- Unknown agent: `400`
- Unknown `{server_id}` for `GET`/`DELETE`/non-bootstrap `POST`: `404`
- Agent mismatch on existing `{server_id}`: `409`
- Timeout waiting for request response: `504`
- Subprocess spawn/write/read failures: `502`
- Successful `DELETE`: `204` (idempotent)
## 4) ACP Adapter Integration
Sandbox Agent reuses `acp-http-adapter` runtime internals for per-server stdio bridging.
- ACP stdio framing follows ACP docs (UTF-8, newline-delimited JSON-RPC).
- No synthetic `_sandboxagent/*` ACP messages are emitted.
- No transport-level metadata injection or translation.
- Sandbox Agent only performs HTTP routing/lifecycle and subprocess orchestration.
## 5) HTTP Endpoints for Non-ACP Features
These are the proposed HTTP surfaces to review.
### 5.1 Agents
- `GET /v1/agents`
- List known agents + install/runtime status.
- `POST /v1/agents/{agent}/install`
- Trigger install/reinstall with existing options.
### 5.2 Filesystem
All filesystem operations are HTTP-only (no ACP extension mirrors):
- `GET /v1/fs/entries`
- `GET /v1/fs/file`
- `PUT /v1/fs/file`
- `DELETE /v1/fs/entry`
- `POST /v1/fs/mkdir`
- `POST /v1/fs/move`
- `GET /v1/fs/stat`
- `POST /v1/fs/upload-batch`
### 5.3 MCP Config (HTTP)
Directory-scoped MCP config endpoints (copy v1 `mcp` config shape, but bind to `directory` instead of session init):
- `GET /v1/config/mcp?directory=<...>&mcpName=<name>`
- Returns one MCP entry by name.
- `PUT /v1/config/mcp?directory=<...>&mcpName=<name>`
- Upserts one MCP entry by name.
- `DELETE /v1/config/mcp?directory=<...>&mcpName=<name>`
- Deletes one MCP entry by name.
Notes:
- Entry payload schema is v1-compatible MCP server config:
- same as one value from legacy `CreateSessionRequest.mcp[<name>]`.
- supports both local (stdio) and remote (http/sse) server forms.
- `directory` is required on all MCP config operations.
- `mcpName` is required on all MCP config operations.
- Server stores/retrieves config only and does not inject ACP payload metadata.
### 5.4 Skills Config (HTTP)
Directory-scoped Skills config endpoints (copy v1 skills config shape, but bind to `directory` instead of session init):
- `GET /v1/config/skills?directory=<...>&skillName=<name>`
- Returns one skill entry by name.
- `PUT /v1/config/skills?directory=<...>&skillName=<name>`
- Upserts one skill entry by name.
- `DELETE /v1/config/skills?directory=<...>&skillName=<name>`
- Deletes one skill entry by name.
Notes:
- Entry payload schema is v1-compatible skills config:
- same source object semantics as legacy `CreateSessionRequest.skills`.
- includes `sources` behavior and compatible source options.
- `directory` is required on all skill config operations.
- `skillName` is required on all skill config operations.
- No ACP-side mutation/injection by server.
## 6) Session Metadata Ownership
Server no longer owns ACP session metadata.
- No `_sandboxagent/session/set_metadata`.
- No `_sandboxagent/session/list` / `_sandboxagent/session/get`.
- Client owns session indexing, labels, and metadata persistence.
- `GET /v1/acp`
- Required endpoint.
- Returns active `{server_id}` instances and process status only.
## 7) Security/Auth
- Existing bearer token auth remains for `/v1/*` when enabled.
- Auth is enforced at HTTP layer only.
- No extra principal scoping inside ACP runtime beyond route auth.
## 8) Testing Requirements
Minimum required coverage:
- ACP proxy e2e for request/response/notification/SSE replay on `/v1/acp/{server_id}`.
- Multi-instance isolation (`server-a`, `server-b`, same agent).
- Lazy install/start on first POST bootstrap.
- Idempotent `DELETE` + cleanup.
- Explicit regression test that `_sandboxagent/*` methods are not handled specially.
## 9) Implementation Checklist
1. Add new router surface `/v1/acp/{server_id}` and remove `/v1/rpc`.
2. Replace current ACP runtime method handling with per-server dumb proxy runtime.
3. Remove extension metadata advertisement/handlers.
4. Remove OpenCode router mount.
5. Keep/add HTTP endpoints listed in section 5.
6. Update OpenAPI/docs to reflect new transport and removed ACP extensions.
## 10) Final HTTP Endpoint Inventory
Expected HTTP endpoints after migration:
- `GET /`
- `GET /v1/health`
- `GET /v1/acp`
- `POST /v1/acp/{server_id}`
- `GET /v1/acp/{server_id}`
- `DELETE /v1/acp/{server_id}`
- `GET /v1/agents`
- `POST /v1/agents/{agent}/install`
- `GET /v1/fs/entries`
- `GET /v1/fs/file`
- `PUT /v1/fs/file`
- `DELETE /v1/fs/entry`
- `POST /v1/fs/mkdir`
- `POST /v1/fs/move`
- `GET /v1/fs/stat`
- `POST /v1/fs/upload-batch`
- `GET /v1/config/mcp?directory=...&mcpName=...`
- `PUT /v1/config/mcp?directory=...&mcpName=...`
- `DELETE /v1/config/mcp?directory=...&mcpName=...`
- `GET /v1/config/skills?directory=...&skillName=...`
- `PUT /v1/config/skills?directory=...&skillName=...`
- `DELETE /v1/config/skills?directory=...&skillName=...`
Removed/disabled surfaces:
- `/v1/rpc` removed.
- `/opencode/*` disabled/unmounted.
- No `_sandboxagent/*` ACP extension behavior.