# DeepSeek-Reasonix ACP Protocol: Implementation and Communication Guide

> Implement the ACP protocol for DeepSeek-Reasonix. Understand JSON-RPC 2.0 communication via NDJSON, covering session management, tool calling, and real-time streaming.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: api-reference
- Published: 2026-06-24

---

**The Agent Client Protocol (ACP) is a lightweight JSON-RPC 2.0 protocol that enables bidirectional communication between DeepSeek-Reasonix and host applications via NDJSON over standard input/output, supporting session management, tool calling, and real-time streaming.**

The DeepSeek-Reasonix repository implements the Agent Client Protocol (ACP) to provide a language-agnostic interface for LLM-powered agents. This protocol allows editors, IDEs, and automation tools to spawn the Reasonix agent as a subprocess and control it through structured JSON messages. All protocol definitions and communication handlers reside in the `internal/acp` package according to the source code.

## What Is the ACP Protocol?

ACP stands for **Agent Client Protocol**, a specification that defines how a host process interacts with an LLM-powered agent over standard input/output streams. The protocol is defined in [`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go) and represents a faithful port of the original TypeScript implementation used by the v1 agent.

### Core Design and Transport

The protocol uses **NDJSON** (Newline-Delimited JSON) over stdio, where each line represents a single JSON-RPC 2.0 message. This design makes it easy to pipe communication through subprocess boundaries and language runtimes.

Key characteristics include:

- **Transport**: Stdio JSON-RPC with line framing via `io.Reader`/`io.Writer`
- **Message Format**: One JSON object per line (NDJSON)
- **Size Limits**: 32 MiB maximum message size enforced by the `Conn` implementation
- **Capabilities**: Load persisted sessions, list/resume/close/delete sessions, embed inline text resources, and stream MCP (Model-Control-Protocol) over HTTP

### Protocol Version and Error Handling

The current implementation uses `ProtocolVersion = 1`, defined as a constant in [`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go) at line 23. Error handling follows the JSON-RPC 2.0 specification with a specific subset of error codes:

- `-32700` (Parse error)
- `-32600` (Invalid Request)
- `-32601` (Method not found)
- `-32602` (Invalid params)
- `-32603` (Internal error)

These codes are defined in [`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go) lines 26-33.

## How DeepSeek-Reasonix Communicates with ACP

The agent-side implementation lives in the `internal/acp` package and is orchestrated by `service.Serve`. The communication flow involves three architectural layers: the transport `Conn`, the RPC `service`, and the event `updateSink`.

### The Connection Layer (Conn)

The `Conn` type in [`internal/acp/server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/server.go) handles low-level NDJSON framing, request-response correlation, and thread-safe writes. It provides the foundational methods for all ACP communication:

```go
conn := acp.NewConn(os.Stdin, os.Stdout)
result, err := conn.Request(ctx, "session/list", acp.SessionListParams{})

```

The `Conn` implementation guarantees correct JSON-RPC framing and uses a mutex-protected `write` method to ensure thread-safe operations when multiple goroutines access the transport.

### RPC Handlers and Service Layer

The `service` struct in [`internal/acp/service.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/service.go) registers RPC method handlers during initialization. The `Serve` method (lines 94-101) sets up the following endpoints:

- `initialize` – Handshake and capability negotiation
- `authenticate` – Authentication flows
- `session/new` – Create new sessions
- `session/prompt` – Send prompts and receive responses
- `session/update` – Notification channel for streaming events

The initialization handshake occurs when the client sends `InitializeParams` and the agent replies with `InitializeResult` containing `AgentCapabilities` and supported auth methods. This implementation is found in `svc.initialize` at lines 40-61 of [`service.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/service.go).

### Session Lifecycle Management

Sessions are created via the `session/new` RPC method, implemented in `svc.sessionNew` (lines 84-111 of [`service.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/service.go)). The process follows this sequence:

1. Generate a UUID using `newSessionID`
2. Create an `updateSink` (event-to-ACP adapter)
3. Instantiate a per-session `control.Controller` via the injected `Factory`
4. Return `SessionNewResult` containing the session ID, model list, and config options

Configuration changes are handled by `session/set_config_option` or the legacy `session/set_model`, which rebuild the controller on-the-fly and persist metadata to an ACP sidecar JSON file (`*.acp.json`).

### Event Streaming and Notifications

When the agent generates events (thoughts, tool calls, or completions), the `updateSink` in [`internal/acp/dispatch.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/dispatch.go) bridges internal events to ACP notifications. The sink emits `session/update` notifications for:

- **Tool calls**: When the agent invokes a tool, the sink emits a `tool_call` notification
- **Permission requests**: For gated tools, `updateSink.requestPermission` (lines 21-46 of [`dispatch.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/dispatch.go)) forwards a `session/request_permission` request and awaits a `PermissionRequestResult`
- **Session state**: Real-time updates on turn completion, errors, or cancellation

The prompting flow in `svc.sessionPrompt` (lines 14-70 of [`service.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/service.go)) flattens prompts using `FlattenPrompt`, resolves slash-commands, runs the turn on the controller, and streams events back via the `updateSink` before returning a `SessionPromptResult` indicating the stop reason (`StopEndTurn`, `StopCancelled`, or `StopError`).

## Implementation Walkthrough

Below is a minimal Go client that demonstrates communicating with a running Reasonix ACP process. This example shows the handshake, session creation, prompting, and graceful shutdown:

```go
package main

import (
	"context"
	"encoding/json"
	"log"
	"os"
	"os/exec"
	"time"

	"reasonix/internal/acp"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Start the agent as a subprocess
	cmd := exec.Command("reasonix", "acp")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		log.Fatalf("start agent: %v", err)
	}
	defer cmd.Process.Kill()

	// Wire the stdio JSON-RPC connection
	conn := acp.NewConn(os.Stdin, os.Stdout)

	// 1. Initialize
	var initRes acp.InitializeResult
	if raw, err := conn.Request(ctx, "initialize", acp.InitializeParams{
		ProtocolVersion: acp.ProtocolVersion,
	}); err != nil {
		log.Fatalf("init: %v", err)
	} else if err := json.Unmarshal(raw, &initRes); err != nil {
		log.Fatalf("decode init: %v", err)
	}
	log.Printf("Agent: %s %s", initRes.AgentInfo.Name, initRes.AgentInfo.Version)

	// 2. Open a new session
	var newRes acp.SessionNewResult
	if raw, err := conn.Request(ctx, "session/new", acp.SessionNewParams{}); err != nil {
		log.Fatalf("session/new: %v", err)
	} else if err := json.Unmarshal(raw, &newRes); err != nil {
		log.Fatalf("decode new: %v", err)
	}
	sid := newRes.SessionID
	log.Printf("Created session %s", sid)

	// 3. Send a prompt
	prompt := []acp.ContentBlock{{
		Type: "text", 
		Text: "Explain the difference between recursion and iteration.",
	}}
	if raw, err := conn.Request(ctx, "session/prompt", acp.SessionPromptParams{
		SessionID: sid,
		Prompt:    prompt,
	}); err != nil {
		log.Fatalf("prompt: %v", err)
	} else {
		var resp acp.SessionPromptResult
		_ = json.Unmarshal(raw, &resp)
		log.Printf("Turn finished: %s", resp.StopReason)
	}

	// 4. Close the session
	if _, err := conn.Request(ctx, "session/close", 
		acp.SessionCloseParams{SessionID: sid}); err != nil {
		log.Fatalf("close: %v", err)
	}
}

```

This snippet uses only public types from [`protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/protocol.go), while the underlying connection logic resides in [`server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/server.go).

## Key Source Files

- **[`internal/acp/protocol.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/protocol.go)**: Defines every wire type including `InitializeResult`, `ContentBlock`, `SessionUpdateParams`, and error codes.
- **[`internal/acp/server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/server.go)**: Implements the NDJSON JSON-RPC connection (`Conn`, `Request`, `Notify`, and framing logic).
- **[`internal/acp/service.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/service.go)**: High-level RPC handlers including `sessionNew`, `sessionPrompt`, and configuration management.
- **[`internal/acp/dispatch.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/dispatch.go)**: Bridges the agent's internal event stream to ACP notifications via `updateSink`.
- **[`internal/acp/live_test.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/live_test.go)**: End-to-end test suite demonstrating full ACP session lifecycle.

## Summary

- **ACP** is a JSON-RPC 2.0 protocol using NDJSON over stdio, designed for low-latency, language-agnostic interaction with the Reasonix agent.
- The **communication stack** consists of three layers: `Conn` for transport, `service` for RPC handlers, and `updateSink` for event streaming.
- **Session management** uses UUIDs with persisted state stored in `*.acp.json` sidecar files, supporting resume, close, and delete operations.
- **Tool permission** flows use bidirectional requests where the client must approve gated operations before execution continues.
- All message handling preserves thread safety through mutex-protected writes and supports messages up to 32 MiB in size.

## Frequently Asked Questions

### What transport mechanism does the ACP protocol use?

The ACP protocol uses **standard input/output (stdio)** with NDJSON (Newline-Delimited JSON) formatting. The `Conn` type in [`internal/acp/server.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/server.go) wraps `io.Reader` and `io.Writer` interfaces to handle line framing, enabling the agent to run as a subprocess that communicates via pipes.

### How does DeepSeek-Reasonix handle session persistence?

Sessions persist state to the filesystem using **sidecar JSON files** (`*.acp.json`). When configuration changes occur via `session/set_config_option`, the service rebuilds the controller and writes metadata to these sidecar files. Sessions can be listed, resumed, or deleted using the `session/list`, `session/resume`, and `session/delete` RPC methods.

### What is the maximum message size supported by ACP?

The protocol enforces a **32 MiB** size limit per message. The `Conn` implementation validates message size during framing to prevent memory exhaustion when handling large text resources or MCP streams.

### How does the protocol handle tool permission requests?

When the agent encounters a gated tool, the `updateSink.requestPermission` method in [`internal/acp/dispatch.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/acp/dispatch.go) sends a `session/request_permission` request to the client and blocks until receiving a `PermissionRequestResult`. This synchronous round-trip ensures the host application can prompt users for approval before sensitive operations execute.