# Agent Code Protocol (ACP) in Reasonix: A Complete Guide to Editor Integration

> Integrate editors with Reasonix using Agent Code Protocol ACP. Learn how Reasonix uses JSON-RPC 2.0 for sessions, prompts, and mid-turn steering.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: deep-dive
- Published: 2026-08-11

---

**Reasonix implements ACP v1 using JSON-RPC 2.0 over stdin/stdout with NDJSON framing, enabling any editor to launch sessions, execute prompts, and apply mid-turn steering through vendor-specific extensions.**

The **Agent Code Protocol (ACP)** is the standardized interface that allows editors like VS Code and VSCodium to communicate with AI coding agents. In the **esengine/DeepSeek-Reasonix** repository, ACP is implemented as a first-class integration surface that exposes the full Reasonix engine—including session management, file operations, and real-time steering—to external clients. This article explains the complete architecture, from process launch to advanced vendor extensions.

## Launching the ACP Agent in Reasonix

The entry point for ACP integration is the **`reasonix acp`** command, defined in [[`internal/cli/acp.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/cli/acp.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/cli/acp.go). This command starts the ACP server with stdio as its transport layer, ensuring **stdout** remains a pure message stream while **stderr** carries diagnostics【/internal/cli/acp.go#L28-L38】.

```bash
reasonix acp                    # default model and preset

reasonix acp --model deepseek-pro
reasonix acp --preset delivery

```

The CLI parses flags, validates configuration, and delegates to the server implementation in [[`internal/acp/server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/server.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/server.go). This server runs a JSON-RPC loop that reads NDJSON from stdin and writes responses to stdout, making it compatible with any client that can spawn a subprocess and pipe stdio.

## ACP Handshake and Capability Negotiation

Before any session operations, the client must complete an **initialize** handshake. Reasonix responds with a capability shape that declares both core ACP features and vendor-specific extensions. The structure is documented in [[`docs/ACP.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/ACP.md)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/ACP.md#L44-L75)【/docs/ACP.md#L68-L73】.

```json
{
  "protocolVersion": 1,
  "agentCapabilities": {
    "loadSession": true,
    "sessionCapabilities": { "list": {}, "resume": {}, "delete": {} },
    "promptCapabilities": { "embeddedContext": true },
    "mcpCapabilities": { "http": true },
    "_meta": {
      "reasonix.io": {
        "sessionSteer": { "method": "_reasonix.io/session/steer" },
        "sessionInbox": { "version": 1 }
      }
    }
  }
}

```

Key capability categories include:

- **sessionCapabilities** — Session listing, resumption, and persistence
- **promptCapabilities** — Context embedding and streaming response formats
- **mcpCapabilities** — Model Context Protocol transport options
- **_meta.reasonix.io** — Vendor extensions for steering, inbox, and UI surfaces

If the client advertises methods like `fs.readTextFile` or terminal forwarding, Reasonix routes these through the editor's buffers rather than local filesystem access, keeping editor state synchronized with agent operations【/docs/ACP.md#L78-L86】.

## ACP Session Lifecycle Methods

Each ACP session maintains isolated state including its controller, workspace root, model configuration, preset, and transcript history. The session API mirrors Reasonix's core controller but is scoped to the ACP surface. All methods are defined in [[`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/protocol.go)【/internal/acp/protocol.go#L1-L30】.

| Method | Purpose |
|--------|---------|
| `session/new` | Create fresh session with absolute `cwd` path |
| `session/load` | Replay persisted transcript from disk |
| `session/resume` | Restore session without replaying history |
| `session/prompt` | Execute one turn with streaming updates |
| `session/cancel` | Abort active turn (non-blocking) |
| `session/list` | Enumerate active and persisted sessions |
| `session/close` | Gracefully terminate session |
| `session/delete` | Permanently remove persisted session |

The `session/prompt` method is the primary interaction surface. It accepts a prompt array ( multimodal messages) and streams progress notifications until reaching a stop reason. The streaming protocol uses JSON-RPC notifications with method names like `$/progress` and `$/promptUpdate`.

## Mid-Turn Steering: The _reasonix.io/session/steer Extension

**Mid-turn steering** is Reasonix's signature ACP extension, allowing editors to inject guidance while a `session/prompt` is actively streaming. This capability is advertised in the `_meta` block and must be discovered by clients—it does not exist in the core ACP specification【/docs/ACP.md#L77-L85】.

The steering method name is **`_reasonix.io/session/steer`**, and clients should verify its presence in the initialize response before presenting steering UI to users.

**Steering request format:**

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "_reasonix.io/session/steer",
  "params": {
    "sessionId": "abc-123",
    "prompt": [{ "type": "text", "text": "use email instead of username" }]
  }
}

```

**Possible responses:**

- `steer_accepted` — Guidance applied to active turn, visible in subsequent output
- `queued_followup` — Guidance stored for next turn, current turn unaffected

The response structure is documented in [[`docs/ACP.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/ACP.md)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/ACP.md#L90-L108). Clients must respect these dispositions and not assume steering always succeeds.

## Durable Inbox and Additional Extensions

Beyond steering, Reasonix exposes three additional vendor extensions under `agentCapabilities._meta["reasonix.io"]`:

| Extension | Method | Purpose |
|-----------|--------|---------|
| **sessionInbox** | `_reasonix.io/session/inbox/*` | Queue follow-up tasks with CRUD operations and dispatcher pause/resume |
| **reloadExtensions** | `_reasonix.io/session/reloadExtensions` | Atomic rebuild of runtime (tools, plugins, providers) between turns |
| **extensionSurface** | (advertised capability) | Receive structured UI payloads (cards, forms) when client supports it |

The **inbox** implementation resides in [[`internal/acp/inbox.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/inbox.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/inbox.go), providing versioned queue semantics for complex multi-step workflows【/internal/acp/inbox.go#L1-L20】. Extension data structures are defined in [[`internal/acp/extension_models.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/extension_models.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/extension_models.go).

The transport layer recognizes ACP through the `UIHostACP` enum (`"acp"`), defined in [[`internal/extension/protocol/enums.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/extension/protocol/enums.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/extension/protocol/enums.go) and exposed in the Go SDK at [[`sdk/go/types_generated.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/sdk/go/types_generated.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/sdk/go/types_generated.go#L193).

## Complete ACP Client Implementation (Node.js)

This minimal client demonstrates the full lifecycle: launch, handshake, session creation, prompting, and steering.

```js
const { spawn } = require('child_process');
const readline = require('readline');

// 1. Spawn Reasonix ACP with isolated stdio
const proc = spawn('reasonix', ['acp', '--model', 'deepseek-pro'], {
  stdio: ['pipe', 'pipe', 'inherit']  // stdin, stdout, stderr
});

let msgId = 0;
const pending = new Map();
let sessionId = null;
let canSteer = false;

// 2. NDJSON writer
function send(method, params) {
  const id = ++msgId;
  const msg = JSON.stringify({ jsonrpc: '2.0', id, method, params });
  proc.stdin.write(msg + '\n');
  pending.set(id, { method, startTime: Date.now() });
  return id;
}

// 3. NDJSON reader
const rl = readline.createInterface({ input: proc.stdout });
rl.on('line', line => {
  const msg = JSON.parse(line);
  
  if (msg.id !== undefined && pending.has(msg.id)) {
    handleResponse(msg, pending.get(msg.id).method);
    pending.delete(msg.id);
  } else if (msg.method?.startsWith('$/')) {
    handleNotification(msg);
  } else if (msg.method) {
    // Client-to-server request (rare in ACP)
    handleRequest(msg);
  }
});

function handleResponse(msg, method) {
  console.log(`← ${method} result:`, msg.result ? 'success' : 'error');
  
  if (method === 'initialize') {
    // 4. Extract steering capability
    const meta = msg.result?.agentCapabilities?._meta?.['reasonix.io'];
    canSteer = !!meta?.sessionSteer?.method;
    console.log(`Steering available: ${canSteer}`);
    
    // 5. Create session with absolute path
    send('session/new', { cwd: process.cwd() });
    
  } else if (method === 'session/new') {
    sessionId = msg.result?.sessionId;
    console.log(`Session created: ${sessionId}`);
    
    // 6. Execute first prompt
    send('session/prompt', {
      sessionId,
      prompt: [{ type: 'text', text: 'Explain how ACP enables editor integration' }]
    });
    
  } else if (method === '_reasonix.io/session/steer') {
    console.log(`Steer result: ${msg.result?.disposition}`);
  }
}

function handleNotification(msg) {
  if (msg.method === '$/promptUpdate') {
    const { content, stopReason } = msg.params;
    process.stdout.write(content || '');
    if (stopReason) {
      console.log(`\n[turn complete: ${stopReason}]`);
    }
  }
}

// (Optional) Mid-turn steering trigger
function injectSteer(text) {
  if (!canSteer || !sessionId) return;
  send('_reasonix.io/session/steer', {
    sessionId,
    prompt: [{ type: 'text', text }]
  });
}

// Example: steer after 5 seconds if still running
setTimeout(() => injectSteer('focus on the JSON-RPC transport layer'), 5000);

```

## Using the Native Go SDK for ACP

Reasonix includes a Go SDK that wraps the JSON-RPC protocol. This matches the implementation in [[`internal/cli/acp.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/cli/acp.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/cli/acp.go).

```go
package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/esengine/DeepSeek-Reasonix/internal/acp"
)

func main() {
    ctx := context.Background()
    
    // 1. Create factory (matches CLI initialization)
    factory := acp.NewFactory(acp.FactoryOptions{
        DefaultModel:  "deepseek-pro",
        DefaultPreset: "delivery",
    })
    
    // 2. Initialize session
    sess, err := factory.NewSession(ctx, acp.SessionParams{
        Cwd:  "/absolute/path/to/workspace",
        Sink: acp.NewStreamingSink(func(evt acp.Event) {
            fmt.Printf("[stream] %s: %s\n", evt.Kind, evt.Data)
        }),
    })
    if err != nil {
        log.Fatal(err)
    }
    defer sess.Close()
    
    // 3. Execute prompt with streaming
    resp, err := sess.Prompt(ctx, acp.PromptParams{
        Prompt: []acp.Message{
            {Type: "text", Text: "How does ACP steering work?"},
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Turn completed: %s\n", resp.StopReason)
    
    // 4. Conditional steering (check capability first)
    caps := sess.AgentCapabilities()
    if steerMethod, ok := caps.Extensions["reasonix.io/session/steer"]; ok {
        steerResp, err := sess.CallMethod(ctx, steerMethod, map[string]any{
            "prompt": []map[string]string{
                {"type": "text", "text": "add a code example"},
            },
        })
        fmt.Printf("Steer response: %+v\n", steerResp)
    }
}

```

## Key Source Files for ACP Implementation

| Path | Role in ACP Architecture |
|------|-------------------------|
| [[`docs/ACP.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/ACP.md)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/ACP.md) | Protocol specification, capability contract, integration checklist |
| [[`internal/cli/acp.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/cli/acp.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/cli/acp.go) | CLI entry point: flag parsing, config binding, server launch |
| [[`internal/acp/server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/server.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/server.go) | NDJSON framing, JSON-RPC request/response routing |
| [[`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/protocol.go) | Core method definitions: session lifecycle, prompt execution |
| [[`internal/acp/inbox.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/inbox.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/inbox.go) | Durable task queue with dispatcher control |
| [[`internal/acp/extension_models.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/extension_models.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/extension_models.go) | `_meta` extension structures (steer, inbox, reload) |
| [[`internal/extension/protocol/enums.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/extension/protocol/enums.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/extension/protocol/enums.go) | `UIHostACP = "acp"` transport identifier |
| [[`internal/cli/acp_test.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/cli/acp_test.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/cli/acp_test.go) | Integration tests for command, session, config handling |
| [[`sdk/go/types_generated.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/sdk/go/types_generated.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/sdk/go/types_generated.go#L193) | Public SDK exposure of ACP types |

## Integration Checklist for Editor Developers

Based on the specification in [[`docs/ACP.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/ACP.md)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/ACP.md#L89-L101), clients should verify:

1. **Launch isolation** — Spawn `reasonix acp` with dedicated stdio pipes, never sharing stdin/stdout with other processes
2. **Capability discovery** — Parse both core and `_meta` blocks; do not assume extensions exist
3. **Absolute paths** — Always provide absolute `cwd` in `session/new` to avoid resolution ambiguity
4. **Request servicing** — Handle `fs.*` and `terminal.*` requests promptly while prompts run
5. **Steering UI gating** — Show steering controls only when `sessionSteer` method is advertised AND a prompt is active
6. **Disposition handling** — Distinguish `steer_accepted` (immediate effect) from `queued_followup` (deferred)
7. **Cleanup protocol** — Use `session/close` for temporary sessions, `session/delete` for permanent removal

## Summary

- **ACP v1 in Reasonix** uses **JSON-RPC 2.0 over NDJSON** on stdio, with stderr reserved for diagnostics
- **Capability negotiation** via `initialize` exposes core features and vendor extensions in `_meta.reasonix.io`
- **Session lifecycle** methods in [[`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/acp/protocol.go) provide full CRUD operations with persistence
- **Mid-turn steering** (`_reasonix.io/session/steer`) allows real-time guidance injection with `steer_accepted` or `queued_followup` dispositions
- **Extended surface** includes inbox queue, runtime reload, and structured UI payloads for capable clients
- **Editor integration** requires careful stdio isolation, absolute paths, and dynamic capability checking

## Frequently Asked Questions

### What transport does Reasonix ACP use?

Reasonix ACP uses **stdio-based NDJSON transport**: the client spawns `reasonix acp` as a subprocess, writes JSON-RPC requests as newline-delimited JSON to stdin, and reads responses from stdout. All diagnostic output goes to stderr, keeping the message stream clean. This design is defined in [[`internal/cli/acp.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/cli/acp.go)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/cli/acp.go#L28-L38) and matches the Agent Client Protocol specification.

### How do I discover if steering is available?

Inspect the **`_meta["reasonix.io"].sessionSteer`** object in the `initialize` response. If present, the `method` field contains the exact string to use (typically `_reasonix.io/session/steer`). Do not attempt steering if this capability is absent—there is no fallback in the core protocol. This discovery pattern is documented in [[`docs/ACP.md`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/docs/ACP.md)](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/ACP.md#L68-L73).

### Can I use ACP with my own editor, or only VS Code?

Any editor or tool that can spawn subprocesses and handle JSON-RPC over stdio can implement ACP. The protocol is editor-agnostic. The esengine/DeepSeek-Reasonix repository includes test clients and the Go SDK demonstrates programmatic usage without any VS Code-specific dependencies.

### What happens to steering commands if the turn completes first?

The server returns **`queued_followup`** in the steer response, indicating the guidance was stored for the next turn rather than applied to the now-completed turn. Clients should surface this distinction to users so they understand whether their intervention affected the current output or will appear in the next exchange.