# V1 Schema to ACP v1 Mapping (1:1)

## 1) Scope

This document maps every current v1 HTTP endpoint and every universal event type to the v1 surface (ACP JSON-RPC for agent/session traffic, HTTP for control-plane/platform APIs).

Important: this is a conversion reference only. Runtime `/v1/*` endpoints are removed in v1 and return HTTP 410.

Source of truth used:

- Current API: `docs/openapi.json`
- Current event schema: `docs/session-transcript-schema.mdx`
- Event enums/types: `server/packages/universal-agent-schema/src/lib.rs`
- ACP protocol docs: `~/misc/acp-docs/docs/protocol/*.mdx`
- ACP schema/method map: `~/misc/acp-docs/schema/schema.json`, `~/misc/acp-docs/schema/meta.json`
- ACP extensibility rules: `~/misc/acp-docs/docs/protocol/extensibility.mdx`

Transport assumption:

- v1 uses ACP over Streamable HTTP (POST + SSE) as the canonical public transport.
- WebSocket can be added later without changing this mapping.

## 2) Mapping Rules

1. Use ACP standard methods/events first.
2. If ACP stable has no equivalent, use either:
   - ACP extension methods (must start with `_`) for agent/session protocol gaps, or
   - HTTP `/v1/*` control-plane/platform endpoints for non-agent/session APIs.
3. Preserve legacy-only data in `_meta` (namespaced), not as ad-hoc root fields.
4. Use `_meta` for correlation and legacy envelope carry-over.

Extension namespace used in this spec:

- `_sandboxagent/...`

`_meta` namespace used in this spec:

- `_meta["sandboxagent.dev"]`

## 3) Endpoint Mapping (All v1 Endpoints)

| v1 endpoint | v1 mapping | Mapping type | Notes |
|---|---|---|---|
| `GET /v1/health` | `GET /v1/health` | HTTP control-plane | v1-parity payload on v1 route. |
| `GET /v1/agents` | `GET /v1/agents` | HTTP control-plane | Agent inventory, capabilities, server status, and optional models/modes for installed agents. |
| `POST /v1/agents/{agent}/install` | `POST /v1/agents/{agent}/install` | HTTP control-plane | Agent process + native agent install flow. |
| `GET /v1/agents/{agent}/models` | Folded into agent response `models` field (installed agents only) | HTTP control-plane | No standalone `/models` endpoint in v1. |
| `GET /v1/agents/{agent}/modes` | Folded into agent response `modes` field (installed agents only) | HTTP control-plane | No standalone `/modes` endpoint in v1. |
| `GET /v1/fs/entries` | `GET /v1/fs/entries` | HTTP platform API | Port v1 behavior. |
| `DELETE /v1/fs/entry` | `DELETE /v1/fs/entry` | HTTP platform API | Port v1 behavior, including `recursive`. |
| `GET /v1/fs/file` | `GET /v1/fs/file` | HTTP platform API | Raw bytes response (octet-stream), v1 parity. |
| `PUT /v1/fs/file` | `PUT /v1/fs/file` | HTTP platform API | Raw bytes write, v1 parity. |
| `POST /v1/fs/mkdir` | `POST /v1/fs/mkdir` | HTTP platform API | Port v1 behavior. |
| `POST /v1/fs/move` | `POST /v1/fs/move` | HTTP platform API | Port v1 behavior. |
| `GET /v1/fs/stat` | `GET /v1/fs/stat` | HTTP platform API | Port v1 behavior. |
| `POST /v1/fs/upload-batch` | `POST /v1/fs/upload-batch` | HTTP platform API | Tar upload/extract behavior from v1. |
| `GET /v1/sessions` | `GET /v1/sessions` | HTTP control-plane | Session inventory without ACP connection requirement. |
| `POST /v1/sessions/{session_id}` | `session/new` | Standard | Path `session_id` becomes alias in `_meta["sandboxagent.dev"].requestedSessionId`. |
| `POST /v1/sessions/{session_id}/messages` | `session/prompt` | Standard | Asynchronous behavior comes from transport (request + stream). |
| `POST /v1/sessions/{session_id}/messages/stream` | `session/prompt` + consume `session/update` on SSE | Standard | Streaming is transport-level, not a distinct ACP method. |
| `POST /v1/sessions/{session_id}/terminate` | `_sandboxagent/session/terminate` | Extension | Idempotent termination semantics distinct from `DELETE /v1/rpc`. |
| `GET /v1/sessions/{session_id}/events` | `_sandboxagent/session/events` (poll view over ACP stream) | Extension | Optional compatibility helper; canonical v1 is stream consumption. |
| `GET /v1/sessions/{session_id}/events/sse` | `GET /v1/rpc` SSE stream | Standard transport | Filter by sessionId client-side or via connection/session binding. |
| `POST /v1/sessions/{session_id}/permissions/{permission_id}/reply` | JSON-RPC response to pending `session/request_permission` request id | Standard | Bridge `permission_id` to request `id` in transport state. |
| `POST /v1/sessions/{session_id}/questions/{question_id}/reply` | JSON-RPC response to pending `_sandboxagent/session/request_question` | Extension | ACP stable has no generic question/HITL request method. |
| `POST /v1/sessions/{session_id}/questions/{question_id}/reject` | JSON-RPC response to pending `_sandboxagent/session/request_question` | Extension | Encode rejection in response outcome. |

### 3.1 `CreateSessionRequest` field mapping

| v1 field | ACP target | Notes |
|---|---|---|
| `agent` | `session/new.params._meta["sandboxagent.dev"].agent` (required) | Agent process selection is explicit session metadata, not an HTTP header. |
| `agentVersion` | `_meta["sandboxagent.dev"].agentVersionRequested` | No ACP standard field. |
| `directory` | `session/new.params.cwd` | Direct mapping. |
| `mcp` | `session/new.params.mcpServers[]` | Convert map format to ACP array format. |
| `agentMode` | `session/set_mode` or `session/set_config_option` | Prefer config option category `mode`; fallback to `_meta` hint. |
| `model` | `session/set_config_option` | Prefer config option category `model`; fallback to `_meta` hint. |
| `variant` | Deferred / out of scope | Do not implement in this pass. |
| `permissionMode` | `session/set_config_option` or `_meta` | No ACP core permission mode field. |
| `skills` | `_meta["sandboxagent.dev"].skills` | Product-specific. |
| `title` | `_meta["sandboxagent.dev"].title` | Product-specific. |
| path `session_id` | `_meta["sandboxagent.dev"].requestedSessionId` | Keep user-facing alias while ACP uses agent-generated `sessionId`. |

### 3.2 `MessageRequest` mapping

| v1 field | ACP target | Notes |
|---|---|---|
| `message` | `session/prompt.params.prompt[]` with `{"type":"text","text":...}` | Direct mapping. |
| `attachments[].path` | `resource_link.uri` or embedded `resource.uri` | Use absolute `file://` URI. |
| `attachments[].mime` | `mimeType` on ACP content block | Direct mapping when available. |
| `attachments[].filename` | `_meta["sandboxagent.dev"].filename` on content block | Not a native ACP field. |

### 3.3 Permission/question reply mapping

| v1 request | ACP target | Notes |
|---|---|---|
| `PermissionReplyRequest.reply=once` | `session/request_permission` response outcome `selected` with option kind `allow_once` | Map by option kind first, then option id. |
| `PermissionReplyRequest.reply=always` | outcome `selected` with option kind `allow_always` | If unavailable, fallback to closest allow option and record in `_meta`. |
| `PermissionReplyRequest.reply=reject` | outcome `selected` with option kind `reject_once` or `reject_always` | Prefer exact semantic match from offered options. |
| `QuestionReplyRequest.answers` | response to `_sandboxagent/session/request_question` | Preserve multi-select shape in `_meta["sandboxagent.dev"].answers`. |
| reject question | response to `_sandboxagent/session/request_question` with `outcome="rejected"` | Extension response schema. |

## 4) Event Mapping (All Universal Event Types)

| Current event type | ACP message/event mapping | Mapping type | `_meta` carry-over |
|---|---|---|---|
| `session.started` | Derived from successful `session/new` (or `session/load`) response; optional notify `_sandboxagent/session/started` | Standard-derived + Extension optional | Carry `event_id`, `sequence`, `source`, `synthetic` in `_meta["sandboxagent.dev"].legacyEvent`. |
| `session.ended` | `_sandboxagent/session/ended` notification | Extension | Include `{reason, terminated_by, message, exit_code}` under extension payload; preserve legacy envelope in `_meta`. |
| `turn.started` | Derived when `session/prompt` request is accepted | Standard-derived | Preserve `turn_id` in `_meta["sandboxagent.dev"].turn`. |
| `turn.ended` | `session/prompt` response (`stopReason`) | Standard | Map stop reason into legacy phase in `_meta`. |
| `item.started` | `session/update` with `tool_call` for tool items; for message/reasoning items, first chunk plus `_meta.phase="started"` | Standard + `_meta` | Include `item_id`, `native_item_id`, `parent_id`, `kind`, `role`. |
| `item.delta` | `session/update` with `agent_message_chunk`, `agent_thought_chunk`, or `tool_call_update.content` | Standard | Include legacy `item_id` in `_meta`. |
| `item.completed` | `session/update` with `tool_call_update.status=completed/failed`; message completion inferred at turn end, optionally emit `_sandboxagent/item/completed` | Standard-derived + Extension optional | Include full finalized item snapshot in `_meta`. |
| `error` | JSON-RPC error response when request-scoped; `_sandboxagent/error` for async/runtime errors | Standard + Extension | Preserve `code` and `details` in `_meta` if converted to JSON-RPC error object. |
| `permission.requested` | `session/request_permission` request from agent | Standard | Store legacy `permission_id` to JSON-RPC request-id mapping in `_meta`. |
| `permission.resolved` | JSON-RPC response to `session/request_permission` | Standard | Include selected option + mapped legacy status in `_meta`. |
| `question.requested` | `_sandboxagent/session/request_question` request from agent | Extension | Keep `{question_id,prompt,options,status}` in params; preserve legacy envelope in `_meta`. |
| `question.resolved` | JSON-RPC response to `_sandboxagent/session/request_question` | Extension | Include `response` and final status mapping in `_meta`. |
| `agent.unparsed` | `_sandboxagent/agent/unparsed` notification | Extension | Carry `{error,location,raw_hash}`; include raw payload hash/correlation in `_meta`. |

## 5) `_meta` Contract for Legacy Parity

ACP extensibility rules require custom data in `_meta`. We reserve:

- Root keys for tracing: `traceparent`, `tracestate`, `baggage` (ACP recommendation)
- Product namespace: `_meta["sandboxagent.dev"]`

Recommended shape:

```json
{
  "_meta": {
    "traceparent": "00-...",
    "sandboxagent.dev": {
      "connectionId": "acp_conn_123",
      "requestedSessionId": "my-session",
      "legacyEvent": {
        "eventId": "evt_123",
        "sequence": 42,
        "source": "agent",
        "synthetic": false,
        "nativeSessionId": "thread_abc"
      }
    }
  }
}
```

## 6) Known Gaps Requiring Extensions

These v1 capabilities are not covered by ACP stable methods and require `_sandboxagent/*` extensions:

- Session termination/history polling (`terminate`, `events` poll view)
- Generic question/HITL request-reply flow
- Session-ended notification payload parity (`_sandboxagent/session/ended`)

Track implementation friction and decisions in `research/acp/friction.md`.
