Agent Communication Protocol (ACP)¶
This document describes Continuum Router's ACP implementation, which enables the router to operate as an agent backend for IDE clients and coding assistants via a JSON-RPC 2.0 protocol.
Overview¶
ACP (Agent Communication Protocol) allows IDE clients (editors, terminals, coding assistants) to communicate with Continuum Router as an LLM agent over a standard JSON-RPC 2.0 transport. When ACP is enabled, the router functions as both:
- An HTTP API gateway for OpenAI-compatible requests (existing functionality)
- A stdio-based agent backend for IDE integrations via ACP
┌─────────────────────────────┐
│ IDE Client (Editor) │
└──────────────┬──────────────┘
│
JSON-RPC 2.0 (NDJSON)
│
┌──────────────▼──────────────┐
│ Continuum Router (ACP) │
│ ┌────────────────────────┐ │
│ │ AcpMethodRouter │ │
│ │ SessionStore │ │
│ │ PermissionPolicy │ │
│ │ ToolExecutor │ │
│ │ McpConnectionManager │ │
│ └────────────────────────┘ │
└──────────────┬──────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌────────┐ ┌───────────┐
│ OpenAI │ ... │ Anthropic │
└────────┘ └───────────┘
Protocol Stack¶
ACP uses JSON-RPC 2.0 as its wire protocol, communicated over newline-delimited JSON (NDJSON) via stdin/stdout. Each line is a complete JSON-RPC message.
Message Types¶
| Type | Description |
|---|---|
| Request | Method call with id — expects a response |
| Notification | Method call without id — no response expected |
| Response | Result or error in reply to a request |
Standard Error Codes¶
| Code | Meaning |
|---|---|
-32700 | Parse error (malformed JSON) |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid params |
-32603 | Internal error |
-32002 | Server not initialized |
-32001 | Application error (session not found, etc.) |
Protocol Lifecycle¶
1. Initialization Handshake¶
The client must call initialize before any other method. All other methods are rejected with -32002 until initialization completes.
Request:
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"readTextFile": true,
"writeTextFile": true,
"terminal": false
},
"clientInfo": {
"name": "MyEditor",
"version": "1.0.0"
}
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"protocolVersion": 1,
"agentCapabilities": {
"loadSession": true,
"image": false,
"audio": false,
"embeddedContext": false,
"mcp": true,
"mcpCapabilities": { "acp": true }
},
"agentInfo": {
"name": "Continuum Router",
"version": "1.3.0-beta.1"
},
"authMethods": []
},
"id": 1
}
2. Authentication (Optional)¶
Currently a stub that accepts all authentication attempts. Future versions will integrate with the router's API key system.
{
"jsonrpc": "2.0",
"method": "authenticate",
"params": {
"method": "api_key",
"token": "your-api-key"
},
"id": 2
}
3. Session Management¶
Create a Session¶
Response:
{
"jsonrpc": "2.0",
"result": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": "2026-03-08T12:00:00Z",
"updatedAt": "2026-03-08T12:00:00Z",
"mode": "code",
"historyLength": 0
},
"id": 3
}
Load an Existing Session¶
Requires the loadSession agent capability.
{
"jsonrpc": "2.0",
"method": "session/load",
"params": { "sessionId": "550e8400-e29b-41d4-a716-446655440000" },
"id": 4
}
Change Session Mode¶
Response:
Cancel In-Flight Operations¶
Triggers the session's cancellation token. A new token is installed so the session can continue to be used.
4. Prompting¶
Send a prompt to the LLM and receive responses via session/update notifications.
Request:
{
"jsonrpc": "2.0",
"method": "session/prompt",
"params": {
"sessionId": "550e8400-...",
"content": [
{ "type": "text", "text": "Explain the architecture of this project" }
],
"model": "gpt-4o",
"stream": true,
"temperature": 0.7,
"maxTokens": 4096
},
"id": 7
}
Streaming notifications are sent as JSON-RPC notifications (no id):
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "550e8400-...",
"content": [{ "type": "text", "text": "The architecture..." }]
}
}
Final response:
Stop Reasons¶
| Value | Description |
|---|---|
end_turn | Model completed its response naturally |
max_tokens | Response truncated at token limit |
refusal | Model refused to generate a response |
cancelled | Operation cancelled via session/cancel |
Content Block Types¶
| Type | Fields | Description |
|---|---|---|
text | text | Plain text content |
image | data, mediaType | Base64-encoded image data |
resource | uri, mediaType (optional) | External resource reference |
Tool Call Reporting¶
When the LLM generates tool calls, the agent reports them to the client via session/update notifications with tool call status updates.
Tool Kinds¶
| Kind | Description | Default Permission |
|---|---|---|
read | Read file or list directory | Auto-allow |
edit | Write or modify files | Always ask |
delete | Delete files or directories | Always ask |
move | Rename or move files | Always ask |
search | Search file contents | Auto-allow |
execute | Execute shell commands | Always ask |
think | Internal reasoning | Auto-allow |
fetch | HTTP requests | Ask |
other | Uncategorized operations | Ask |
Tool Call Lifecycle¶
┌─────────┐
│ Pending │
└────┬────┘
│
classify + permission check
│
┌────▼────┐
┌─────────│ Ask │─────────┐
│ └─────────┘ │
allow/always reject/always
│ │
┌────▼──────┐ ┌──────▼──┐
│InProgress │ │ Failed │
└────┬──────┘ └─────────┘
│
execute tool
│
┌────▼──────┐
│ Completed │
└───────────┘
Permission Delegation¶
The permission system allows clients to control which tool operations are allowed:
Permission request notification:
{
"jsonrpc": "2.0",
"method": "session/request_permission",
"params": {
"sessionId": "...",
"toolCallId": "tc-1",
"title": "Edit File",
"kind": "edit",
"locations": [{ "uri": "file:///src/main.rs", "startLine": 42 }]
}
}
Client response options:
allow_once— Allow this specific operationallow_always— Allow all operations of this kind for the sessionreject_once— Reject this specific operationreject_always— Reject all operations of this kind for the session
Cached always_* decisions persist for the duration of the session.
MCP-over-ACP Bridge¶
The MCP bridge allows tunneling existing MCP (Model Context Protocol) servers through the ACP channel. This enables IDE clients to access MCP servers without managing separate connections.
Connect to an MCP Server¶
Response:
Route Messages¶
{
"jsonrpc": "2.0",
"method": "mcp/message",
"params": {
"connectionId": "uuid-...",
"message": {
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
},
"id": 11
}
Disconnect¶
{
"jsonrpc": "2.0",
"method": "mcp/disconnect",
"params": { "connectionId": "uuid-..." },
"id": 12
}
Server ID Validation Rules¶
- Maximum 128 characters
- ASCII alphanumeric, hyphens, underscores, and dots only
- No double underscores (reserved for tool namespacing)
- Must start and end with an alphanumeric character
Process Lifecycle¶
mcp/connectspawns the MCP server process with stdio pipes- Background tasks bridge stdin/stdout bidirectionally (64-message buffer)
- stderr is forwarded to the router's
tracing::warnlogs mcp/disconnectsends SIGTERM, waits 5 seconds, then SIGKILL if needed
Capability Negotiation¶
During initialization, the client and agent exchange capability declarations. The negotiated result determines which features are available for the session.
Client Capabilities¶
| Capability | Description |
|---|---|
readTextFile | Client supports reading files from the local filesystem |
writeTextFile | Client supports writing files to the local filesystem |
terminal | Client supports executing terminal commands |
Agent Capabilities¶
| Capability | Description |
|---|---|
loadSession | Agent supports resuming previous sessions |
image | Agent supports image content blocks |
audio | Agent supports audio content blocks |
embeddedContext | Agent supports embedded context blocks |
mcp | Agent supports MCP-over-ACP bridge |
Agent Registry Metadata¶
The router generates an agent.json metadata document for agent registry discovery, accessible via GET /admin/acp/agent.json.
Example output:
{
"name": "continuum-router",
"displayName": "Continuum Router",
"version": "1.3.0-beta.1",
"description": "Local LLM inference agent with multi-backend routing",
"transport": [
{ "type": "stdio" }
],
"capabilities": {
"loadSession": true,
"mcp": true
}
}
Admin API Endpoints¶
| Endpoint | Description |
|---|---|
GET /admin/acp/status | ACP server status (enabled, transport, capabilities) |
GET /admin/acp/sessions | List active ACP sessions |
GET /admin/acp/agent.json | Dynamic agent registry metadata |
Configuration¶
ACP is disabled by default for backward compatibility. Add the following to your config.yaml to enable it:
acp:
enabled: true
transport:
stdio:
enabled: true
agent:
name: "Continuum Router"
version: "1.0.0"
description: "Local LLM inference agent"
capabilities:
load_session: true
image: false
audio: false
embedded_context: false
mcp: true
# Default model for ACP sessions (optional)
default_model: "gpt-4o"
# System prompt injection (optional)
system_prompt: "You are a helpful coding assistant."
# Enable coding agent mode
coding_agent_mode: true
permissions:
# Default policy: ask_always, allow_read, or allow_all
default_policy: ask_always
auto_allow:
- read
- search
- think
always_ask:
- edit
- delete
- execute
sessions:
max_concurrent: 10
idle_timeout: "1h"
storage: "memory"
mcp:
max_connections_per_session: 5
allowed_servers: [] # Empty = all configured servers allowed
server_spawn_timeout: "10s"
Configuration Reference¶
Top-Level Options¶
| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable/disable the ACP subsystem |
default_model | string | none | Override model selection for ACP sessions |
system_prompt | string | none | Inject a system prompt into all ACP requests |
coding_agent_mode | bool | false | Enable coding agent system prompt |
Transport Options¶
| Option | Type | Default | Description |
|---|---|---|---|
transport.stdio.enabled | bool | true | Enable stdio transport |
Permission Options¶
| Option | Type | Default | Description |
|---|---|---|---|
permissions.default_policy | enum | ask_always | Default policy: ask_always, allow_read, allow_all |
permissions.auto_allow | list | [read, search, think] | Tool kinds auto-allowed without asking |
permissions.always_ask | list | [edit, delete, execute] | Tool kinds that always require permission |
Session Options¶
| Option | Type | Default | Description |
|---|---|---|---|
sessions.max_concurrent | int | 10 | Maximum concurrent sessions |
sessions.idle_timeout | string | "1h" | Idle timeout before cleanup |
sessions.storage | string | "memory" | Storage backend: memory or file |
sessions.storage_path | string | none | Path for file-based storage |
MCP Bridge Options¶
| Option | Type | Default | Description |
|---|---|---|---|
mcp.max_connections_per_session | int | 5 | Max MCP connections per session |
mcp.allowed_servers | list | [] | Allowed server IDs (empty = all) |
mcp.server_spawn_timeout | string | "10s" | Timeout for spawning MCP server processes |
Module Structure¶
src/acp/
├── mod.rs # Module root
├── jsonrpc.rs # JSON-RPC 2.0 protocol types
├── types.rs # ACP content blocks and messages
├── error.rs # Error types
├── capabilities.rs # Capability negotiation
├── config.rs # Configuration structures
├── config_validate.rs # Configuration validation
├── metadata.rs # Agent registry metadata (agent.json)
├── transport/
│ ├── mod.rs # AcpTransport trait
│ └── stdio.rs # NDJSON over stdin/stdout
├── session.rs # Session lifecycle and storage
├── router.rs # Method dispatch router
├── handlers/
│ ├── initialize.rs # Protocol initialization
│ ├── authenticate.rs # Authentication (stub)
│ ├── session.rs # session/* methods
│ ├── prompt.rs # session/prompt handler
│ ├── prompt_stream.rs # SSE streaming processor
│ └── translate.rs # ACP → OpenAI format translation
├── tools/
│ ├── mod.rs # Tool call types
│ ├── classifier.rs # Tool kind classification
│ ├── permission.rs # Permission policy engine
│ ├── executor.rs # Tool execution orchestrator
│ └── client_methods.rs # Client-side methods (fs/*, terminal/*)
├── mcp_bridge/
│ ├── mod.rs # MCP capabilities
│ ├── handlers.rs # connect/message/disconnect handlers
│ ├── connection.rs # Connection manager
│ ├── process.rs # MCP server process spawning
│ └── schema_translate.rs # MCP ↔ ACP schema conversion
└── admin.rs # Admin API endpoints