| 1 | # ACP editor integration |
| 2 | |
| 3 | <a href="../README.md">README</a> |
| 4 | · |
| 5 | <a href="./ACP.zh-CN.md">简体中文</a> |
| 6 | · |
| 7 | <a href="./GUIDE.md">Guide</a> |
| 8 | · |
| 9 | <a href="https://agentclientprotocol.com/">ACP specification</a> |
| 10 | |
| 11 | Reasonix implements Agent Client Protocol (ACP) v1 as an NDJSON JSON-RPC 2.0 |
| 12 | agent over standard input and output. Editors and other ACP hosts launch the |
| 13 | process, open one or more workspace-scoped sessions, and receive streamed |
| 14 | messages, tool activity, plans, permission requests, and configuration updates. |
| 15 | |
| 16 | Session status usage objects may include structured `costQuote` (original |
| 17 | currency, `originalTotals`, identity/official-table valuations, |
| 18 | `costComplete`, `displayComplete`, `displayStatus`, and `billingMode`) alongside legacy |
| 19 | `estimatedCost` / `currency` aliases that mirror the selected display valuation. |
| 20 | See [Billing](./BILLING.md). |
| 21 | |
| 22 | ## Start the agent |
| 23 | |
| 24 | An ACP host should launch one of these commands: |
| 25 | |
| 26 | ```sh |
| 27 | reasonix acp |
| 28 | reasonix acp --model deepseek-pro |
| 29 | reasonix acp |
| 30 | ``` |
| 31 | |
| 32 | `--model` selects the startup model when the client does not override it. |
| 33 | Ordinary requests always enter the executor. There is no automatic simple / |
| 34 | light / full task mode. Verification obligations come from real tool actions. |
| 35 | |
| 36 | Standard output is reserved for ACP messages. Reasonix sends diagnostics to |
| 37 | standard error, so hosts must not merge the two streams. Run `reasonix setup` |
| 38 | beforehand when no provider is configured; the initialize response also |
| 39 | advertises a terminal authentication method that launches `reasonix setup`. |
| 40 | |
| 41 | ## Initialize and negotiate capabilities |
| 42 | |
| 43 | Clients should call `initialize` before opening a session. Reasonix advertises |
| 44 | the following capability shape (irrelevant fields omitted): |
| 45 | |
| 46 | ```json |
| 47 | { |
| 48 | "protocolVersion": 1, |
| 49 | "agentCapabilities": { |
| 50 | "loadSession": true, |
| 51 | "sessionCapabilities": { |
| 52 | "list": {}, |
| 53 | "resume": {}, |
| 54 | "close": {}, |
| 55 | "delete": {} |
| 56 | }, |
| 57 | "promptCapabilities": { |
| 58 | "image": false, |
| 59 | "audio": false, |
| 60 | "embeddedContext": true |
| 61 | }, |
| 62 | "mcpCapabilities": { |
| 63 | "http": true, |
| 64 | "sse": false |
| 65 | }, |
| 66 | "_meta": { |
| 67 | "reasonix.io": { |
| 68 | "sessionSteer": { |
| 69 | "method": "_reasonix.io/session/steer" |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | ``` |
| 76 | |
| 77 | When the client advertises `fs.readTextFile`, `fs.writeTextFile`, or |
| 78 | `terminal`, Reasonix routes eligible file operations through the editor's |
| 79 | unsaved buffers and eligible foreground commands through a client-owned |
| 80 | terminal. Every file tool takes part — reads, edits and writes alike — so an |
| 81 | edit applies to what the editor currently shows instead of to the last saved |
| 82 | copy on disk. A non-UTF-8 file is not eligible: the ACP file methods are |
| 83 | text-only, so it stays on the local encoding-preserving path and keeps its |
| 84 | original charset. Without those client capabilities, the normal workspace |
| 85 | tools run locally inside the Reasonix process. |
| 86 | |
| 87 | ## Opt-in MCP interaction extension |
| 88 | |
| 89 | Hosts supporting MCP elicitation advertise this in `initialize.clientCapabilities`: |
| 90 | |
| 91 | ```json |
| 92 | {"_meta":{"reasonix.io":{"mcpInteraction":{"supported":true,"schemaVersion":1}}}} |
| 93 | ``` |
| 94 | |
| 95 | Reasonix advertises the matching capability under |
| 96 | `agentCapabilities._meta.reasonix.io.mcpInteraction`, including the method |
| 97 | `_reasonix.io/mcp/request_interaction`. Negotiated sessions use the interactive |
| 98 | MCP host profile. Clients without this exact opt-in keep the core profile and |
| 99 | receive no new reverse requests. This applies to new, loaded and rebuilt sessions. |
| 100 | |
| 101 | The reverse request contains `sessionId`, `promptId`, `turnId`, `server`, `mode` |
| 102 | and `message`, plus `requestedSchema` for a form or `url` and `elicitationId` for |
| 103 | URL mode. Respond with `{"action":"accept","content":{}}`, |
| 104 | `{"action":"decline"}` or `{"action":"cancel"}`. Validate the requested form; |
| 105 | URL flows require user interaction and must not send credentials as form content. |
| 106 | Hosts that cannot render a requested interaction should cancel it. |
| 107 | |
| 108 | Replies belong to the originating controller and turn. Cancellation, malformed |
| 109 | replies and rejected URLs cancel the interaction; content is ignored unless the |
| 110 | action is `accept`. Reasonix persists the decision through the controller before |
| 111 | releasing its MCP waiter. This does not replace standard |
| 112 | `session/request_permission` or change tool permission policy. The negotiated |
| 113 | host profile affects MCP capability/cache identity; transcript schema is unchanged. |
| 114 | |
| 115 | ## Session lifecycle |
| 116 | |
| 117 | Each ACP session owns an independent Reasonix controller, workspace root, model, |
| 118 | collaboration mode, approval mode, MCP set, and persisted transcript. State does |
| 119 | not leak between sessions. |
| 120 | |
| 121 | | Method | Behavior | |
| 122 | | --- | --- | |
| 123 | | `session/new` | Opens a session for an absolute `cwd` and returns its configuration state. | |
| 124 | | `session/load` | Opens a persisted ACP session and replays its transcript as `session/update` notifications. | |
| 125 | | `session/resume` | Opens a persisted session without replaying the transcript. | |
| 126 | | `session/prompt` | Runs one turn and streams updates until it returns a stop reason. | |
| 127 | | `session/cancel` | Cancels the active turn; this is a notification. | |
| 128 | | `session/list` | Lists live and persisted ACP sessions, optionally filtered by absolute `cwd`. | |
| 129 | | `session/close` | Stops a live session and releases resources without deleting history. | |
| 130 | | `session/delete` | Stops the session and removes its persisted ACP history. | |
| 131 | |
| 132 | `session/new`, `session/load`, and `session/resume` may include `mcpServers`. |
| 133 | Reasonix accepts stdio, Streamable HTTP, and legacy SSE servers. ACP's official `[{"name":"...","value":"..."}]` |
| 134 | shape is supported for stdio `env` and HTTP `headers`; the older object-map |
| 135 | shape remains accepted for compatibility. |
| 136 | |
| 137 | ## Session controls |
| 138 | |
| 139 | Reasonix exposes independent controls instead of combining unrelated choices in |
| 140 | one mode selector: |
| 141 | |
| 142 | | Control | Values | Wire surface | |
| 143 | | --- | --- | --- | |
| 144 | | Collaboration mode | `normal`, `plan`, `goal` | `modes` and `session/set_mode` | |
| 145 | | Model | Configured `provider/model` entries | `configOptions` with id `model` | |
| 146 | | Reasoning effort | Provider-supported levels or `auto` | `configOptions` with id `effort` | |
| 147 | | Permission preset | `read-only`, `workspace-write`, `danger-full-access` | `configOptions` with id `tool_approval` | |
| 148 | |
| 149 | Use `session/set_config_option` for model, effort, and tool approval. |
| 150 | Its parameters are `sessionId`, `configId` and `value`, where `configId` is the |
| 151 | `id` of the option as advertised in `configOptions`: |
| 152 | |
| 153 | ```json |
| 154 | { |
| 155 | "jsonrpc": "2.0", |
| 156 | "id": 3, |
| 157 | "method": "session/set_config_option", |
| 158 | "params": { |
| 159 | "sessionId": "session-id", |
| 160 | "configId": "tool_approval", |
| 161 | "value": "danger-full-access" |
| 162 | } |
| 163 | } |
| 164 | ``` |
| 165 | |
| 166 | Note that the field is `configId`, not `optionId`. The result is the full |
| 167 | refreshed `configOptions` array. An unknown id returns `-32602 InvalidParams`. |
| 168 | |
| 169 | Model and effort changes rebuild the session controller while preserving its |
| 170 | history and the other axes. Tool-approval changes update the gate in place |
| 171 | without rebuilding the controller. |
| 172 | |
| 173 | Execution modes are gone. For one compatibility version, clients that still |
| 174 | send `session/set_config_option` with `configId` `agent_preset` or `work_mode` |
| 175 | (including legacy aliases `profile`, `runtime_profile`, `token_mode`) receive a |
| 176 | successful no-op: nothing switches, nothing rebuilds, and the result carries a |
| 177 | `deprecatedNotice` explaining the adaptive standard execution. |
| 178 | The returned `configOptions` list does not advertise these retired selectors or |
| 179 | `quality_floor`. A known legacy `quality_floor` value is accepted as the same |
| 180 | no-op, while unknown values still return `InvalidParams`. |
| 181 | |
| 182 | For older clients, `session/set_model` remains available. The legacy |
| 183 | `session/set_mode` values `default` and `auto` are also accepted as Normal + |
| 184 | Read only and Normal + Workspace access respectively; new clients should use |
| 185 | the independent selectors above. Legacy permission values are accepted only as |
| 186 | input migration aliases and are never advertised in `configOptions`. |
| 187 | |
| 188 | ## Prompts, updates, and approvals |
| 189 | |
| 190 | `session/prompt` accepts text blocks and embedded text resources. Images and |
| 191 | audio are not advertised. During a turn, Reasonix may send: |
| 192 | |
| 193 | - agent message and thought chunks; |
| 194 | - pending and completed tool-call updates; |
| 195 | - complete plan updates derived from `todo_write`; |
| 196 | - available slash commands; |
| 197 | - current-mode and configuration-option updates; and |
| 198 | - `session/request_permission` requests for permission-gated tools and user |
| 199 | questions. |
| 200 | |
| 201 | Hosts should keep the `session/prompt` request open until Reasonix returns its |
| 202 | stop reason, while continuing to process requests and notifications in both |
| 203 | directions. |
| 204 | |
| 205 | Reasonix emits only ACP v1 stop reasons. Model completion ends the ordinary |
| 206 | turn without a host readiness check or recovery action. An explicit model-round limit (`max_steps`) sends a `[warning]`, returns |
| 207 | `max_turn_requests`, and records a paused vendor outcome. A host task-time, |
| 208 | token, or cost budget also sends a `[warning]` and records a paused outcome, |
| 209 | but returns `end_turn` because ACP v1 has no task-budget-specific stop reason. |
| 210 | The completion validator has been removed. A clean model stop without tool |
| 211 | calls returns `end_turn`; a response with tool calls continues through the |
| 212 | agent loop, and a truly empty response is retried at the frozen-request |
| 213 | boundary. Legacy `completion_validation`, `completion_evaluator_model`, and |
| 214 | `REASONIX_COMPLETION_VALIDATION_MODE` settings remain readable but are ignored |
| 215 | and are no longer emitted. Host-owned readiness, budget, tool-safety, and |
| 216 | recovery boundaries remain active. |
| 217 | Client cancellation returns `cancelled`, even when the interrupted runner exits |
| 218 | without an error. Other provider, tool, or runtime failures return a JSON-RPC |
| 219 | `-32603 InternalError` whose message contains a bounded, credential-redacted |
| 220 | cause; they do not return a successful prompt result with a non-standard |
| 221 | `stopReason`. |
| 222 | |
| 223 | When the status phase is `readiness_paused`, resume that exact check with a |
| 224 | `session/prompt` request whose optional `action` is |
| 225 | `"final_readiness_recovery"`. Sending `/continue-checks` as the sole text block |
| 226 | is the compatibility form. Both forms consume a one-shot, persisted host |
| 227 | checkpoint; ordinary prompt text never inherits it, and a stale action after a |
| 228 | newer user turn is rejected as JSON-RPC `-32600 InvalidRequest` without |
| 229 | publishing or persisting a synthetic status turn. |
| 230 | |
| 231 | ## Mid-turn steering extension |
| 232 | |
| 233 | Reasonix exposes mid-turn guidance as an ACP v1 vendor extension. It is not a |
| 234 | core ACP method, and it is not the still-unreleased ACP v2 `session/inject` |
| 235 | proposal. |
| 236 | |
| 237 | ### Discover support |
| 238 | |
| 239 | Read the method name from: |
| 240 | |
| 241 | ```text |
| 242 | agentCapabilities._meta["reasonix.io"].sessionSteer.method |
| 243 | ``` |
| 244 | |
| 245 | Do not assume the extension exists, and do not call the unnamespaced |
| 246 | `session/steer` name. ACP reserves non-underscore method names for the core |
| 247 | protocol. |
| 248 | |
| 249 | ### Send guidance |
| 250 | |
| 251 | Call the advertised method while `session/prompt` is active: |
| 252 | |
| 253 | ```json |
| 254 | { |
| 255 | "jsonrpc": "2.0", |
| 256 | "id": 2, |
| 257 | "method": "_reasonix.io/session/steer", |
| 258 | "params": { |
| 259 | "sessionId": "session-id", |
| 260 | "prompt": [ |
| 261 | {"type": "text", "text": "use email instead of username"} |
| 262 | ] |
| 263 | } |
| 264 | } |
| 265 | ``` |
| 266 | |
| 267 | A persistent session returns an item id and disposition: |
| 268 | |
| 269 | ```json |
| 270 | {"itemId":"inbox-item-id","disposition":"steer_accepted"} |
| 271 | ``` |
| 272 | |
| 273 | Reasonix durably commits the guidance before returning. `steer_accepted` means |
| 274 | the active turn accepted it; `queued_followup` means that admission lost a race |
| 275 | or no turn was active, so the same item remains queued for a later turn. A |
| 276 | pathless compatibility session may omit `itemId` and still returns |
| 277 | `steer_accepted`. Applied guidance is persisted in normal history; transcript |
| 278 | replay shows the original user text, not Reasonix's internal steer marker. |
| 279 | |
| 280 | | Condition | JSON-RPC result | |
| 281 | | --- | --- | |
| 282 | | Active prompt accepted durable guidance | `{"itemId":"...","disposition":"steer_accepted"}` | |
| 283 | | Guidance persisted but active admission was rejected | `{"itemId":"...","disposition":"queued_followup"}` | |
| 284 | | Unknown session or empty prompt | `-32602 InvalidParams` | |
| 285 | | Pathless compatibility session has no active prompt | `-32600 InvalidRequest` | |
| 286 | | Client calls `session/steer` | `-32601 MethodNotFound` | |
| 287 | |
| 288 | On `InvalidRequest`, the compatibility session did not queue the guidance. |
| 289 | |
| 290 | ## Durable session inbox extension |
| 291 | |
| 292 | Discover the versioned queue at |
| 293 | `agentCapabilities._meta["reasonix.io"].sessionInbox`. Schema version 1 |
| 294 | advertises method names in its `methods` map; clients must use those advertised |
| 295 | names rather than constructing vendor method strings. |
| 296 | |
| 297 | | Key | Purpose | Main parameters | |
| 298 | | --- | --- | --- | |
| 299 | | `enqueue` | Persist a follow-up or steer | `sessionId`, `text`, optional `intent`, `idempotencyKey` | |
| 300 | | `list` | Read metadata, capacity, pause and recovery state | `sessionId` | |
| 301 | | `get` | Read one full envelope on demand | `sessionId`, `itemId` | |
| 302 | | `update` / `delete` | Edit or delete pending work | `sessionId`, `itemId` | |
| 303 | | `move` | Reorder pending work | `sessionId`, `itemId`, zero-based `toIndex` | |
| 304 | | `setPaused` | Pause or resume dispatch | `sessionId`, `paused` | |
| 305 | | `retry` / `refresh` | Retry uncertain work or re-freeze references | `sessionId`, `itemId` | |
| 306 | |
| 307 | `enqueue` returns `itemId`, `disposition`, `position`, `paused`, and |
| 308 | `idempotent`. List responses contain previews and byte counts, never prompt |
| 309 | bodies. A recovered inbox is paused; clients should let users inspect it before |
| 310 | calling `setPaused` with `false`. |
| 311 | |
| 312 | ## Runtime reload and extension surface |
| 313 | |
| 314 | Reasonix advertises two more extension points in |
| 315 | `agentCapabilities._meta["reasonix.io"]`: |
| 316 | |
| 317 | - `sessionReloadExtensions` — the vendor method |
| 318 | `_reasonix.io/session/reloadExtensions`. Calling it reloads the session's |
| 319 | agent runtime (extensions, tools, skills, commands, hooks, providers) with |
| 320 | the same fail-atomic semantics as the CLI `/reload` command: while a turn |
| 321 | or rebuild is active exactly one reload is queued (`{"queued": true}`) and |
| 322 | runs when the session goes idle; otherwise the runtime is rebuilt and |
| 323 | swapped atomically, and a failed rebuild keeps the previous runtime. After |
| 324 | a successful reload Reasonix pushes a fresh `available_commands_update`. |
| 325 | - `extensionSurface` — structured extension UI support. Clients that also |
| 326 | advertise `reasonix.io.extensionSurface` in their initialize `_meta` |
| 327 | receive structured extension surface payloads; clients without it receive |
| 328 | equivalent text fallbacks (`agent_message_chunk` for cards and statuses, |
| 329 | permission requests for extension forms), so no client-side handling is |
| 330 | required to stay compatible. |
| 331 | |
| 332 | Extension actions declared by installed plugins are exposed as |
| 333 | `/<plugin>:<action>` in `available_commands_update` and can be invoked like |
| 334 | any other slash command. |
| 335 | |
| 336 | ## Compatibility and cache behavior |
| 337 | |
| 338 | | Surface | Older or non-Reasonix clients | Conclusion | |
| 339 | | --- | --- | --- | |
| 340 | | Existing ACP v1 methods | Their names and response shapes are unchanged. | Compatible | |
| 341 | | Capability `_meta` | Unknown metadata may be ignored. | Compatible | |
| 342 | | Persisted transcripts | Transcript schema is unchanged; the inbox is a versioned sidecar. | Compatible | |
| 343 | | CLI, Desktop, and Bot steering | Rejected steers remain durable follow-ups. | Compatible | |
| 344 | |
| 345 | Steering appends a user-requested message to normal conversation history. It |
| 346 | does not change the system prompt, tool schemas, tool order, or other stable |
| 347 | provider-prefix bytes. The next provider request necessarily misses the suffix |
| 348 | that did not previously exist, just like any normal new user message, while the |
| 349 | earlier prefix remains reusable. |
| 350 | |
| 351 | ## Client integration checklist |
| 352 | |
| 353 | 1. Launch `reasonix acp` with separate stdin, stdout, and stderr streams. |
| 354 | 2. Call `initialize` and honor both standard and `_meta` capabilities. |
| 355 | 3. Open sessions with absolute workspace paths and keep their ids isolated. |
| 356 | 4. Process agent-to-client filesystem, terminal, and permission requests while |
| 357 | a prompt is running. |
| 358 | 5. Show steer UI only when the Reasonix capability is advertised and a prompt |
| 359 | is active. |
| 360 | 6. Branch on the steer `disposition`; both accepted steer and queued follow-up |
| 361 | are durable, but only the former can affect the active turn. |
| 362 | 7. Use `session/close` for resource cleanup and `session/delete` only when the |
| 363 | user intends to remove persisted history. |
| 364 |