ag2-acp
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCLI coding agents over ACP
基于ACP的CLI编码代理
Drive external CLI coding agents — Claude Code, Codex, OpenCode — as first-class AG2 s, using the Agent Client Protocol (ACP). AG2 plays the ACP Client role; each CLI agent runs as an ACP Agent subprocess. Everything the agent does — message output, thinking, tool calls, plans, permission prompts — is externalized onto AG2's event stream, so you can observe, gate, and orchestrate it like any other AG2 agent.
AgentThe integration is just a config class ( and its presets) — no changes to the API.
ACPConfigAgent通过Agent Client Protocol(ACP)将外部CLI编码代理——Claude Code、Codex、OpenCode——作为一等AG2 进行驱动。AG2扮演ACP Client角色;每个CLI代理以ACP Agent子进程的形式运行。代理的所有操作——消息输出、思考过程、工具调用、计划、权限提示——都会被外化到AG2的事件流中,因此你可以像管控其他AG2 Agent一样观察、管控和编排它。
Agent该集成仅通过一个配置类(及其预设)实现——无需修改 API。
ACPConfigAgentWhen to use
使用场景
- You want to orchestrate CLI coding agents from Python: headless refactoring pipelines, "manager" agents delegating to coder agents, batch code-mod runs.
- You want to observe a coding agent's work live — thoughts, tool calls, plans — on the AG2 event stream.
- You want to gate the agent's sensitive actions (file writes, shell commands) with a permission policy or a human in the loop.
- You need the agent's file access confined to a workspace root () and its terminal use mediated by AG2.
fs_root
Not this skill: exposing an AG2 agent to other systems — that is (A2A protocol) or (MCP server). For the HITL input plumbing that relies on, see .
ag2-a2aag2-mcppermission_policy="ask"ag2-hitl- 你希望从Python中编排CLI编码代理:无头重构流水线、将任务委托给编码代理的「管理型」Agent、批量代码修改运行。
- 你希望实时观察编码代理的工作过程——思考内容、工具调用、计划——在AG2事件流中。
- 你希望通过权限策略或人机协同方式管控代理的敏感操作(文件写入、Shell命令)。
- 你需要将代理的文件访问限制在工作区根目录(),并由AG2管控其终端使用。
fs_root
不适用场景:将AG2 Agent暴露给其他系统——这属于(A2A协议)或(MCP服务器)的功能。依赖的人机协同输入流程,请查看。
ag2-a2aag2-mcppermission_policy="ask"ag2-hitlInstallation
安装
bash
pip install "ag2[acp]"The extra pulls in the SDK. Each CLI agent additionally needs its own ACP adapter on :
acpagent-client-protocolPATH| Agent | Adapter install | Auth |
|---|---|---|
| Claude Code | | |
| Codex | | |
| OpenCode | | |
No global install? Override the launch command to use npx: .
ClaudeCodeConfig(command=["npx", "-y", "@agentclientprotocol/claude-agent-acp"])Public API (): , , , .
from ag2.acp import ...ACPConfigClaudeCodeConfigCodexConfigOpenCodeConfigbash
pip install "ag2[acp]"acpagent-client-protocolPATH| 代理 | 适配器安装方式 | 认证方式 |
|---|---|---|
| Claude Code | | 环境变量 |
| Codex | | |
| OpenCode | 直接使用 | |
不想全局安装?可以覆盖启动命令使用npx:。
ClaudeCodeConfig(command=["npx", "-y", "@agentclientprotocol/claude-agent-acp"])公开API():、、、。
from ag2.acp import ...ACPConfigClaudeCodeConfigCodexConfigOpenCodeConfig60-second recipe — ask a coding agent to do work
60秒快速入门——让编码代理执行任务
python
import asyncio
from ag2 import Agent
from ag2.acp import ClaudeCodeConfig
async def main():
config = ClaudeCodeConfig(cwd="/path/to/repo") # workspace root
agent = Agent("coder", config=config)
try:
reply = await agent.ask("Refactor the auth module and add tests")
print(reply.body)
finally:
await config.aclose() # tear down the CLI subprocess
asyncio.run(main())One / = one ACP prompt turn: the CLI agent runs its own internal tool loop (possibly many tool calls) and AG2 streams every step as it happens.
ask()run()python
import asyncio
from ag2 import Agent
from ag2.acp import ClaudeCodeConfig
async def main():
config = ClaudeCodeConfig(cwd="/path/to/repo") # 工作区根目录
agent = Agent("coder", config=config)
try:
reply = await agent.ask("重构认证模块并添加测试用例")
print(reply.body)
finally:
await config.aclose() # 终止CLI子进程
asyncio.run(main())一次 / 调用对应一次ACP 对话轮次:CLI代理会运行自身的内部工具循环(可能包含多次工具调用),AG2会实时流式传输每一步操作。
ask()run()Choosing an adapter
选择适配器
- — launches
ClaudeCodeConfig(). Select the model via the adapter'sclaude-agent-acpenv var.ANTHROPIC_MODEL - — launches
CodexConfig(). Model via the adapter'scodex-acpenv var.MODEL_PROVIDER - — launches
OpenCodeConfig(). Model in OpenCode's own config (opencode acp:opencode.json)."model": "provider/model"
The presets'field is response metadata only — it is not sent to the agent. Pick the model through each adapter's own mechanism (env var / config file) as above.model
- —— 启动
ClaudeCodeConfig()。通过适配器的环境变量claude-agent-acp选择模型。ANTHROPIC_MODEL - —— 启动
CodexConfig()。通过适配器的环境变量codex-acp选择模型。MODEL_PROVIDER - —— 启动
OpenCodeConfig()。模型配置在OpenCode自身的配置文件中(opencode acp:opencode.json)。"model": "provider/model"
预设中的字段仅作为响应元数据——不会发送给代理。请通过上述适配器自身的机制(环境变量/配置文件)选择模型。model
Observing the agent's work
观察代理工作过程
Subscribe to the run's stream before awaiting the result:
python
from ag2 import Agent
from ag2.acp import ClaudeCodeConfig
from ag2.acp.events import ACPPlan
from ag2.events import ModelMessageChunk, ModelReasoning
from ag2.events.tool_events import BuiltinToolCallEvent
agent = Agent("coder", config=ClaudeCodeConfig(cwd="/path/to/repo"))
def observe(event):
if isinstance(event, ModelReasoning):
print("thinking:", event.content)
elif isinstance(event, ModelMessageChunk):
print(event.content, end="")
elif isinstance(event, BuiltinToolCallEvent):
print(f"tool: {event.name}({event.arguments})") # arguments = JSON string of the tool input
elif isinstance(event, ACPPlan):
for step in event.entries:
print(f" [{step.status}] {step.content}")
async with agent.run("Add a healthcheck endpoint") as run:
run.stream.subscribe(observe)
reply = await run.result()How ACP session updates map onto AG2 events:
| ACP update | AG2 event |
|---|---|
| agent message chunk | |
| thinking chunk | |
| tool call / tool result | |
| plan | |
| mode change | |
| available commands | |
The ACP-specific events live in ; the rest are the standard events from .
ag2.acp.eventsag2.events在等待结果之前订阅运行的事件流:
python
from ag2 import Agent
from ag2.acp import ClaudeCodeConfig
from ag2.acp.events import ACPPlan
from ag2.events import ModelMessageChunk, ModelReasoning
from ag2.events.tool_events import BuiltinToolCallEvent
agent = Agent("coder", config=ClaudeCodeConfig(cwd="/path/to/repo"))
def observe(event):
if isinstance(event, ModelReasoning):
print("思考中:", event.content)
elif isinstance(event, ModelMessageChunk):
print(event.content, end="")
elif isinstance(event, BuiltinToolCallEvent):
print(f"工具调用: {event.name}({event.arguments})") # arguments = 工具输入的JSON字符串
elif isinstance(event, ACPPlan):
for step in event.entries:
print(f" [{step.status}] {step.content}")
async with agent.run("添加健康检查端点") as run:
run.stream.subscribe(observe)
reply = await run.result()ACP会话更新与AG2事件的映射关系:
| ACP更新 | AG2事件 |
|---|---|
| 代理消息块 | |
| 思考内容块 | |
| 工具调用/工具结果 | |
| 计划 | |
| 模式变更 | |
| 可用命令 | |
ACP专属事件位于;其余为中的标准事件。
ag2.acp.eventsag2.eventsPermissions (human-in-the-loop)
权限管控(人机协同)
When the agent wants to perform a sensitive action (write a file, run a command), it sends a permission request. decides the answer:
permission_policy| Policy | Behavior |
|---|---|
| Route to the agent's |
| Approve automatically (headless orchestration) |
| Reject automatically |
python
agent = Agent(
"coder",
config=ClaudeCodeConfig(cwd="/repo", permission_policy="auto"), # fully autonomous
)Pitfall:with no input route available (no"ask", no interactive context) denies the request — a headless run with the default policy will quietly reject every sensitive action. For unattended runs sethitl_hookexplicitly.permission_policy="auto"
当代理想要执行敏感操作(写入文件、运行命令)时,会发送权限请求。决定处理方式:
permission_policy| 策略 | 行为 |
|---|---|
| 路由到代理的 |
| 自动批准(无头编排场景) |
| 自动拒绝 |
python
agent = Agent(
"coder",
config=ClaudeCodeConfig(cwd="/repo", permission_policy="auto"), # 完全自主运行
)注意事项:在没有可用输入路由的情况下使用(无"ask"、无交互上下文)会拒绝请求——默认策略下的无头运行会静默拒绝所有敏感操作。无人值守运行请显式设置hitl_hook。permission_policy="auto"
Configuration reference
配置参考
ACPConfig| Field | Default | Purpose |
|---|---|---|
| preset per agent | Executable + args launching the agent in ACP mode |
| | Workspace root for the session |
| | Extra env vars, merged over a trimmed base env ( |
| | Response metadata only — see "Choosing an adapter" |
| | |
| | Root for mediated |
| | Advertise the ACP terminal capability |
| | Extra workspace roots |
| | Subprocess spawn + handshake timeout (s) |
| | Per-prompt-turn timeout (s); on expiry the turn is cancelled and the reply body is whatever streamed so far |
| | Grace period (s) after a timed-out turn signals |
File and terminal operations the agent requests are mediated by AG2: file access is confined to , and commands run under AG2's control. clones a config (sessions are not carried over).
fs_rootconfig.copy(**overrides)ACPConfig| 字段 | 默认值 | 用途 |
|---|---|---|
| 各代理预设值 | 启动代理进入ACP模式的可执行文件+参数 |
| | 会话的工作区根目录 |
| | 额外环境变量,与精简版基础环境变量合并(包含 |
| | 仅作为响应元数据——查看「选择适配器」部分 |
| | |
| | 受管控的 |
| | 启用ACP终端功能 |
| | 额外工作区根目录 |
| | 子进程启动+握手超时时间(秒) |
| | 单轮对话超时时间(秒);超时后终止轮次,回复内容为已流式传输的部分 |
| | 超时轮次发送 |
代理请求的文件和终端操作由AG2管控:文件访问被限制在,命令在AG2控制下运行。可克隆配置(不会保留会话状态)。
fs_rootconfig.copy(**overrides)Lifecycle
生命周期
The ACP subprocess is spawned on the first turn and reused across turns of the same run. Call to tear down all live subprocesses started from a config (a finalizer terminates them as a safety net if you forget).
await config.aclose()ACP子进程在第一次轮次时启动,并在同一运行的多轮对话中复用。调用可终止该配置启动的所有活跃子进程(如果忘记调用,终结器会作为安全网自动终止它们)。
await config.aclose()Testing — in-process, no subprocess, no API keys
测试——进程内、无子进程、无需API密钥
ag2.acp.testing.fake_acp_configACPConfigACPTurnsession/updateAgent.runpython
import asyncio
from acp import schema
from ag2 import Agent
from ag2.acp.testing import ACPTurn, fake_acp_config
def text(t):
return schema.TextContentBlock(type="text", text=t)
async def main():
config = fake_acp_config(
ACPTurn(updates=[
schema.AgentThoughtChunk(session_update="agent_thought_chunk", content=text("planning")),
schema.AgentMessageChunk(session_update="agent_message_chunk", content=text("done")),
]),
permission_policy="auto", # overrides forward to ACPConfig
)
agent = Agent("coder", config=config)
try:
reply = await agent.ask("hello")
assert reply.body == "done"
finally:
await config.aclose()
asyncio.run(main())ACPTurn(hang=True)turn_timeoutag2.acp.testing.fake_acp_configACPConfigACPTurnsession/updateAgent.runpython
import asyncio
from acp import schema
from ag2 import Agent
from ag2.acp.testing import ACPTurn, fake_acp_config
def text(t):
return schema.TextContentBlock(type="text", text=t)
async def main():
config = fake_acp_config(
ACPTurn(updates=[
schema.AgentThoughtChunk(session_update="agent_thought_chunk", content=text("planning")),
schema.AgentMessageChunk(session_update="agent_message_chunk", content=text("done")),
]),
permission_policy="auto", # 覆盖ACPConfig的配置
)
agent = Agent("coder", config=config)
try:
reply = await agent.ask("hello")
assert reply.body == "done"
finally:
await config.aclose()
asyncio.run(main())ACPTurn(hang=True)turn_timeoutCommon pitfalls
常见陷阱
- Missing extra —
acp; without itpip install "ag2[acp]"fails on the missingfrom ag2.acp import ...SDK.acp - Exported API keys are not inherited — the subprocess env is a trimmed base set plus , so
env=in your shell does not reach the agent. Pass it viaexport ANTHROPIC_API_KEY=.... (env={"ANTHROPIC_API_KEY": ...}logins work becauseCLAUDE_CONFIG_DIRis in the base set.)HOME - Deprecated adapter name — the old (
claude-code-acp) is deprecated; use@zed-industries/claude-code-acp(claude-agent-acp), which is what@agentclientprotocol/claude-agent-acplaunches.ClaudeCodeConfig - in headless runs = deny — with no human input route, every permission request is rejected. Set
"ask"for unattended orchestration.permission_policy="auto" - does nothing on the wire — select the model via
model=/ANTHROPIC_MODEL/MODEL_PROVIDERinstead.opencode.json - Adapter not on —
PATHerrors usually mean the launch command wasn't found; install the adapter globally or use thestartup_timeoutcommand override.npx -y - AG2 are not exposed to the CLI agent yet — CLI-backed agents use their own built-in tools; the MCP tool bridge for AG2-provided tools is an upstream roadmap item.
tools=[...]
- 缺少扩展依赖——请执行
acp;否则pip install "ag2[acp]"会因缺少from ag2.acp import ...SDK而失败。acp - 导出的API密钥未被继承——子进程环境是精简的基础环境加上配置的变量,因此Shell中
env=不会传递给代理。请通过export ANTHROPIC_API_KEY=...显式传递。(env={"ANTHROPIC_API_KEY": ...}登录方式有效,因为CLAUDE_CONFIG_DIR在基础环境变量中。)HOME - 适配器名称已过时——旧的(
claude-code-acp)已被弃用;请使用@zed-industries/claude-code-acp(claude-agent-acp),这也是@agentclientprotocol/claude-agent-acp默认启动的适配器。ClaudeCodeConfig - 无头运行中使用=拒绝请求——没有人工输入路由时,所有权限请求都会被拒绝。无人值守编排请设置
"ask"。permission_policy="auto" - 配置无效——请通过
model=/ANTHROPIC_MODEL/MODEL_PROVIDER选择模型。opencode.json - 适配器不在中——
PATH错误通常意味着找不到启动命令;请全局安装适配器或使用startup_timeout命令覆盖。npx -y - AG2的尚未暴露给CLI代理——基于CLI的代理使用自身内置工具;AG2提供工具的MCP工具桥是上游路线图中的功能。
tools=[...]
Going deeper (source of truth)
深入了解(权威来源)
- —
ag2/acp/config.py+ the three presets and their defaults.ACPConfig - /
ag2/acp/client.py/bridge.py— the ACP Client, event bridging, subprocess lifecycle.session.py - — the exact ACP-update → AG2-event mapping.
ag2/acp/mappers.py - — how
ag2/acp/permissions.pyresolves permission requests.permission_policy - —
ag2/acp/events.py,ACPPlan,ACPModeChange.ACPAvailableCommands - —
ag2/acp/testing.py,fake_acp_config.ACPTurn - ACP protocol: https://agentclientprotocol.com
- ——
ag2/acp/config.py+三个预设及其默认值。ACPConfig - /
ag2/acp/client.py/bridge.py—— ACP客户端、事件桥接、子进程生命周期。session.py - —— ACP更新→AG2事件的精确映射关系。
ag2/acp/mappers.py - ——
ag2/acp/permissions.py如何解析权限请求。permission_policy - ——
ag2/acp/events.py、ACPPlan、ACPModeChange。ACPAvailableCommands - ——
ag2/acp/testing.py、fake_acp_config。ACPTurn - ACP协议:https://agentclientprotocol.com