ACP
ACP (Agent Client Protocol)
Zed 같은 편집기는 ACP를 사용해요. TUI나 편집기가 외부 코딩 에이전트를 구동하게 하는 stdio JSON-RPC 프로토콜이죠. 텍스트를 스트리밍하고, 파일 편집을 diff로 렌더링하며, 민감한 도구 호출을 승인하도록 사용자에게 프롬프트해요. ACP 서버 측을 직접 구현하지 않고 Pydantic AI Agent를 그런 편집기 안의 일급 에이전트로 나타내고 싶을 때 이 capability를 사용하세요.
출처: 문서
본문
실험적
이 문서의 졸업된(graduated) capability들과 달리, ACP 자체는 바뀔 뿐만 아니라 제거될 수도 있어요. pydantic_ai_harness.experimental 아래에 있고, 폐기 기간 없이 어떤 릴리스에서든 바뀌거나 제거될 수 있어요. 실험적 경로에서 임포트하세요 — 최상위 내보내기는 없습니다:
from pydantic_ai_harness.experimental.acp import run_acp_stdio_sync
실험적 capability를 임포트하면 HarnessExperimentalWarning이 발생해요. 단일 필터로 모든 하네스 실험적 경고를 조용히 할 수 있어요 (capability별 줄은 필요 없음):
import warnings
from pydantic_ai_harness.experimental import HarnessExperimentalWarning
warnings.filterwarnings('ignore', category=HarnessExperimentalWarning)
문제 (The problem)
Pydantic AI 에이전트를 ACP 편집기에 끼우려면 ACP 서버 측을 손으로 구현해야 해요. 유선 한도 아래로 스트리밍 텍스트를 청킹하고, 도구 호출을 diff로 렌더링하며, 프로토콜의 권한 요청을 에이전트의 도구에 매핑하고, 워크스페이스별 세션을 관리해야 하죠.
해결책 (The solution)
run_acp_stdio는 어떤 Pydantic AI Agent든 ACP 에이전트로 stdin/stdout 위에서 서빙해요. 편집기가 당신의 스크립트를 서브프로세스로 실행하고 대화하며, 어댑터가 ACP와 에이전트의 실행 루프 사이를 번역합니다:
| ACP가 필요로 하는 것 | 어댑터가 제공하는 것 |
|---|---|
| 스트리밍된 보조 텍스트와 추론 | 유선 한도 아래로 청킹된 에이전트 텍스트/사고 델타 |
풍부한 도구 호출(kind, 파일 locations, diff) |
FileSystem/Shell 도구 호출을 인식하는 프레젠터 |
| 인간-in-the-loop 도구 승인 | ACP 권한 요청을 Pydantic AI의 지연-승인 도구에 매핑 |
| 워크스페이스별 세션 | 클라이언트 작업 디렉터리에 도구를 뿌리내리는 session_config 훅 |
| 취소, 멀티턴 히스토리, 세션 종료 | 세션별로 처리 |
설치 (Installation)
pip install "pydantic-ai-harness[acp]"
uv add "pydantic-ai-harness[acp]"
이것은 agent-client-protocol SDK를 끌어와요. 하네스의 나머지는 그것에 의존하지 않아요. pydantic_ai_harness.experimental.acp만 의존합니다.
빠른 시작 (Quick start)
에이전트를 만들고 서빙하는 스크립트를 작성하세요:
# my_acp_agent.py
from pydantic_ai import Agent
from pydantic_ai_harness.experimental.acp import run_acp_stdio_sync
def build_agent() -> Agent[None, str]:
return Agent('anthropic:claude-sonnet-4-6', instructions='You are a coding assistant.')
if __name__ == '__main__':
run_acp_stdio_sync(build_agent())
run_acp_stdio_sync는 연결이 살아 있는 동안 블록해요. 편집기가 실행하는 에이전트의 main()이죠. 기존 이벤트 루프 안에서는 비동기 run_acp_stdio를 쓰세요.
편집기에서 연결하기 (Connecting from an editor)
ACP 클라이언트는 에이전트를 서브프로세스로 실행해요. Zed에서는 외부 에이전트로 settings.json에 등록합니다:
{
"agent_servers": {
"My Pydantic AI Agent": {
"type": "custom",
"command": "python",
"args": ["/absolute/path/to/my_acp_agent.py"],
"env": { "ANTHROPIC_API_KEY": "..." }
}
}
}
어떤 ACP 호환 클라이언트든 같은 방식으로 동작해요. python my_acp_agent.py를 가리키면 됩니다.
프로바이더 환경은 실행된 서브프로세스가 볼 수 있어야 해요. GUI 편집기와 SDK 기반 테스트 래퍼는 당신의 대화형 셸 시작 파일을 소싱하지 않을 수 있어요. 실제 모델 에이전트가 initialize 전에 종료되거나 프로바이더 인증에 실패하면, 먼저 명령의 프로세스가 ANTHROPIC_API_KEY 같은 변수를 볼 수 있는지 확인하세요.
워크스페이스에 도구 뿌리내리기 (Rooting tools at the workspace)
코딩 에이전트는 서브프로세스가 시작된 곳이 아니라 편집기가 연 워크스페이스의 파일을 읽고 써야 해요. ACP는 각 세션에 작업 디렉터리(cwd)를 줘요. session_config 팩토리가 그것을 세션별 도구로 바꿉니다:
from pydantic_ai import Agent
from pydantic_ai_harness import FileSystem, Shell
from pydantic_ai_harness.experimental.acp import AcpSession, AcpSessionConfig, run_acp_stdio_sync
agent = Agent('anthropic:claude-sonnet-4-6')
def session_config(session: AcpSession) -> AcpSessionConfig[None]:
# Root file and shell tools at the workspace the client opened.
return AcpSessionConfig(
deps=None,
toolsets=[
FileSystem[None](root_dir=session.cwd).get_toolset(),
Shell[None](cwd=session.cwd).get_toolset(),
],
)
if __name__ == '__main__':
run_acp_stdio_sync(agent, session_config=session_config)
팩토리는 세션당 한 번, 클라이언트의 AcpSession 설정(그 cwd, mcp_servers, capabilities)과 함께 실행되어, 그 세션의 모든 실행에 적용되는 deps와 toolsets를 가진 AcpSessionConfig를 반환해요. 정적 FileSystem 하나로는 할 수 없는, 하나의 프로세스에서 여러 동시 세션에 걸쳐 올바른 방식이에요.
편집기 네이티브 파일시스템과 셸 (Editor-native filesystem and shell, optional)
위의 로컬 FileSystem과 Shell은 에이전트 프로세스 자신의 디스크와 서브프로세스에서 동작해요. 편집기의 진실 원천은 다르죠. 저장하지 않은 버퍼, 워크스페이스 레이아웃에 대한 편집기 자신의 생각, 원격이나 컨테이너화된 편집기라면 코드가 실제로 사는 머신까지요. 클라이언트가 지원을 광고하면, acp_filesystem과 acp_terminal이 에이전트에게 클라이언트를 통해 라우팅되는 read_file/write_file/run_command 도구를 줘서, 사용자가 있는 곳에서 작용하도록 해요:
from pydantic_ai_harness import FileSystem, Shell
from pydantic_ai_harness.experimental.acp import AcpSession, AcpSessionConfig, acp_filesystem, acp_terminal
def session_config(session: AcpSession) -> AcpSessionConfig[None]:
# Use the editor's filesystem/terminal when offered; otherwise fall back to local.
fs = acp_filesystem(session) or FileSystem[None](root_dir=session.cwd).get_toolset()
shell = acp_terminal(session) or Shell[None](cwd=session.cwd).get_toolset()
return AcpSessionConfig(deps=None, toolsets=[fs, shell])
각 헬퍼는 클라이언트가 capability를 광고하지 않으면 None을 반환하므로, or가 로컬로 폴백하고 에이전트는 어느 쪽이든 동작해요. 도구 이름이 로컬 FileSystem/Shell과 일치해서 풍부한 렌더링이 동일하게 유지돼요.
도구 승인 (Tool approval)
도구를 승인 필요로 표시하면 ACP가 결정을 클라이언트에 릴레이하고, 클라이언트가 사용자에게 승인/거부 프롬프트를 보여줘요:
@agent.tool_plain(requires_approval=True)
def delete_file(path: str) -> str:
...
클라이언트가 보는 라이프사이클은 pending(승인 대기) -> in_progress(허용됨, 실행 중) -> completed/failed예요. 그래서 승인되지 않은 동작이 이미 실행 중으로 보이지 않아요. "Always allow"/"always reject" 결정은 세션 동안 기억되며, 기본적으로 정확한 호출(도구 이름 + 인자)로 범위가 한정되어, 한 호출 승인이 다른 호출을 조용히 승인하지 않아요. 그 범위를 넓히거나 좁히려면 permission_policy를 넘기세요.
풍부한 도구 렌더링 (Rich tool rendering)
기본적으로 어댑터는 하네스 FileSystem·Shell 도구 호출을 이름으로 인식하고, ACP kind(read/edit/search/execute), 그것이 닿는 파일 locations, 편집용 인라인 diff로 주석을 달아요. 편집기가 불투명한 JSON 대신 클릭 가능한 파일 링크와 diff 뷰를 렌더링하게요. 자신의 도구에 렌더링을 추가하려면 tool_presenter를 넘기세요(선택적으로 기본 default_coding_presenter 앞에 chain_presenters를 사용). 비활성화하려면 lambda _call: None.
MCP 서버
ACP 클라이언트는 세션 설정 중에 MCP 서버를 제공할 수 있어요. 이 어댑터는 그것을 스스로 연결하지 않아요. session_config가 session.mcp_servers를 Pydantic AI toolsets로 바꾸는 곳이에요(예를 들어 pydantic_ai.mcp.MCPServerStdio 사용). 클라이언트가 MCP 서버를 보내는데 그것을 소비할 session_config가 설치되어 있지 않으면, 조용히 무시하지 않고 세션 요청이 거부돼요. 규격을 따르는 클라이언트는 에이전트가 initialize 중 지원을 광고할 때만 HTTP/SSE MCP 서버를 보내요. session_config가 그것들을 연결한다면 그렇게 광고하세요:
from acp import schema
from pydantic_ai_harness.experimental.acp import PydanticAIACPAgent
PydanticAIACPAgent(
agent,
session_config=connect_mcp_servers,
mcp_capabilities=schema.McpCapabilities(http=True, sse=True),
)
프롬프트 콘텐츠 타입 (Prompt content types)
에이전트는 수용하는 프롬프트 콘텐츠를 광고해요. 기본은 텍스트 전용이므로, 클라이언트는 텍스트 모델이 다룰 수 없는 블록을 보내도록 초대받지 않아요. 모델이 지원하는 종류를 활성화하세요:
from acp import schema
run_acp_stdio_sync(agent, prompt_capabilities=schema.PromptCapabilities(image=True, embedded_context=True))
세션 영속성 (Session persistence)
session_store를 넘기면 클라이언트가 session/load로 과거 대화를 다시 열 수 있어요. 각 확정된 턴은 두 부분 — 모델의 메시지 히스토리와 클라이언트가 볼 수 있는 트랜스크립트 — 으로 영속되고, 다시 열면 히스토리를 에이전트로 복원하고 트랜스크립트를 클라이언트에 재생해서, UI가 사용자가 마지막으로 본 대로 재구성돼요. 스토어가 없으면 session/load는 미지원으로 광고됩니다.
from pydantic_ai_harness.experimental.acp import InMemorySessionStore
run_acp_stdio_sync(agent, session_store=InMemorySessionStore())
InMemorySessionStore는 프로세스 수명 동안 세션을 유지해요. SessionStore 프로토콜(StoredSession의 save/load)을 파일이나 데이터베이스 위에 구현해 재시작 후에도 살아남게 하세요. 저장된 값은 Pydantic 모델이라 Pydantic으로 직렬화됩니다. 세션 영속성은 대화를 다시 여는 것용이에요. 실행별(per-run) 영속성과는 직교합니다. 개별 턴도 충돌에 강하게 만들려면 step-durability capability를 추가하세요. 각 ACP 턴은 에이전트 실행 하나라, 두 계층이 접착제 없이 조합됩니다.
모델 선택 (Model selection)
models를 넘기면 model이라는 안정적인 ACP 세션 구성 옵션을 광고해요(Pydantic AI 모델 이름 사용). 첫 번째가 각 세션의 기본값이에요. 선택은 실행별 오버라이드로 적용돼요. 공유 에이전트는 절대 변경되지 않죠. session_store가 설정되면 세션과 함께 영속됩니다.
run_acp_stdio_sync(agent, models=['anthropic:claude-sonnet-4-6', 'anthropic:claude-opus-4-8', 'openai:gpt-4o'])
모델 id는 Pydantic AI 모델이 받아들이는 어떤 문자열이든 되므로, 아직 KnownModelName에 없는 더 새로운 모델도 동작해요. models='all'을 넘기면 Pydantic AI가 아는 모든 모델을 제공해요. infer_model이 이해하지 못하는 id(OAuth 또는 구독 모델)를 광고하려면 model_resolver를 넘겨 선택된 id를 미리 만들어진 Model에 매핑하세요.
취소와 한계 (Cancellation and limitations)
- 취소.
session/cancel과session/close는 진행 중인 턴을 취소해요. close는 반환 전에 그것이 풀릴 때까지 기다려요. 협력적인 비동기 도구는 즉시 멈춰요. 워커 스레드에서 이미 실행 중인 동기 도구는 강제로 멈출 수 없으므로, 취소에 민감한 작업에는 비동기 도구를 선호하세요. - 승인 감지. 승인이 필요한 도구는
FunctionToolset(하네스FileSystem/Shell과@agent.tool이 모두 씀)에 있을 때 인식돼요. 호출별로 동적으로 결정되는 승인 요구 사항을 가진 도구(본문에서ApprovalRequired를 발생시키는 방식)는in_progress로 시작하며, 발생시키기 전에 실행한 부수 효과는 이미 일어났어요. 승인 전에 부분 실행되면 안 되는 동작에는ApprovalRequiredToolset을 사용하세요. capability의for_run()이나AgentToolset의 호출 가능한 팔로 실행별로만 추가된 도구는 미리 인식되지 않지만, 실행은 정확히 어떤 호출이 승인을 위해 멈췄는지 보고하고 어댑터는 클라이언트에게 묻기 전에 그들의 공지 상태를pending으로 정정해요. - 덮어쓰기 diff.
write_file은 덮어쓰기를 새 파일을 만드는 것처럼 렌더링해서, diff가 무엇을 대체했는지를 과소 표현해요. - 라이브 터미널 패널.
acp_terminal은 명령의 캡처된 출력을 반환해요. 도구 호출 안에 라이브 터미널 패널을 임베드하지 않아요. - 이미지. 프롬프트 이미지 블록은 기본적으로 꺼져 있고, 그것들을 받는 모델로
prompt_capabilities를 통해 활성화해야 해요. - 슬래시 명령. 어댑터는 아직 어떤 명령도 광고하지 않아서, 클라이언트에 슬래시 명령이 나타나지 않아요. 계획 중입니다.
API
run_acp_stdio( # async; serve until the client disconnects
agent,
*,
deps=None,
name=None, # advertised name; defaults to the agent's name
version='0.1.0',
session_config=None, # per-session deps/toolsets from the client's setup
permission_policy=None, # scope of remembered "always" approval decisions
prompt_capabilities=None, # defaults to text-only
mcp_capabilities=None, # MCP transports to advertise; needs a session_config to connect them
tool_presenter=None, # defaults to the FileSystem/Shell presenter
session_store=None, # enables session/load by persisting each session
models=None, # models offered as the `model` config option ('all' for every known model)
model_resolver=None, # maps an advertised model id to the Model used for the run
usage_limits=None, # per-run request/token ceilings
)
run_acp_stdio_sync(...) # synchronous wrapper, same arguments
PydanticAIACPAgent(agent, *, ...) # the ACP agent object, to embed in a custom server
모듈은 또한 세션 타입(AcpSession, AcpSessionConfig, McpServer), 스토어 타입(SessionStore, StoredSession, InMemorySessionStore), 클라이언트 toolsets(AcpFileSystemToolset, AcpTerminalToolset, acp_filesystem, acp_terminal), 권한 타입(ToolCallPermission, default_permission_scope), 프레젠테이션 헬퍼(ToolCallPresentation, chain_presenters, default_coding_presenter)를 내보내요.
소스: pydantic_ai_harness/experimental/acp/.
더 읽기 (Further reading)
- Agent Client Protocol — 프로토콜 명세
- Zed external agents — 편집기 측 구성
- Human-in-the-loop tool approval (Pydantic AI)
- Pydantic AI capabilities
더 알아보기 (Learn more)
- FileSystem — 파일 도구.
- Shell — 셸 도구.
- Pydantic AI Harness — 패키지 전반.