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

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-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】.

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-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-v2/docs/ACP.md#L44-L75)【/docs/ACP.md#L68-L73】.

{
  "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-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:

{
  "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-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-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-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-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-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.

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-v2/internal/cli/acp.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-v2/docs/ACP.md) Protocol specification, capability contract, integration checklist
[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-v2/internal/acp/server.go) NDJSON framing, JSON-RPC request/response routing
[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-v2/internal/acp/inbox.go) Durable task queue with dispatcher control
[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-v2/internal/extension/protocol/enums.go) UIHostACP = "acp" transport identifier
[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-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-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-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-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-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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →