ACP Usage Guide¶
This guide covers how to use Continuum Router as an ACP (Agent Communication Protocol) agent — from configuration to connecting IDE clients.
Prerequisites¶
- Continuum Router binary (see Installation)
- At least one configured LLM backend (OpenAI, Anthropic, Ollama, etc.)
- A configuration file with ACP enabled
Configuration¶
Add the acp section to your config.yaml:
backends:
- url: http://localhost:11434
name: ollama
models: ["llama3.2"]
acp:
enabled: true
transport:
stdio:
enabled: true
capabilities:
load_session: true
mcp: true
default_model: "llama3.2"
permissions:
default_policy: ask_always
auto_allow:
- read
- search
- think
always_ask:
- edit
- delete
- execute
sessions:
max_concurrent: 10
idle_timeout: "1h"
storage: memory
For a complete configuration reference, see the ACP Architecture page.
Starting the Router in ACP Mode¶
Launch the router with --mode stdio to enable ACP transport:
In this mode:
- stdin/stdout are reserved for JSON-RPC 2.0 messages (NDJSON format)
- stderr receives log output (tracing)
- No HTTP listeners are started — the router communicates exclusively via stdio
Tip
You can still run the HTTP API gateway separately. ACP stdio mode is a different execution mode, not an add-on to HTTP mode.
Manual Testing¶
You can test ACP communication by piping JSON-RPC messages through stdin.
Step 1: Initialize the Protocol¶
Every ACP session must begin with an initialize handshake:
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"readTextFile":true,"writeTextFile":true,"terminal":false},"clientInfo":{"name":"manual-test","version":"1.0.0"}},"id":1}' \
| continuum-router --config config.yaml --mode stdio 2>/dev/null
Expected 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}
Step 2: Interactive Session¶
For a full interactive session, use a tool like socat or a simple script:
# Start the router in background, capturing stdin/stdout
continuum-router --config config.yaml --mode stdio 2>/dev/null &
ROUTER_PID=$!
# Send messages via /proc or named pipes
# (See the scripted example below for a practical approach)
Scripted Testing¶
Here is a complete test session using a shell script:
#!/bin/bash
# acp-test.sh — Send a sequence of ACP messages to the router
CONFIG="config.yaml"
{
# 1. Initialize
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"readTextFile":true},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
# Wait for the response to be processed
sleep 0.5
# 2. Create a new session
echo '{"jsonrpc":"2.0","method":"session/new","params":{"mode":"code"},"id":2}'
sleep 0.5
# 3. Send a prompt (replace SESSION_ID with the actual session ID from step 2)
# echo '{"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"SESSION_ID","content":[{"type":"text","text":"Hello, what can you do?"}],"stream":false},"id":3}'
# sleep 2
} | continuum-router --config "$CONFIG" --mode stdio 2>/dev/null
Using Python for Testing¶
For more control over the bidirectional communication:
#!/usr/bin/env python3
"""ACP client test script."""
import json
import subprocess
import sys
import threading
def read_responses(proc):
"""Read and print responses from the router."""
for line in proc.stdout:
resp = json.loads(line)
print(json.dumps(resp, indent=2))
def send(proc, method, params=None, msg_id=None):
"""Send a JSON-RPC message."""
msg = {"jsonrpc": "2.0", "method": method}
if params:
msg["params"] = params
if msg_id is not None:
msg["id"] = msg_id
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()
proc = subprocess.Popen(
["continuum-router", "--config", "config.yaml", "--mode", "stdio"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
# Read responses in a background thread
reader = threading.Thread(target=read_responses, args=(proc,), daemon=True)
reader.start()
# 1. Initialize
send(proc, "initialize", {
"protocolVersion": 1,
"clientCapabilities": {"readTextFile": True, "writeTextFile": True},
"clientInfo": {"name": "python-test", "version": "1.0.0"},
}, msg_id=1)
import time; time.sleep(1)
# 2. Create session
send(proc, "session/new", {"mode": "code"}, msg_id=2)
time.sleep(1)
# 3. Close stdin to trigger graceful shutdown
proc.stdin.close()
proc.wait()
IDE Integration¶
ACP is designed for IDE clients to spawn the router as a child process and communicate over stdio. The general pattern:
IDE Process
│
├── spawn: continuum-router --config config.yaml --mode stdio
│ stdin ← JSON-RPC requests from IDE
│ stdout → JSON-RPC responses to IDE
│ stderr → logs (can be captured for debugging)
│
└── communicate via NDJSON (one JSON object per line)
Integration Steps¶
-
Spawn the process: Start
continuum-router --mode stdio --config <path>as a child process with piped stdin/stdout. -
Send
initialize: The first message must be theinitializerequest. All other methods are rejected until initialization completes. -
Create a session: Call
session/newwith a mode (code,architect, etc.) to get asessionId. -
Send prompts: Use
session/promptwith the session ID to send user messages. The router streams responses back assession/updatenotifications. -
Handle permissions: When the LLM requests tool operations (file edits, command execution), the router sends
session/request_permissionnotifications. The IDE should display a UI prompt and respond withallow_once,allow_always,reject_once, orreject_always. -
Manage lifecycle: Use
session/cancelto abort in-flight operations,session/set_modeto change modes, and close stdin for graceful shutdown.
Example: Spawning from Node.js¶
const { spawn } = require("child_process");
const readline = require("readline");
const router = spawn("continuum-router", [
"--config", "config.yaml",
"--mode", "stdio",
]);
// Parse NDJSON responses
const rl = readline.createInterface({ input: router.stdout });
rl.on("line", (line) => {
const msg = JSON.parse(line);
console.log("Received:", JSON.stringify(msg, null, 2));
});
// Log stderr for debugging
router.stderr.on("data", (data) => {
console.error("[router log]", data.toString());
});
function send(method, params, id) {
const msg = JSON.stringify({ jsonrpc: "2.0", method, params, id });
router.stdin.write(msg + "\n");
}
// Initialize
send("initialize", {
protocolVersion: 1,
clientCapabilities: { readTextFile: true, writeTextFile: true },
clientInfo: { name: "my-ide", version: "1.0.0" },
}, 1);
// After receiving initialize response, create session...
setTimeout(() => {
send("session/new", { mode: "code" }, 2);
}, 1000);
MCP Server Tunneling¶
If you have MCP servers configured, ACP clients can access them through the router without managing separate MCP connections.
Configuration¶
Add MCP servers to your config:
acp:
enabled: true
capabilities:
mcp: true
mcp:
max_connections_per_session: 5
allowed_servers: [] # Empty = all allowed
mcp_servers:
- id: filesystem
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
- id: github
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
Usage¶
After initializing, connect to an MCP server and route messages:
// Connect
{"jsonrpc":"2.0","method":"mcp/connect","params":{"id":"filesystem"},"id":10}
// List tools
{"jsonrpc":"2.0","method":"mcp/message","params":{"connectionId":"<returned-uuid>","message":{"jsonrpc":"2.0","method":"tools/list","id":1}},"id":11}
// Disconnect
{"jsonrpc":"2.0","method":"mcp/disconnect","params":{"connectionId":"<returned-uuid>"},"id":12}
Admin Endpoints¶
When running in HTTP mode alongside ACP configuration, the admin API provides ACP status endpoints:
| Endpoint | Description |
|---|---|
GET /admin/acp/status | ACP subsystem status and capabilities |
GET /admin/acp/sessions | List active ACP sessions |
GET /admin/acp/agent.json | Agent registry metadata for discovery |
# Check ACP status
curl http://localhost:8080/admin/acp/status
# View active sessions
curl http://localhost:8080/admin/acp/sessions
# Get agent metadata
curl http://localhost:8080/admin/acp/agent.json
Troubleshooting¶
Router exits immediately¶
Ensure your config file exists and has valid YAML syntax. Check stderr output:
"Server not initialized" errors (-32002)¶
The initialize handshake must be the first message sent. All other methods are rejected until initialization completes.
No response to prompts¶
- Verify that
acp.enabledistruein your config - Verify that the configured backend is reachable
- Check that
acp.default_modelmatches a model available on your backend - Review stderr logs for backend connection errors
Permission requests not working¶
If tool calls are being silently rejected, check your permission configuration:
acp:
permissions:
default_policy: ask_always # Not "allow_all" for production
auto_allow:
- read
- search
- think
MCP server fails to connect¶
- Verify the MCP server command is installed (e.g.,
npxis available) - Check that the server ID matches the configuration
- Review the
server_spawn_timeoutsetting (default 10s)
Next Steps¶
- ACP Architecture — Protocol specification and internal design
- Configuration Guide — Full configuration reference
- Deployment Guide — Production deployment options