mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
A session method refused because another runtime owns the transcript answered -32600 and prose, the code a malformed frame gets; an ownership probe that could not decide answered -32603, the same as a disk that would not write. A client could not tell "held" from "unreadable" from "your request was broken", so it could not decide whether to ask the person to close a window or retry. session/new, session/load and session/resume now answer -32022 with data.code busy.session_in_use for a held lease and -32023 with data.code session.lease_unprovable when the store's probe cannot prove ownership either way; -32600 is left to a genuinely malformed request. The human message stays. The HTTP frontend, the kernel, the lease and the inbox are untouched. docs/ACP.md states the table and what a client does with each answer. repolint's family-vs-writer check is now parameterised by family; it reads the lease sentinels beside the inbox ones, so a lease sentinel added without a coded case fails by name against the ACP writer.
558 lines
27 KiB
Markdown
558 lines
27 KiB
Markdown
# ACP editor integration
|
|
|
|
<a href="../README.md">README</a>
|
|
·
|
|
<a href="./GUIDE.md">Guide</a>
|
|
·
|
|
<a href="https://agentclientprotocol.com/">ACP specification</a>
|
|
|
|
Reasonix implements Agent Client Protocol (ACP) v1 as an NDJSON JSON-RPC 2.0
|
|
agent over standard input and output. Editors and other ACP hosts launch the
|
|
process, open one or more workspace-scoped sessions, and receive streamed
|
|
messages, tool activity, plans, permission requests, and configuration updates.
|
|
|
|
Session status usage objects may include structured `costQuote` (original
|
|
currency, `originalTotals`, identity/official-table valuations,
|
|
`costComplete`, `displayComplete`, `displayStatus`, and `billingMode`) alongside legacy
|
|
`estimatedCost` / `currency` aliases that mirror the selected display valuation.
|
|
See [Billing](./BILLING.md).
|
|
|
|
## Start the agent
|
|
|
|
An ACP host should launch one of these commands:
|
|
|
|
```sh
|
|
reasonix acp
|
|
reasonix acp --model deepseek-pro
|
|
reasonix acp --preset delivery
|
|
```
|
|
|
|
`--model` selects the startup model when the client does not override it.
|
|
`--preset` sets the startup execution setting to `light`, `balanced`, or `delivery`
|
|
(default `balanced`). Legacy `--profile economy|balanced|delivery` still works
|
|
(`economy` → `light`). Both remain session-configurable after initialization.
|
|
|
|
Standard output is reserved for ACP messages. Reasonix sends diagnostics to
|
|
standard error, so hosts must not merge the two streams. Run `reasonix setup`
|
|
beforehand when no provider is configured; the initialize response also
|
|
advertises a terminal authentication method that launches `reasonix setup`.
|
|
|
|
## Initialize and negotiate capabilities
|
|
|
|
Clients should call `initialize` before opening a session. Reasonix advertises
|
|
the following capability shape (irrelevant fields omitted):
|
|
|
|
```json
|
|
{
|
|
"protocolVersion": 1,
|
|
"agentCapabilities": {
|
|
"loadSession": true,
|
|
"sessionCapabilities": {
|
|
"list": {},
|
|
"resume": {},
|
|
"close": {},
|
|
"delete": {},
|
|
"additionalDirectories": {}
|
|
},
|
|
"promptCapabilities": {
|
|
"image": true,
|
|
"audio": false,
|
|
"embeddedContext": true
|
|
},
|
|
"mcpCapabilities": {
|
|
"http": true,
|
|
"sse": false
|
|
},
|
|
"_meta": {
|
|
"reasonix.io": {
|
|
"sessionSteer": {
|
|
"method": "_reasonix.io/session/steer"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
When the client advertises `fs.readTextFile`, `fs.writeTextFile`, or
|
|
`terminal`, Reasonix routes eligible file operations through the editor's
|
|
unsaved buffers and eligible foreground commands through a client-owned
|
|
terminal. Every file tool takes part — reads, edits and writes alike — so an
|
|
edit applies to what the editor currently shows instead of to the last saved
|
|
copy on disk. A non-UTF-8 file is not eligible: the ACP file methods are
|
|
text-only, so it stays on the local encoding-preserving path and keeps its
|
|
original charset. Without those client capabilities, the normal workspace
|
|
tools run locally inside the Reasonix process.
|
|
|
|
|
|
### Authentication
|
|
|
|
`session/new` fails with the ACP "Authentication required" error (`-32000`) when
|
|
the selected model has no API key or no provider is configured, per the [ACP schema](https://agentclientprotocol.com/protocol/v1/schema) and
|
|
[authentication](https://agentclientprotocol.com/protocol/v1/authentication) pages:
|
|
|
|
- the advertised `reasonix-setup` method is of type `terminal`, so a client runs `reasonix setup` interactively and then reconnects; it must not send `authenticate` for it;
|
|
- provider keys are read from `<Reasonix home>/.env`, never from the host's environment variables, so an `env` block on the host side cannot supply one.
|
|
|
|
## Session lifecycle
|
|
|
|
Each ACP session owns an independent Reasonix controller, workspace root, model,
|
|
execution setting (agent preset), collaboration mode, approval mode, MCP set, and
|
|
persisted transcript. State does not leak between sessions.
|
|
|
|
| Method | Behavior |
|
|
| --- | --- |
|
|
| `session/new` | Opens a session for an absolute `cwd` and returns its configuration state. |
|
|
| `session/load` | Opens a persisted ACP session and replays its transcript as `session/update` notifications. |
|
|
| `session/resume` | Opens a persisted session without replaying the transcript. |
|
|
| `session/prompt` | Runs one turn and streams updates until it returns a stop reason. |
|
|
| `session/cancel` | Cancels the active turn; this is a notification. |
|
|
| `session/list` | Lists live and persisted ACP sessions, optionally filtered by absolute `cwd`. |
|
|
| `session/close` | Stops a live session and releases resources without deleting history. |
|
|
| `session/delete` | Stops the session and removes its persisted ACP history. |
|
|
|
|
`session/new`, `session/load`, and `session/resume` may include `mcpServers`.
|
|
Reasonix accepts stdio, Streamable HTTP, and legacy SSE servers. ACP's official `[{"name":"...","value":"..."}]`
|
|
shape is supported for stdio `env` and HTTP `headers`; the older object-map
|
|
shape remains accepted for compatibility.
|
|
|
|
`session/new`, `session/load`, and `session/resume` may also include
|
|
`additionalDirectories`: absolute folders that join the session's workspace as
|
|
write roots.
|
|
|
|
- They hold for that session only; nothing is written to the user's folder grants.
|
|
- Each folder gets the vetting a user-granted root gets (it must exist, belong to
|
|
the user, hold no keys, not be a link to one, and not overlap another write root).
|
|
- One refused folder refuses the whole request with `-32602` and `data.code`
|
|
naming the reason (for example `workspace_roots.protected`); nothing is skipped silently.
|
|
- At most 32 folders. An agent started to write only inside the cwd refuses them
|
|
(`acp.additional_directories.disabled`).
|
|
- Resuming an already-open session with a different set is refused
|
|
(`acp.additional_directories.session_open`); close it first.
|
|
|
|
### When a session is already open elsewhere
|
|
|
|
`session/new`, `session/load` and `session/resume` take the transcript's lease
|
|
for the session's lifetime. A lease refusal answers with an implementation-defined
|
|
JSON-RPC code and a `data.code` a client branches on; `message` stays Reasonix's
|
|
English, which clients still show.
|
|
|
|
```json
|
|
{"error": {"code": -32022, "message": "session/load: this session is already open elsewhere in this Reasonix (since 09:14); close the other Reasonix window or process first", "data": {"code": "busy.session_in_use"}}}
|
|
```
|
|
|
|
- `-32022` is "another runtime owns this session" and `-32023` is "ownership
|
|
could not be proven". They are different answers: the first names a holder a
|
|
person can close, the second names missing evidence, and reporting it as held
|
|
would blame a window that may not exist.
|
|
- `-32600` is not a lease refusal. A malformed request gets that code before any
|
|
session method runs, and a lease refusal never uses it.
|
|
- `-32603` means the lease could not be taken for a reason the store cannot
|
|
classify, such as a metadata write that failed.
|
|
|
|
| `data.code` | JSON-RPC | Refusal | What the client does |
|
|
| --- | --- | --- | --- |
|
|
| `busy.session_in_use` | `-32022` | Another runtime owns this session's transcript lease. | Do not bind it elsewhere or retry blindly. Ask the person to close the other Reasonix window or process, then retry once that holder exits. The transcript is intact and nothing already written is lost. |
|
|
| `session.lease_unprovable` | `-32023` | The lease probe could not prove ownership either way: the lock or the lease metadata could not be read. | Treat it as neither held nor free, and do not tell the person a window holds it. Ask them to check the session directory's `.lease.lock` and `.lease.json` entries, then retry once the probe can decide. |
|
|
|
|
`busy.session_in_use` is the HTTP frontend's identity for the same conflict, so one
|
|
vocabulary names it on both surfaces. `session.lease_unprovable` has no HTTP answer
|
|
because no HTTP path asks the three-state probe.
|
|
|
|
ACP has no read-only attach: `session/load` and `session/resume` bind the transcript
|
|
for writing, so a client that cannot close the other holder reports the refusal
|
|
instead of loading.
|
|
|
|
## Session controls
|
|
|
|
Reasonix exposes independent controls instead of combining unrelated choices in
|
|
one mode selector:
|
|
|
|
| Control | Values | Wire surface |
|
|
| --- | --- | --- |
|
|
| Collaboration mode | `normal`, `plan`, `goal` | `modes` and `session/set_mode` |
|
|
| Model | Configured `provider/model` entries | `configOptions` with id `model` |
|
|
| Reasoning effort | Provider-supported levels or `auto` | `configOptions` with id `effort` |
|
|
| Execution setting | `light`, `balanced`, `delivery` | `configOptions` with id `agent_preset` (legacy id `work_mode` still accepted: `economy` → `light`) |
|
|
| Tool approval | `ask`, `auto`, `yolo`; also `readOnly`, `dontAsk` with `--permission-mode` | `configOptions` with id `tool_approval` |
|
|
|
|
Use `session/set_config_option` for model, effort, execution setting, and tool approval.
|
|
Its parameters are `sessionId`, `configId` and `value`, where `configId` is the
|
|
`id` of the option as advertised in `configOptions`:
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"id": 3,
|
|
"method": "session/set_config_option",
|
|
"params": {
|
|
"sessionId": "session-id",
|
|
"configId": "tool_approval",
|
|
"value": "yolo"
|
|
}
|
|
}
|
|
```
|
|
|
|
Note that the field is `configId`, not `optionId`. The result is the full
|
|
refreshed `configOptions` array. An unknown id returns `-32602 InvalidParams`.
|
|
|
|
Model and effort changes rebuild the session controller while preserving its
|
|
history and the other axes. Execution-setting (`agent_preset`) and tool-approval
|
|
changes update the gate in place without rebuilding the controller.
|
|
|
|
For older clients, `session/set_model` remains available. The legacy
|
|
`session/set_mode` values `default` and `auto` are also accepted as Normal + Ask
|
|
and Normal + Yolo respectively; new clients should use the independent
|
|
selectors above.
|
|
|
|
### Fixing the posture: `--permission-mode`
|
|
|
|
`reasonix acp --permission-mode <mode>` starts every session in that tool
|
|
approval posture and makes it the most permissive one a client can select.
|
|
|
|
- The modes are those of `reasonix run`: `read-only`, `dontAsk`, `manual`/`ask`,
|
|
`auto`, `acceptEdits` (ask, with file edits allowed) and `bypassPermissions`.
|
|
`plan` needs an interactive session and is refused.
|
|
- Without the flag nothing changes: sessions start in `ask` and the client may
|
|
pick any of `ask`, `auto`, `yolo`.
|
|
- The order, lowest first, is `readOnly`, `dontAsk`, `ask`, `auto`, `yolo`. A client
|
|
may move down it but not up.
|
|
- `session/set_config_option` and the legacy `session/set_mode` answer a raise with
|
|
`-32602`, `data.code` `acp.tool_approval.above_ceiling` and `data.ceiling`; the
|
|
posture stays as it was.
|
|
- A saved session whose file holds a higher posture opens at the ceiling. The
|
|
file keeps the higher value until the client chooses a posture itself.
|
|
- The flag is read from the command line only, so a project file cannot loosen it.
|
|
- `bypassPermissions` as the ceiling is the same as no ceiling: nothing sits above `yolo`.
|
|
- A posture is not an OS sandbox. The `auto` and `yolo` ceilings do not themselves
|
|
confine shell commands; macOS and Linux rely on the OS sandbox for that.
|
|
- Windows has no OS-level shell sandbox, so a shell command the posture lets through
|
|
can write outside the workspace there. Do not rely on a posture for confinement on Windows.
|
|
|
|
The ceiling limits the posture, not the answers to approval cards. Under `ask`
|
|
the client can still approve a card; a host that must never approve (for example
|
|
DeepSeek Harness with `permission: reject`) is what keeps `ask` from writing.
|
|
|
|
## Prompts, updates, and approvals
|
|
|
|
`session/prompt` accepts text blocks, image blocks, and embedded resources.
|
|
Audio is not advertised.
|
|
|
|
- An image block or a binary (`blob`) resource is saved under
|
|
`.reasonix/attachments/` in the session's workspace and referenced from the
|
|
prompt, the same way a pasted attachment is in the other frontends.
|
|
- Data that is not base64, or an image that is not a supported image, fails
|
|
the prompt with invalid params instead of being dropped.
|
|
|
|
During a turn, Reasonix may send:
|
|
|
|
- agent message and thought chunks;
|
|
- pending and completed tool-call updates;
|
|
- complete plan updates derived from `todo_write`;
|
|
- available slash commands;
|
|
- current-mode and configuration-option updates; and
|
|
- `session/request_permission` requests for permission-gated tools and user
|
|
questions.
|
|
|
|
Hosts should keep the `session/prompt` request open until Reasonix returns its
|
|
stop reason, while continuing to process requests and notifications in both
|
|
directions.
|
|
|
|
- `agent_message_chunk` updates carry an opaque `messageId`.
|
|
- Chunks of one sampling attempt share it; a retried attempt gets a new one,
|
|
so a client never joins a discarded draft to the answer.
|
|
- A chunk outside any attempt carries none.
|
|
- `tool_call` updates and permission requests carry the tool's own `name` beside
|
|
its `title`. The name is informational: it grants nothing and is not changed
|
|
later.
|
|
- `$/cancel_request` with `{"requestId": <id>}` cancels that one in-flight request
|
|
without ending the session.
|
|
- It is answered with the request's own result if it has one, else with error
|
|
`-32800` (Request Cancelled).
|
|
- An id that is unknown or already answered is ignored.
|
|
- `session/cancel` still stops the active turn.
|
|
- An MCP server's form or external-page request is sent as `elicitation/create`
|
|
when the client advertised `clientCapabilities.elicitation.form` or `.url`.
|
|
- A mode counts only when its key is present and not null; otherwise the
|
|
request is declined, as before.
|
|
- A form is never offered for a request that needs a page, and a page is never
|
|
offered to a client without `url`.
|
|
- The `message` begins "MCP server <name> asks:", so the person can tell the
|
|
server's words from the agent's. Control characters are removed and the text
|
|
is capped.
|
|
- The form's `requestedSchema` is a flat object: a string, an enum, or an array
|
|
of an enum per question.
|
|
- A value outside its enum, of the wrong shape or over the cap declines the
|
|
whole form rather than reaching the server.
|
|
- A page is an `http(s)` address the host already validated. The client should
|
|
show it and ask before opening it.
|
|
- `accept` for a page means the person agreed to open it, not that the
|
|
interaction finished.
|
|
|
|
### Vendor notifications are opt-in
|
|
|
|
Reasonix pushes `_reasonix.io/session/status_update` and `_reasonix.io/session/mcpStatus_update`
|
|
only to a client that declared them in its `initialize` request:
|
|
|
|
```json
|
|
{"clientCapabilities": {"_meta": {"reasonix.io": {"statusUpdates": {"supported": true}}}}}
|
|
```
|
|
|
|
- without the declaration, those two notifications are never sent;
|
|
- the matching requests, `_reasonix.io/session/status` and `_reasonix.io/session/mcpStatus`, work either way;
|
|
- the on-disk transcript of a turn is reported as `_meta["reasonix.io"].transcriptPath` in the `session/prompt` result.
|
|
|
|
### When a turn fails
|
|
|
|
A turn that fails after it was admitted ends `session/prompt` with a JSON-RPC
|
|
error, not with a stop reason:
|
|
|
|
- the cases are an unreachable model endpoint or a provider that refuses the request;
|
|
- the [ACP prompt-turn specification](https://agentclientprotocol.com/protocol/prompt-turn)
|
|
allows only `end_turn`, `max_tokens`, `max_turn_requests`, `refusal` and `cancelled` as stop reasons.
|
|
|
|
```json
|
|
{"error": {"code": -32603, "message": "<cause, secrets redacted>", "data": {"code": "turn_failed"}}}
|
|
```
|
|
|
|
`data.code` is the identity a client keys on; `message` is the host's
|
|
description of the cause, clipped to 2048 bytes.
|
|
|
|
### When the host pauses a turn
|
|
|
|
A turn that stops before delivery is verified (for example, files were written and no
|
|
verification command ran) is not a failure and not a completion:
|
|
|
|
- `session/prompt` returns `stopReason: "end_turn"`;
|
|
- the response carries the typed outcome in `_meta["reasonix.io"].turnOutcome`, e.g. `{"kind": "paused", "reason": "…"}`;
|
|
- the streamed text ends with a host-written `[paused] Not verified, …` line, so a client that reads only message text does not take the work for done.
|
|
|
|
## Mid-turn steering extension
|
|
|
|
Reasonix exposes mid-turn guidance as an ACP v1 vendor extension. It is not a
|
|
core ACP method, and it is not the still-unreleased ACP v2 `session/inject`
|
|
proposal.
|
|
|
|
### Discover support
|
|
|
|
Read the method name from:
|
|
|
|
```text
|
|
agentCapabilities._meta["reasonix.io"].sessionSteer.method
|
|
```
|
|
|
|
Do not assume the extension exists, and do not call the unnamespaced
|
|
`session/steer` name. ACP reserves non-underscore method names for the core
|
|
protocol.
|
|
|
|
### Send guidance
|
|
|
|
Call the advertised method while `session/prompt` is active:
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"id": 2,
|
|
"method": "_reasonix.io/session/steer",
|
|
"params": {
|
|
"sessionId": "session-id",
|
|
"prompt": [
|
|
{"type": "text", "text": "use email instead of username"}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
A persistent session returns an item id and disposition:
|
|
|
|
```json
|
|
{"itemId":"inbox-item-id","disposition":"steer_accepted"}
|
|
```
|
|
|
|
Reasonix durably commits the guidance before returning. `steer_accepted` means
|
|
the active turn accepted it; `queued_followup` means that admission lost a race
|
|
or no turn was active, so the same item remains queued for a later turn. A
|
|
pathless compatibility session may omit `itemId` and still returns
|
|
`steer_accepted`. Applied guidance is persisted in normal history; transcript
|
|
replay shows the original user text, not Reasonix's internal steer marker.
|
|
|
|
| Condition | JSON-RPC result |
|
|
| --- | --- |
|
|
| Active prompt accepted durable guidance | `{"itemId":"...","disposition":"steer_accepted"}` |
|
|
| Guidance persisted but active admission was rejected | `{"itemId":"...","disposition":"queued_followup"}` |
|
|
| Unknown session or empty prompt | `-32602 InvalidParams` |
|
|
| Pathless compatibility session has no active prompt | `-32600 InvalidRequest` |
|
|
| Client calls `session/steer` | `-32601 MethodNotFound` |
|
|
|
|
On `InvalidRequest`, the compatibility session did not queue the guidance.
|
|
|
|
## Durable session inbox extension
|
|
|
|
Discover the versioned queue at
|
|
`agentCapabilities._meta["reasonix.io"].sessionInbox`. Schema version 1
|
|
advertises method names in its `methods` map; clients must use those advertised
|
|
names rather than constructing vendor method strings.
|
|
|
|
| Key | Purpose | Main parameters |
|
|
| --- | --- | --- |
|
|
| `enqueue` | Persist a follow-up or steer | `sessionId`, `text`, optional `intent`, `idempotencyKey` |
|
|
| `list` | Read metadata, capacity, pause and recovery state | `sessionId` |
|
|
| `get` | Read one full envelope on demand | `sessionId`, `itemId` |
|
|
| `update` / `delete` | Edit or delete pending work | `sessionId`, `itemId` |
|
|
| `move` | Reorder pending work | `sessionId`, `itemId`, zero-based `toIndex` |
|
|
| `setPaused` | Pause or resume dispatch | `sessionId`, `paused` |
|
|
| `retry` / `refresh` | Retry uncertain work or re-freeze references | `sessionId`, `itemId` |
|
|
|
|
`enqueue` returns `itemId`, `disposition`, `position`, `paused`, and
|
|
`idempotent`. List responses contain previews and byte counts, never prompt
|
|
bodies. A recovered inbox is paused; clients should let users inspect it before
|
|
calling `setPaused` with `false`.
|
|
|
|
### When an inbox call is refused
|
|
|
|
A refused inbox call answers with an implementation-defined JSON-RPC code and a
|
|
`data.code` a client branches on; `message` stays Reasonix's English, which
|
|
clients still show. The identities are the HTTP frontend's, so one vocabulary
|
|
names the queue's conditions on both surfaces.
|
|
|
|
```json
|
|
{"error": {"code": -32020, "message": "inbox item not found", "data": {"code": "inbox.not_found"}}}
|
|
```
|
|
|
|
`-32020` is "the queue refused this call" and `-32021` is "the work may already
|
|
have run". The class is the client's instruction:
|
|
|
|
- **terminal** — the same call cannot start succeeding; repair the client's view
|
|
instead of retrying it;
|
|
- **retry-after-repair** — the same call succeeds once the named condition is
|
|
repaired;
|
|
- **do not retry** — the work may already have run, so retrying could run it
|
|
twice.
|
|
|
|
| `data.code` | JSON-RPC | Class | Refusal |
|
|
| --- | --- | --- | --- |
|
|
| `inbox.not_found` | `-32020` | terminal | No item with this id is in the queue. Read `list` and drop it. |
|
|
| `inbox.invalid_state` | `-32020` | terminal | This item's state does not allow this verb. Read `list` for the state, then pick a verb that fits. |
|
|
| `inbox.retry_may_repeat` | `-32021` | do not retry | Its block code is not `steer_unapplied`, so the turn may already have run. Retrying could run the work twice. |
|
|
| `inbox.item_too_large` | `-32020` | retry-after-repair | The body is over the single-item limit. Send a shorter one. |
|
|
| `inbox.capacity_items` | `-32020` | retry-after-repair | The queue holds its maximum count. Delete pending work, then retry. |
|
|
| `inbox.capacity_bytes` | `-32020` | retry-after-repair | The queue holds its maximum bytes. Delete pending work, then retry. |
|
|
| `inbox.paused` | `-32020` | retry-after-repair | The queue is paused. Resume it where the pause is one a client may lift; a host-set pause is for the person to inspect first, then retry. |
|
|
| `inbox.unrecovered` | `-32020` | retry-after-repair | The queue could not be read, so its contents are unknown. Inspect the queue directory before resuming. |
|
|
| `inbox.closed` | `-32020` | terminal | This queue is closed. Open the session again. |
|
|
| `inbox.schema_readonly` | `-32020` | terminal | A newer Reasonix wrote the queue and this build may read it only. Upgrade to write it. |
|
|
| `inbox.foreign_run` | `-32020` | terminal | Another run owns this item. Change it from the session that owns it. |
|
|
| `inbox.idempotency_conflict` | `-32020` | terminal | The key already belongs to different input. The same body still refuses; new input needs a new key. |
|
|
| `steer.already_applied` | `-32020` | terminal | The steer reached the turn before this call did. Nothing is lost and nothing is left to do. |
|
|
| `inbox.empty` | `-32602` | terminal | The request carries no text. Send a non-empty body. |
|
|
|
|
Three answers are not queue refusals:
|
|
|
|
- `-32602` with no `data.code`: the request cannot address a queue at all — an
|
|
unknown `sessionId`, or params that do not parse;
|
|
- `-32600`: a malformed JSON-RPC frame (no method, or not an object);
|
|
- `-32603`: the queue, or the session's path to it, failed rather than refused.
|
|
|
|
## Goal pause and resume extension
|
|
|
|
Selecting `goal` with `session/set_mode` always drafts a **new** Goal: the next
|
|
prompt becomes its objective and its counters start at zero. To bring back a
|
|
Goal the session already holds, Reasonix advertises two vendor methods as keys
|
|
of `agentCapabilities._meta`, each with `{"schemaVersion": 1}`:
|
|
|
|
| Method | Params | Behavior |
|
|
| --- | --- | --- |
|
|
| `_reasonix.io/session/goal/resume` | `sessionId` | Re-enters a stopped, blocked, paused or cancelled Goal with its objective, counters, todos and delivery checkpoint. It does not start a turn: the next `session/prompt` continues the retained objective. Fails with `-32010` when the session has no resumable Goal (none, complete, or already running). |
|
|
| `_reasonix.io/session/goal/pause` | `sessionId` | Pauses a running Goal so it stops at the next continuation boundary; the turn in flight is left to `session/cancel`. Fails with `-32011` when no Goal is running. |
|
|
|
|
On success both methods:
|
|
|
|
- return `{"goal": …}` in the shape of the `goal` field of `_reasonix.io/session/status`;
|
|
- publish a `_reasonix.io/session/status_update` with event `goal`;
|
|
- send `current_mode_update` when the collaboration mode changes;
|
|
- replace a turn-outcome label the status was showing (`cancelled`, `failed`) with the Goal's own state.
|
|
|
|
The status `goal.status` value `failed` labels the last turn, not the Goal:
|
|
after a turn ends in an error the Goal is still running. A plain
|
|
`session/prompt` continues it, `resume` returns `-32010` because there is
|
|
nothing to resume, and `pause` stops it and reports the paused state.
|
|
|
|
## MCP server health extension
|
|
|
|
Reasonix advertises two versioned methods in `agentCapabilities._meta`:
|
|
`_reasonix.io/session/mcpStatus` and
|
|
`_reasonix.io/session/mcpStatus_update`, each with `{"schemaVersion": 1}`.
|
|
The first is a request with `{"sessionId": "…"}`. It returns a snapshot:
|
|
|
|
```json
|
|
{"schemaVersion":1,"sessionId":"…","servers":[{"name":"tools","status":"ready","tools":3}]}
|
|
```
|
|
|
|
The second is a notification with the same shape:
|
|
|
|
- Reasonix sends an initial snapshot after `session/new`, `session/load`, or
|
|
`session/resume`, then another when a server starts connecting, connects,
|
|
fails, or starts awaiting launch approval.
|
|
- Clients can request a fresh snapshot after reattaching.
|
|
- Status values are `ready`, `connecting`, `failed`, `pending`, `disabled`,
|
|
`standby`, and `idle`. `standby` means cached tools are callable but the
|
|
server has not connected yet. `pending` means project-server authorization
|
|
is required, including after launch approval is revoked; it is not a
|
|
connection failure.
|
|
- A status read does not start a server or probe its endpoint.
|
|
- Failed entries can include a sanitized `error` and an `httpStatus`; both
|
|
fields are omitted when absent.
|
|
|
|
## Runtime reload and extension surface
|
|
|
|
Reasonix advertises two more extension points in
|
|
`agentCapabilities._meta["reasonix.io"]`:
|
|
|
|
- `sessionReloadExtensions` — the vendor method
|
|
`_reasonix.io/session/reloadExtensions`. Calling it reloads the session's
|
|
agent runtime (extensions, tools, skills, commands, hooks, providers) with
|
|
the same fail-atomic semantics as the CLI `/reload` command: while a turn
|
|
or rebuild is active exactly one reload is queued (`{"queued": true}`) and
|
|
runs when the session goes idle; otherwise the runtime is rebuilt and
|
|
swapped atomically, and a failed rebuild keeps the previous runtime. After
|
|
a successful reload Reasonix pushes a fresh `available_commands_update`.
|
|
- `extensionSurface` — structured extension UI support. Clients that also
|
|
advertise `reasonix.io.extensionSurface` in their initialize `_meta`
|
|
receive structured extension surface payloads; clients without it receive
|
|
equivalent text fallbacks (`agent_message_chunk` for cards and statuses,
|
|
permission requests for extension forms), so no client-side handling is
|
|
required to stay compatible.
|
|
|
|
Extension actions declared by installed plugins are exposed as
|
|
`/<plugin>:<action>` in `available_commands_update` and can be invoked like
|
|
any other slash command.
|
|
|
|
## Compatibility and cache behavior
|
|
|
|
| Surface | Older or non-Reasonix clients | Conclusion |
|
|
| --- | --- | --- |
|
|
| Existing ACP v1 methods | Their names and response shapes are unchanged. | Compatible |
|
|
| Capability `_meta` | Unknown metadata may be ignored. | Compatible |
|
|
| Persisted transcripts | Transcript schema is unchanged; the inbox is a versioned sidecar. | Compatible |
|
|
| CLI and Desktop steering | Rejected steers remain durable follow-ups. | Compatible |
|
|
|
|
Steering appends a user-requested message to normal conversation history. It
|
|
does not change the system prompt, tool schemas, tool order, or other stable
|
|
provider-prefix bytes. The next provider request necessarily misses the suffix
|
|
that did not previously exist, just like any normal new user message, while the
|
|
earlier prefix remains reusable.
|
|
|
|
## Client integration checklist
|
|
|
|
1. Launch `reasonix acp` with separate stdin, stdout, and stderr streams.
|
|
2. Call `initialize` and honor both standard and `_meta` capabilities.
|
|
3. Open sessions with absolute workspace paths and keep their ids isolated.
|
|
4. Process agent-to-client filesystem, terminal, and permission requests while
|
|
a prompt is running.
|
|
5. Show steer UI only when the Reasonix capability is advertised and a prompt
|
|
is active.
|
|
6. Branch on the steer `disposition`; both accepted steer and queued follow-up
|
|
are durable, but only the former can affect the active turn.
|
|
7. Use `session/close` for resource cleanup and `session/delete` only when the
|
|
user intends to remove persisted history.
|