Skip to content

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

{
  "jsonrpc": "2.0",
  "method": "session/new",
  "params": { "mode": "code" },
  "id": 3
}

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

{
  "jsonrpc": "2.0",
  "method": "session/set_mode",
  "params": { "mode": "architect" },
  "id": 5
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "mode": "architect",
    "previousMode": "code"
  },
  "id": 5
}

Cancel In-Flight Operations

Triggers the session's cancellation token. A new token is installed so the session can continue to be used.

{
  "jsonrpc": "2.0",
  "method": "session/cancel",
  "id": 6
}

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:

{
  "jsonrpc": "2.0",
  "result": { "stopReason": "end_turn" },
  "id": 7
}

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 operation
  • allow_always — Allow all operations of this kind for the session
  • reject_once — Reject this specific operation
  • reject_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.

IDE Client ←→ [ACP stdio] ←→ Continuum Router ←→ [MCP stdio] ←→ MCP Server

Connect to an MCP Server

{
  "jsonrpc": "2.0",
  "method": "mcp/connect",
  "params": { "id": "filesystem" },
  "id": 10
}

Response:

{
  "jsonrpc": "2.0",
  "result": { "connectionId": "uuid-..." },
  "id": 10
}

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

  1. mcp/connect spawns the MCP server process with stdio pipes
  2. Background tasks bridge stdin/stdout bidirectionally (64-message buffer)
  3. stderr is forwarded to the router's tracing::warn logs
  4. mcp/disconnect sends 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