Agent Communication Protocol (ACP)¶
이 문서는 Continuum Router의 ACP 구현을 설명합니다. 이 구현으로 라우터는 JSON-RPC 2.0 프로토콜을 통해 IDE 클라이언트와 코딩 어시스턴트를 위한 에이전트 백엔드로 동작할 수 있습니다.
개요¶
ACP(Agent Communication Protocol)를 사용하면 IDE 클라이언트(에디터, 터미널, 코딩 어시스턴트)가 Continuum Router를 LLM 에이전트로 삼아 표준 JSON-RPC 2.0 전송 위에서 통신할 수 있습니다. ACP가 활성화되면 라우터는 다음 두 역할을 동시에 수행합니다:
- OpenAI 호환 요청을 위한 HTTP API 게이트웨이 (기존 기능)
- ACP를 통한 IDE 통합용 stdio 기반 에이전트 백엔드
┌─────────────────────────────┐
│ IDE 클라이언트 (에디터) │
└──────────────┬──────────────┘
│
JSON-RPC 2.0 (NDJSON)
│
┌──────────────▼──────────────┐
│ Continuum Router (ACP) │
│ ┌────────────────────────┐ │
│ │ AcpMethodRouter │ │
│ │ SessionStore │ │
│ │ PermissionPolicy │ │
│ │ ToolExecutor │ │
│ │ McpConnectionManager │ │
│ └────────────────────────┘ │
└──────────────┬──────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌────────┐ ┌───────────┐
│ OpenAI │ ... │ Anthropic │
└────────┘ └───────────┘
프로토콜 스택¶
ACP는 JSON-RPC 2.0을 와이어 프로토콜로 사용하고, stdin/stdout을 통해 줄바꿈으로 구분된 JSON(NDJSON) 형식으로 통신합니다. 각 줄이 하나의 완전한 JSON-RPC 메시지입니다.
메시지 유형¶
| 유형 | 설명 |
|---|---|
| Request | id가 있는 메서드 호출이며, 응답을 기대합니다 |
| Notification | id가 없는 메서드 호출이며, 응답을 기대하지 않습니다 |
| Response | 요청에 대한 결과 또는 오류 |
표준 오류 코드¶
| 코드 | 의미 |
|---|---|
-32700 | 파싱 오류 (형식이 잘못된 JSON) |
-32600 | 잘못된 요청 |
-32601 | 메서드를 찾을 수 없음 |
-32602 | 잘못된 매개변수 |
-32603 | 내부 오류 |
-32002 | 서버가 초기화되지 않음 |
-32001 | 애플리케이션 오류 (세션을 찾을 수 없음 등) |
프로토콜 라이프사이클¶
1. 초기화 핸드셰이크¶
클라이언트는 다른 어떤 메서드보다 먼저 반드시 initialize를 호출해야 합니다. 초기화가 완료될 때까지 다른 모든 메서드는 -32002로 거부됩니다.
요청:
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"readTextFile": true,
"writeTextFile": true,
"terminal": false
},
"clientInfo": {
"name": "MyEditor",
"version": "1.0.0"
}
},
"id": 1
}
응답:
{
"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.14.0"
},
"authMethods": []
},
"id": 1
}
2. 인증 (선택 사항)¶
authenticate 메서드는 모든 인증 시도를 수락하는 스텁(stub)이며, 라우터의 API 키 시스템과는 연결되어 있지 않습니다. 접근 제어 용도로 이 메서드에 의존하지 마세요.
{
"jsonrpc": "2.0",
"method": "authenticate",
"params": {
"method": "api_key",
"token": "your-api-key"
},
"id": 2
}
3. 세션 관리¶
세션 생성¶
응답:
{
"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
}
기존 세션 로드¶
loadSession 에이전트 기능이 필요합니다.
{
"jsonrpc": "2.0",
"method": "session/load",
"params": { "sessionId": "550e8400-e29b-41d4-a716-446655440000" },
"id": 4
}
세션 모드 변경¶
응답:
진행 중인 작업 취소¶
세션의 취소 토큰을 발동시킵니다. 새 토큰이 다시 설치되므로 세션은 계속 사용할 수 있습니다.
4. 프롬프트 전송¶
LLM에 프롬프트를 보내고 session/update 알림으로 응답을 받습니다.
요청:
{
"jsonrpc": "2.0",
"method": "session/prompt",
"params": {
"sessionId": "550e8400-...",
"content": [
{ "type": "text", "text": "Explain the architecture of this project" }
],
"model": "gpt-5.6-sol",
"stream": true,
"temperature": 0.7,
"maxTokens": 4096
},
"id": 7
}
스트리밍 알림은 id가 없는 JSON-RPC 알림으로 전송됩니다:
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "550e8400-...",
"content": [{ "type": "text", "text": "The architecture..." }]
}
}
최종 응답:
모델 결정¶
프롬프트를 어떤 모델로 보낼지는 다음 순서로 정해집니다.
session/prompt요청의model필드- 라우터 설정의
acp.default_model
그 아래로 내려가는 기본값은 없습니다. 둘 다 모델을 지정하지 않으면 -32602를 반환하며, data.field는 "model"이고 메시지는 요청에 모델을 넣거나 acp.default_model을 설정하라고 안내합니다. 모델이 없는 프롬프트는 정보가 부족한 요청이고, 아무 백엔드의 아무 모델을 골라 답하면 호출자가 요청하지 않은 응답을 돌려주는 셈이 됩니다.
모델 이름은 HTTP 채팅 경로와 똑같이 문자 그대로 비교합니다. ACP 경로에는 별칭(alias) 해석이 없으므로, 백엔드를 고른 이름과 그 백엔드로 보내는 이름이 항상 같습니다.
백엔드 선택¶
session/prompt는 HTTP 채팅, Responses, 이미지, 실시간 경로가 함께 쓰는 공용 선택 지점을 통해 백엔드를 고릅니다. 순서는 다음과 같습니다.
- 모델 기반 후보 결정: 결정된 모델을 실제로 제공하는 백엔드만 후보가 됩니다.
- 내부/비활성 백엔드 제외:
internal: true또는enabled: false인 백엔드는 HTTP 경로와 같은 규칙으로 ACP 트래픽을 받지 않습니다. - 헬스 필터링: 헬스 체크에 실패 중인 백엔드는 선택되지 않습니다.
- 서킷 브레이커 승인: 정상 백엔드가 남아 있는 한 열린 서킷은 제외되고, 선택 시점에 얻은 승인은 요청 결과로 정산되므로 ACP 트래픽도 다른 트래픽과 동일하게 서킷 브레이커에 반영됩니다.
- 선택 전략: 남은 후보는 설정된
selection_strategy(및 등록된 스코어러)를 거칩니다.
범위 결정: ACP는 의도적으로 얇은 전달 경로입니다. HTTP 프록시의 재시도 루프, 모델 폴백 체인(fallback.fallback_chains), 키별 백엔드 허용 목록은 적용하지 않습니다. stdio ACP 클라이언트는 API 키 없이 동작하는 단일 로컬 프로세스라 범위를 좁힐 키 자체가 없고, 재시도와 폴백은 시도 이력을 관리하는 HTTP 요청 루프의 몫입니다. 실패한 프롬프트는 클라이언트에 그대로 보고되며, 재시도는 프로토콜 수준에서 이뤄집니다.
프롬프트 오류 매핑¶
| 조건 | 코드 | 페이로드 |
|---|---|---|
요청에 model이 없고 acp.default_model도 없음 | -32602 | data.field: "model" |
| 결정된 모델을 제공하는 백엔드가 없음 | -32602 | data.field, data.value(모델), data.source(request 또는 acp.default_model) |
| 모델을 제공하는 백엔드는 있으나 모두 비정상이거나 서킷이 열림 | -32603 | data.value(모델), data.reason: "all_backends_unhealthy" |
| 선택된 백엔드가 HTTP 오류를 반환 | -32603 | data.backend, data.status |
acp.default_model이 어떤 백엔드도 제공하지 않는 모델을 가리키면 요청 시점에 경고 로그도 남깁니다. 제공 모델 목록은 모델 디스커버리가 끝나야 알 수 있으므로 설정 검증 단계에서는 잡을 수 없습니다.
종료 사유¶
| 값 | 설명 |
|---|---|
end_turn | 모델이 응답을 자연스럽게 완료함 |
max_tokens | 토큰 한도에서 응답이 잘림 |
refusal | 모델이 응답 생성을 거부함 |
cancelled | session/cancel로 작업이 취소됨 |
콘텐츠 블록 유형¶
| 유형 | 필드 | 설명 |
|---|---|---|
text | text | 일반 텍스트 콘텐츠 |
image | data, mediaType | Base64로 인코딩된 이미지 데이터 |
resource | uri, mediaType (선택 사항) | 외부 리소스 참조 |
도구 호출 보고¶
LLM이 도구 호출을 생성하면, 에이전트는 도구 호출 상태 업데이트를 담은 session/update 알림으로 이를 클라이언트에 보고합니다.
도구 종류¶
| 종류 | 설명 | 기본 권한 |
|---|---|---|
read | 파일 읽기 또는 디렉터리 목록 조회 | 자동 허용 |
edit | 파일 쓰기 또는 수정 | 항상 확인 |
delete | 파일 또는 디렉터리 삭제 | 항상 확인 |
move | 파일 이름 변경 또는 이동 | 항상 확인 |
search | 파일 내용 검색 | 자동 허용 |
execute | 셸 명령 실행 | 항상 확인 |
think | 내부 추론 | 자동 허용 |
fetch | HTTP 요청 | 확인 |
other | 분류되지 않은 작업 | 확인 |
도구 호출 라이프사이클¶
┌─────────┐
│ Pending │
└────┬────┘
│
분류 + 권한 확인
│
┌────▼────┐
┌─────────│ Ask │─────────┐
│ └─────────┘ │
allow/always reject/always
│ │
┌────▼──────┐ ┌──────▼──┐
│InProgress │ │ Failed │
└────┬──────┘ └─────────┘
│
도구 실행
│
┌────▼──────┐
│ Completed │
└───────────┘
권한 위임¶
권한 시스템은 클라이언트가 어떤 도구 작업을 허용할지 제어할 수 있게 합니다:
권한 요청 알림:
{
"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 }]
}
}
클라이언트 응답 옵션:
allow_once: 이 작업 한 번만 허용allow_always: 세션 동안 이 종류의 모든 작업 허용reject_once: 이 작업 한 번만 거부reject_always: 세션 동안 이 종류의 모든 작업 거부
캐시된 always_* 결정은 세션이 유지되는 동안 지속됩니다.
MCP-over-ACP 브리지¶
MCP 브리지는 기존 MCP(Model Context Protocol) 서버를 ACP 채널을 통해 터널링할 수 있게 합니다. 덕분에 IDE 클라이언트는 별도의 연결을 관리하지 않고도 MCP 서버에 접근할 수 있습니다.
MCP 서버에 연결¶
응답:
메시지 라우팅¶
{
"jsonrpc": "2.0",
"method": "mcp/message",
"params": {
"connectionId": "uuid-...",
"message": {
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
},
"id": 11
}
연결 해제¶
{
"jsonrpc": "2.0",
"method": "mcp/disconnect",
"params": { "connectionId": "uuid-..." },
"id": 12
}
서버 ID 검증 규칙¶
- 최대 128자
- ASCII 영숫자, 하이픈, 밑줄, 점만 사용 가능
- 이중 밑줄 사용 불가 (도구 네임스페이스용으로 예약)
- 영숫자로 시작하고 끝나야 함
프로세스 라이프사이클¶
mcp/connect가 stdio 파이프를 연결한 MCP 서버 프로세스를 스폰합니다- 백그라운드 태스크가 stdin/stdout을 양방향으로 브리지합니다 (메시지 버퍼 64개)
- stderr는 라우터의
tracing::warn로그로 전달됩니다 mcp/disconnect는 SIGTERM을 보내고 5초간 기다린 뒤, 필요하면 SIGKILL을 보냅니다
기능 협상¶
초기화 과정에서 클라이언트와 에이전트는 서로의 기능(capability) 선언을 교환하며, 협상된 결과에 따라 해당 세션에서 사용할 수 있는 기능이 결정됩니다.
클라이언트 기능¶
| 기능 | 설명 |
|---|---|
readTextFile | 클라이언트가 로컬 파일시스템에서 파일 읽기를 지원합니다 |
writeTextFile | 클라이언트가 로컬 파일시스템에 파일 쓰기를 지원합니다 |
terminal | 클라이언트가 터미널 명령 실행을 지원합니다 |
에이전트 기능¶
| 기능 | 설명 |
|---|---|
loadSession | 에이전트가 이전 세션 재개를 지원합니다 |
image | 에이전트가 이미지 콘텐츠 블록을 지원합니다 |
audio | 에이전트가 오디오 콘텐츠 블록을 지원합니다 |
embeddedContext | 에이전트가 임베디드 컨텍스트 블록을 지원합니다 |
mcp | 에이전트가 MCP-over-ACP 브리지를 지원합니다 |
에이전트 레지스트리 메타데이터¶
라우터는 에이전트 레지스트리 디스커버리에 쓰이는 agent.json 메타데이터 문서를 생성하며, GET /admin/acp/agent.json으로 조회할 수 있습니다.
출력 예제:
{
"name": "continuum-router",
"displayName": "Continuum Router",
"version": "1.14.0",
"description": "Local LLM inference agent with multi-backend routing",
"transport": [
{ "type": "stdio" }
],
"capabilities": {
"loadSession": true,
"mcp": true
}
}
Admin API 엔드포인트¶
| 엔드포인트 | 설명 |
|---|---|
GET /admin/acp/status | ACP 서버 상태 (활성화 여부, 전송, 기능) |
GET /admin/acp/sessions | 활성 ACP 세션 목록 |
GET /admin/acp/agent.json | 동적 에이전트 레지스트리 메타데이터 |
설정¶
ACP는 기본적으로 비활성화되어 있습니다. 활성화하려면 config.yaml에 다음을 추가하세요:
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
# ACP 세션의 기본 모델 (선택 사항)
default_model: "gpt-5.6-sol"
# 시스템 프롬프트 주입 (선택 사항)
system_prompt: "You are a helpful coding assistant."
# 코딩 에이전트 모드 활성화
coding_agent_mode: true
permissions:
# 기본 정책: ask_always, allow_read 또는 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: [] # 비어 있으면 설정된 모든 서버 허용
server_spawn_timeout: "10s"
설정 레퍼런스¶
최상위 옵션¶
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
enabled | bool | false | ACP 서브시스템 활성화/비활성화 |
default_model | string | 없음 | session/prompt 요청이 model을 생략했을 때 사용할 모델. 둘 다 없으면 프롬프트는 -32602로 거부된다 |
system_prompt | string | 없음 | 모든 ACP 요청에 시스템 프롬프트 주입 |
coding_agent_mode | bool | false | 코딩 에이전트 시스템 프롬프트 활성화 |
전송 옵션¶
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
transport.stdio.enabled | bool | true | stdio 전송 활성화 |
권한 옵션¶
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
permissions.default_policy | enum | ask_always | 기본 정책: ask_always, allow_read, allow_all |
permissions.auto_allow | list | [read, search, think] | 확인 없이 자동 허용되는 도구 종류 |
permissions.always_ask | list | [edit, delete, execute] | 항상 권한 확인이 필요한 도구 종류 |
세션 옵션¶
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
sessions.max_concurrent | int | 10 | 최대 동시 세션 수 |
sessions.idle_timeout | string | "1h" | 정리 전 유휴 타임아웃 |
sessions.storage | string | "memory" | 저장소 백엔드: memory 또는 file |
sessions.storage_path | string | 없음 | 파일 기반 저장소 경로 |
MCP 브리지 옵션¶
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
mcp.max_connections_per_session | int | 5 | 세션당 최대 MCP 연결 수 |
mcp.allowed_servers | list | [] | 허용할 서버 ID (비어 있으면 모두 허용) |
mcp.server_spawn_timeout | string | "10s" | MCP 서버 프로세스 스폰 타임아웃 |
모듈 구조¶
src/acp/
├── mod.rs # 모듈 루트
├── jsonrpc.rs # JSON-RPC 2.0 프로토콜 타입
├── types.rs # ACP 콘텐츠 블록과 메시지
├── error.rs # 오류 타입
├── capabilities.rs # 기능 협상
├── config.rs # 설정 구조체
├── config_validate.rs # 설정 검증
├── metadata.rs # 에이전트 레지스트리 메타데이터 (agent.json)
├── transport/
│ ├── mod.rs # AcpTransport 트레이트
│ └── stdio.rs # stdin/stdout 위의 NDJSON
├── session.rs # 세션 라이프사이클과 저장소
├── router.rs # 메서드 디스패치 라우터
├── handlers/
│ ├── initialize.rs # 프로토콜 초기화
│ ├── authenticate.rs # 인증 (스텁)
│ ├── session.rs # session/* 메서드
│ ├── prompt.rs # session/prompt 핸들러
│ ├── prompt_select.rs # 프롬프트용 모델 기반 백엔드 선택
│ ├── prompt_stream.rs # SSE 스트리밍 프로세서
│ └── translate.rs # ACP → OpenAI 형식 변환
├── tools/
│ ├── mod.rs # 도구 호출 타입
│ ├── classifier.rs # 도구 종류 분류
│ ├── permission.rs # 권한 정책 엔진
│ ├── executor.rs # 도구 실행 오케스트레이터
│ └── client_methods.rs # 클라이언트 측 메서드 (fs/*, terminal/*)
├── mcp_bridge/
│ ├── mod.rs # MCP 기능
│ ├── handlers.rs # connect/message/disconnect 핸들러
│ ├── connection.rs # 연결 관리자
│ ├── process.rs # MCP 서버 프로세스 스폰
│ └── schema_translate.rs # MCP ↔ ACP 스키마 변환
└── admin.rs # Admin API 엔드포인트