Deep Agents 12. Deep Agents Code 与 ACP 协议


前面十一篇文章一直站在“怎样用 SDK 构建 Agent”的角度讨论 Deep Agents。这一篇把视角切换到产品和协议:如果希望直接在终端使用一个编码 Agent,或者让编辑器接入自己编写的 Deep Agent,deepagents、Deep Agents Code 和 ACP 分别负责什么?ACP、MCP、A2A 又应该怎样组合?

先说明成熟度边界:PyPI 将 deepagents-code 标记为 Beta,deepagents-acp 仍是快速演进的早期适配器。本文通过锁定版本、阅读安装包源码和真实 stdio 子进程测试说明锁定版本的行为,但不把它们描述成已经稳定的长期契约。

本文完成四项可复核工作:

  1. 隔离安装 deepagents-code==0.1.41,检查真实 CLI、SDK 依赖和运行方式;
  2. 梳理 Code 产品怎样组合 Memory、Skills、SubAgent、Sandbox、审批与会话恢复;
  3. 使用 deepagents-acp==0.0.9 建立可独立运行的 ACP stdio Server;
  4. 用真实子进程验证初始化、Session、Prompt、事件、权限、路径隔离和取消。

1. Deep Agents Code、SDK 与 ACP 的定位

1.1 Code 是产品,SDK 是 Harness

deepagents 是 Agent Harness:它在 LangChain Tool Calling 循环和 LangGraph Runtime 之上,预装任务规划、文件系统、上下文管理、SubAgent 等能力。开发者通过 create_deep_agent() 构建自己的 Agent。

Deep Agents Code 是基于这个 Harness 组装好的终端产品。它增加了终端 UI、会话恢复、Web Search、项目 Memory、Skills、SubAgent、远程 Sandbox、非交互模式、人工审批和配置管理。目标是“直接让 Agent 在项目中工作”时使用 Code;目标是“构建企业数据分析 Agent”时仍应使用 SDK。

ACP,即 Agent Client Protocol,是客户端与 Agent 之间的协议。它定义 Session、Prompt、增量更新、Tool 展示、权限请求和取消,不负责替模型思考,也不定义业务 Tool 怎样实现。

层次 主要对象 本文对应实现
产品 TUI、线程、配置、Sandbox、审批 dcode
Agent Harness Model、Middleware、Tool、State、SubAgent deepagents
协议适配 Session、Prompt、Update、Permission deepagents-acp
协议 Schema/Transport JSON-RPC、Content Block、stdio agent-client-protocol

1.2 ACP、MCP 与 A2A 连接不同的边

最容易记住、也最容易被过度简化的一句话是:ACP 连接客户端,MCP 连接工具,A2A 连接 Agent。更准确的区别是:

  • ACP 让编辑器或其他交互客户端控制 Agent 会话,并接收结构化执行过程;
  • MCP 让 Agent Host 发现并调用 Tool、Resource 和 Prompt;
  • A2A 面向独立 Agent 服务之间的任务、消息和产物协作。

三种协议可以同时出现在一个系统里,但协议名字相同或都使用 JSON,并不代表可以互相替代。

ACP、MCP 与 A2A 的连接边界

1.3 本文锁定环境与版本证据

本文同时涉及系列示例环境和 Code 产品环境,必须隔离:

环境 锁定版本 证据与用途
Python 3.12.11 本机实测
deepagents 0.6.12 系列示例主线
deepagents-acp 0.0.9 requirements-lock.txt 与安装元数据
agent-client-protocol 0.11.0 ACP Python SDK,协议常量为 1
deepagents-code 0.1.41 本机独立安装
Code 内部 deepagents 0.7.0a7 dcode –version 实测

实际输出为:

deepagents-code 0.1.41
deepagents (SDK) 0.7.0a7

这张表同时揭示了环境隔离问题:Code 内部使用预发布 SDK,不能安装进系列锁定的 .venv_deepagent,否则前十一篇的稳定回归环境会被升级。

协议版本相同也不代表实现完全兼容。两个实现都报告 protocolVersion=1,仍可能在 Content Block、Session Resume、Permission Option、Tool Update 粒度和实验性扩展方面不同。必须同时记录协议、SDK、适配器、Agent、客户端和模型版本。

2. 安装和运行 Deep Agents Code

2.1 使用独立工具环境安装

官方快速安装命令是:

curl -LsSf https://langch.in/dcode | bash

这适合个人快速体验,但生产环境不应直接把网络响应送入 Shell。至少先下载、计算摘要、人工或自动审查,再执行:

installer="$(mktemp)"
curl -LsSf https://langch.in/dcode -o "$installer"
shasum -a 256 "$installer"
less "$installer"
bash "$installer"
rm -f "$installer"

安装脚本没有经过本文验证的 DEEPAGENTS_CODE_VERSION 参数,因此不使用“给 curl 设置版本环境变量再 pipe 给 bash”的写法。Shell 中 VAR=value curl … | bash 只把变量传给 curl,也不会自动传给管道另一侧的 bash。

本文使用可验证的 PyPI 版本和 uv tool 隔离安装:

tool_root="$(mktemp -d /tmp/deepagents-code-tool.XXXXXX)"
bin_root="$(mktemp -d /tmp/deepagents-code-bin.XXXXXX)"

UV_TOOL_DIR="$tool_root" \
UV_TOOL_BIN_DIR="$bin_root" \
uv tool install 'deepagents-code==0.1.41'

"$bin_root/dcode" --version

团队构建还应把 wheel、依赖锁文件和摘要保存在制品仓库,以免安装同一顶层版本时解析出不同的间接依赖。

Deep Agents Code 的本地与 Sandbox 运行边界

2.2 doctor 检查的是哪一个环境

dcode doctor 会输出版本、平台、Commit、安装位置、Tracing 和配置目录:

home="$(mktemp -d /tmp/dcode-home.XXXXXX)"
HOME="$home" "$bin_root/dcode" doctor
rm -rf "$home"

核心输出如下:

Diagnostics ✓
├ deepagents-code: 0.1.41
├ deepagents (SDK): 0.7.0a7
├ Commit hash: d46a2cb
├ Python: 3.12.11
├ Platform: darwin-arm64
└ Install method: other

隔离 HOME 的目的只是验证命令能在干净目录启动,它不会读取真实用户配置、凭据或 Session 数据。排查真实配置问题时,要在真实 HOME 下执行 dcode doctor 和 dcode config show,并在分享输出前脱敏。

2.3 三种运行方式与权限范围

交互模式直接启动 TUI:

dcode

非交互模式适合 CI:

dcode -n "运行测试并解释失败原因" \
  --max-turns 10 \
  --timeout 120 \
  -S "pytest,git"

当前 0.1.41 的真实帮助信息表明:–timeout 超时时退出码为 124;本地 Shell 默认不开放;-S 是 Shell 命令允许列表。允许 python、node、bash 等解释器仍然等于允许执行大量任意逻辑,所以 Allowlist 不是 Sandbox。

ACP Server 模式由编辑器或 ACP Client 启动:

dcode --acp

-y/–auto-approve 与 -S all 作用范围不同:前者自动批准交互模式中的 Tool Call,后者允许非交互模式调用任意 Shell 命令。它们都需要谨慎使用,但不能混写成同一个开关。对不可信仓库,应优先使用远程 Sandbox,而不是扩大本机权限。

2.4 配置、Memory、Skills 与 SubAgent

用以下命令查看有效配置及来源:

dcode config show --verbose
dcode config get interpreter.memory_limit_mb
dcode config path
dcode doctor

模型解析要结合当前 CLI 帮助和 config show 判断。显式 –model 用于本次运行,默认模型和最近模型由 Code 的配置与状态管理,Provider 凭据还可能触发自动发现。不要只看一个环境变量就断言最终模型。

Code 使用两套数据目录:

  • ~/.deepagents/ 和项目 .deepagents/ 存放 Code 专用配置、Agent、Skills、Memory 与状态;
  • ~/.agents/ 和项目 .agents/ 存放可以被其他 AI 工具复用的 Skills。

项目指令会合并包内基础 Prompt、用户级 AGENTS.md、项目 .deepagents/AGENTS.md 与项目根 AGENTS.md。Memory 应保存构建命令、目录职责和安全规则等稳定事实,不应保存一次任务的临时 Todo。

项目 Skill 可以放在 .deepagents/skills//,通用 Skill 可以放在 .agents/skills//。SubAgent 则使用 .deepagents/agents//AGENTS.md;同名项目 SubAgent 的优先级高于用户级定义。Code 当前文档说明同步 SubAgent 可用,异步 SubAgent 是否可用必须按所装版本重新核对,不能从 SDK 能力直接推导产品能力。

2.5 Sandbox 不是“换台机器跑命令”

dcode --sandbox daytona
dcode --sandbox daytona --sandbox-id existing-id

远程 Sandbox 的价值是建立本机与不可信代码之间的边界。仍需限制 CPU、内存、磁盘、网络出口、凭据、最长运行时间和产物下载。使用 –sandbox-id 附着已有环境时,Code 通常不会为本次任务创建和销毁它;旧进程、文件、环境变量与缓存可能污染新任务,必须额外设计清理和过期策略。

3. ACP 的核心模型

3.1 Client、Agent 与 stdio Transport

本文的四个角色如下:

角色 本文实现 职责
ACP Client RecordingClient 发送请求、记录 Update、回答 Permission
ACP Agent AgentServerACP 管理 Session,桥接 ACP 与 LangGraph
Transport SDK 的 stdio Transport 按 ACP Python SDK 的 JSON-RPC framing 收发数据
Deep Agent/Graph CompiledStateGraph 执行模型、Tool 和状态流转

不要手工用 print(json.dumps(…)) 拼协议。SDK 会处理字段别名、联合类型、请求 ID 和连接关闭;手写报文极易在 Schema 升级后失效。

stdio Server 的 stdout 只能输出协议 Frame,日志必须进入 stderr:

import logging

logging.basicConfig()  # 默认写入 stderr
logging.getLogger(__name__).info("server started")

这个规则必须在编写 Server 前确定。第三方库如果把进度条或调试信息写到 stdout,Client 可能报 JSON 解析错误或连接关闭。

3.2 Session 与 Prompt Turn

ACP Session 与 Prompt Turn 生命周期

一个最小 Turn 包含:

  1. initialize 协商协议版本和 Capability;
  2. session/new 创建 Session,并提交工作目录;
  3. session/prompt 发送 Content Block;
  4. Agent 通过 session/update 回传文本、计划和 Tool 进度;
  5. 敏感 Tool 通过权限请求等待客户端决策;
  6. Prompt 返回 Stop Reason;客户端也可发送 session/cancel。

Session 与 Prompt Turn 不是同一生命周期。一个 Session 可以包含多个 Turn;取消当前 Turn 不等于关闭、删除或吊销 Session。生产系统还应明确 Session 的关闭、过期、轮换、Fork/Resume 所有权与重新认证规则。

3.3 Capability、Adapter 和模型能力必须分开

当前安装的 deepagents-acp==0.0.9 在 initialize 中声明:

PromptCapabilities(image=True)

这只证明适配器对 ACP 图片 Content Block 提供协议转换路径,不证明实际模型能看图,也不授权 Agent 读取任意本地图片。本文测试只覆盖文本 Prompt,没有做端到端视觉断言,因此不把 image=True 写成“已验证图片理解”。

当前 ACP SDK Schema 还包含其他 Content Block;是否可以使用,要分别检查协议版本、客户端、适配器转换、模型和安全策略。本文未验证音频链路,所以不宣称支持音频。

Session Update 同样要分三层描述:

能力层 示例 本文结论
ACP Schema 定义 message、plan、tool、usage 等 Update 类型 代表协议可表达
0.0.9 Adapter 发出 取决于 LangGraph Stream 和适配代码分支 需要源码与场景验证
本文自动化测试收到 agent_message_chunk、Permission Request 已实测

agent_thought_chunk 即使出现,也只应称为可公开的分析/状态信息,不能暗示客户端拿到了模型私有 Chain-of-thought。

3.4 Permission 是结构化决策,不是聊天确认

ACP 的 Permission Response 选择 Server 本次请求提供的 optionId。approve、reject 和 approve_always 是 deepagents-acp==0.0.9 当前适配器生成的选项 ID,不是所有 ACP Agent 必须实现的通用枚举。

客户端不能固定返回 approve,而应先验证这个选项确实存在:

async def request_permission(self, **kwargs):
    option_ids = {option.option_id for option in kwargs["options"]}
    if self.decision not in option_ids:
        raise ValueError(f"permission option was not offered: {self.decision}")
    return RequestPermissionResponse(
        outcome=AllowedOutcome(
            outcome="selected",
            optionId=self.decision,
        )
    )

Capability 不是授权,用户批准也不是访问控制。可靠权限链至少包含:认证用户、Session 所有权、Policy、参数摘要、HITL、Tool 内部强制检查、Sandbox 与审计。

ACP 权限决策与副作用边界

3.5 Cancel 是控制面信号

session/cancel 表示客户端不再希望当前执行继续。本文安装的适配器在流式循环中检查取消标记,测试得到 stop_reason=”cancelled”。这不等于 Provider HTTP 请求、子进程、Sandbox 命令或已发出的数据库写入一定停止,更不等于副作用回滚。

因此还要为下游调用设置超时和取消传播,为子进程设置进程组终止,为写操作使用幂等键、事务、状态查询与补偿操作。当前 0.0.9 的取消标记还是适配器实例级状态;多 Session 并发取消行为必须在真实部署拓扑中单独回归。

4. 使用 deepagents-acp 暴露 Deep Agent

4.1 可独立运行的 Server

examples/p12_code_acp/acp_server.py 的正式入口如下:

def build_server() -> AgentServerACP:
    # 独立 stdio 进程不会像 Agent Server 那样注入 checkpointer。
    agent = create_deep_agent(
        model=build_model(),
        checkpointer=InMemorySaver(),
    )
    return AgentServerACP(agent=agent)


if __name__ == "__main__":
    demo_kind = os.environ.get("P12_ACP_DEMO")
    server = build_demo_server(demo_kind) if demo_kind else build_server()
    asyncio.run(run_agent(server))

这里必须传具体 Checkpointer。checkpointer=True 表示期待运行时注入持久层;独立 stdio 进程没有该注入,可能出现握手成功但 Prompt 返回 Internal error。教学示例使用 InMemorySaver,生产环境再替换为经过同一 ACP/HITL 测试的持久 Saver。

4.2 为什么保留确定性 Graph

协议测试要验证子进程、JSON-RPC、Content Block、Session Update 和权限,而不是赌模型今天会不会调用 Tool。本文保留一个真实 LangGraph,只把输出设计成确定性结果:

def build_echo_graph():
    def reply(state: MessagesState) -> dict[str, list[AIMessage]]:
        content = state["messages"][-1].content
        if not isinstance(content, str):
            text_parts = [
                str(block.get("text", ""))
                for block in content
                if isinstance(block, dict) and block.get("type") == "text"
            ]
            content = "".join(text_parts)
        return {"messages": [AIMessage(content=f"ACP echo: {content}")]}

    builder = StateGraph(MessagesState)
    builder.add_node("reply", reply)
    builder.add_edge(START, "reply")
    builder.add_edge("reply", END)
    return builder.compile(checkpointer=MemorySaver())

这里只接受明确的文本块,不对未知字典或任意对象调用 str() 后当成用户正文。

4.3 自定义 Tool 必须自己做路径授权

自定义 save_note 不会自动继承内置文件 Backend 的权限。修正后的实现要求从可信部署配置提供工作区根目录,再拒绝路径穿越:

@tool
def save_note(path: str, content: str) -> str:
    """Save a UTF-8 note below the trusted workspace root."""
    root_value = os.environ.get("P12_ACP_WORKSPACE")
    if not root_value:
        raise RuntimeError("P12_ACP_WORKSPACE is required")

    root = Path(root_value).resolve(strict=True)
    requested = Path(path)
    target = requested.resolve() if requested.is_absolute() else (root / requested).resolve()
    if not target.is_relative_to(root):
        raise ValueError(f"path escapes workspace: {path}")

    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content, encoding="utf-8")
    return f"saved:{target}"

Path.is_relative_to() 在本系列 Python 3.12 中可用。这段代码能阻止常见 ../ 和符号链接逃逸,但高安全场景还要防止检查后路径被替换的 TOCTOU 问题,使用目录文件描述符、容器挂载和操作系统权限建立更强边界。

4.4 真正从子进程连接

测试不是直接调用 AgentServerACP.initialize(),而是启动真实子进程:

async with spawn_agent_process(
    client,
    sys.executable,
    "-m",
    "p12_code_acp.acp_server",
    env=env,
    cwd=os.getcwd(),
) as (connection, process):
    initialized = await connection.initialize(PROTOCOL_VERSION)
    session = await connection.new_session(cwd=os.getcwd())
    response = await connection.prompt(
        session_id=session.session_id,
        prompt=[text_block("hello protocol")],
    )

链路实际经过 ClientSideConnection -> stdin -> Python 子进程 -> AgentServerACP -> LangGraph -> stdout -> Client。测试结束后还检查子进程退出码为 0,stderr 不包含 Traceback,从而捕获 stdout 污染、导入失败和粗暴退出。

客户端提交的 cwd、附加目录和 MCP 配置都属于外部输入。生产 Server 要将规范化路径限制在允许根目录,并把 Session 与经过认证的用户、租户和 Workspace 绑定。

5. ACP 协议回归测试

5.1 九个测试项覆盖了什么

执行:

cd source/_posts/deepagent/examples

.venv_deepagent/bin/python -m compileall -q p12_code_acp
.venv_deepagent/bin/ruff check p12_code_acp
.venv_deepagent/bin/python -m pytest p12_code_acp -q

实测结果:

All checks passed!
.........                                                                [100%]

这是 9 个 Pytest Test Item,不是“完整覆盖 ACP 的 9 类能力”。它们验证:

  1. 正式 Server 与 Demo Server 可构造;
  2. 非法 Demo 类型被拒绝;
  3. 真实 initialize 与 session/new 握手;
  4. 真实 Prompt 回传 agent_message_chunk;
  5. session/cancel 使当前慢 Turn 返回 cancelled;
  6. Client 拒绝选择 Server 未提供的 Permission Option;
  7. save_note 拒绝工作区外路径;
  8. approve 执行一次文件副作用;
  9. reject 阻止文件副作用。

权限测试还断言 Permission Request 中的 session_id 是当前 Session,tool_call.raw_input 与待执行路径、内容一致。它证明的是单 Session、单 Tool Call 场景下结构化决策控制副作用;它没有证明并发 Session、双 Tool Call、错误 Tool Call ID、Response 重放和参数变化后的批准失效。

5.2 演示输出

.venv_deepagent/bin/python -m p12_code_acp.run_demo

真实输出:

echo: protocol=1 stop=end_turn permissions=0 message=ACP echo: hello protocol
approve: stop=end_turn permissions=1 file_exists=True message=权限决策已经处理。
reject: stop=end_turn permissions=1 file_exists=False message=权限决策已经处理。

“拒绝”是正常业务结果,不是 Transport 或 Protocol 异常。Agent 应能接收拒绝结果并调整方案。

5.3 真实模型普通 Prompt 的 ACP stdio 验证

live_demo.py 不设置 P12_ACP_DEMO,因此 Server 内部创建真实在线 Deep Agent。使用 OpenAI 兼容网关时,网关返回模型 ID qwen3.7-plus,脱敏结果为:

{
  "scene": "p12_live_acp_stdio",
  "status": "passed",
  "model": "qwen3.7-plus",
  "latency_ms": 4912,
  "protocol_version": 1,
  "stop_reason": "end_turn",
  "permission_requests": 0,
  "final_answer": "ACP 在线会话成功"
}

这条在线记录只证明“真实模型普通文本 Prompt 经过 ACP stdio 完成”。permission_requests=0 清楚表明它没有验证真实模型 Tool Calling、HITL、Diff、MCP、Cancel、图片或 Session Resume;这些协议行为由确定性回归测试覆盖,不能把两类证据合并成更大的结论。

5.4 当前版本的已知边界

边界 来源 影响
自由格式 interrupt() 不能直接映射为固定 Permission UI 0.0.9 安装源码 应使用标准 HITL action_requests
Cancel 使用适配器内标记 源码与本文实测 不保证下游任务和副作用停止
approve_always 使用 Session 内缓存 源码 不是企业 Policy Engine
某些异步持久 Checkpointer 曾报告 Interrupt 状态竞态 Issue #4358,已关闭 不能从 MemorySaver 测试推导生产 Saver 可靠
嵌套 SubAgent HITL 到父 task Tool Call 的映射需按版本验证 Issue #4033 生产前必须增加嵌套场景测试

锁定安装源码包含 PR #4542 所描述的“离开流迭代后再读取 Interrupt State”处理,但这不替代真实持久 Saver 测试。升级适配器前后都应重跑 Permission、Cancel、进程重启、并发 Session 和嵌套 SubAgent 用例。

6. ACP、MCP 与 A2A 的系统边界

6.1 按连接双方和生命周期选择

维度 ACP MCP A2A
连接双方 Client/IDE ↔ Agent Agent Host ↔ Tool/Resource Server Agent Client ↔ 远程 Agent
核心对象 Session、Prompt、Update、Permission Tool、Resource、Prompt Agent 能力、Message、Task、Artifact
生命周期 人机 Session 与 Turn 能力发现和调用 独立远程任务与协作
本文传输 stdio 可为 stdio/HTTP,取决于 MCP 配置 未实现;以采用的 A2A 规范版本为准
本文验证 第 3 篇验证

假设用户在编辑器中提出“分析订单下降原因并生成修复补丁”:

  1. 编辑器通过 ACP 创建 Agent Session;
  2. Agent 通过 MCP 调用订单指标服务;
  3. Agent 通过 A2A 或业务任务协议委派远程数据 Agent;
  4. 主 Agent 修改文件,通过 ACP 回传 Tool 状态或 Diff;
  5. 编辑器用 ACP Permission 请求用户批准测试命令;
  6. Agent 在 Sandbox 中执行测试并回传结果。

6.2 ACP Session 中携带 MCP 配置

ACP Schema 可以让 session/new 携带 MCP Server 配置,但只有 Agent 声明并实现相应 Capability 时客户端才能使用。即便配置通过 ACP 送达:

  • ACP 只负责将配置交给 Agent;
  • Agent 内部仍建立 MCP Client;
  • MCP Server 仍按 MCP 协议初始化和调用;
  • MCP 的认证、授权、结果解析和生命周期仍独立存在。

stdio MCP 配置能启动本地进程,本身就是代码执行入口。HTTP MCP 还要验证 TLS、Server Identity、OAuth 流程、Redirect URI 和 Token 存储,不能因为它“只是工具配置”就降低信任要求。

6.3 什么时候可以把远程 Agent 包装成 MCP Tool

远程 Agent 完全可以包装为 MCP Tool。短时、单请求单响应、无独立状态、无取消、无 Artifact 的能力,这样做通常更简单。

当远程端需要能力发现、长任务状态、查询/取消、异步消息、独立 Artifact、跨组织认证时,包装成一个 Tool 会隐藏这些一等任务语义。这时应考虑 A2A 或明确的业务任务协议。不是“技术上不能包装”,而是包装后是否丢失系统需要的生命周期。

6.4 dcode –acp 还是自定义 Server

需求 推荐方案
直接把通用编码 Agent 接入兼容客户端 dcode –acp
使用 Code 的 Memory、Skills、Sandbox 和配置 dcode –acp
暴露自定义业务 Tool、State 和策略 自定义 AgentServerACP
自定义 Session 身份、审计和租户隔离 自定义 Server/适配器
面向通用业务方提供成熟 HTTP API 当前优先考虑 LangGraph Agent Server
远程 ACP Transport 按所采用的 ACP RFD/规范单独实现和验收

本文实现的是 stdio ACP,不把结论扩大成“ACP 永远只能本地使用”,也不把 stdio Server 直接暴露为公网 HTTP 服务。

7. 安全、生产治理与排错

7.1 Session ID 不是认证凭据

多用户系统至少要校验:

(tenant_id, user_id, session_id) -> session owner + workspace + policy

Session 需要创建、恢复、轮换、关闭、过期和吊销规则。Fork 与 Resume 必须重新检查所有权,不能因知道随机 ID 就获得访问权。stdio 的攻击面比公开网络服务小,但本机恶意进程、插件、环境变量和项目 Prompt Injection 仍然存在。

7.2 Permission、Policy 与副作用

权限 UI 至少展示 Tool 名、真实参数、工作目录、目标路径、Diff、网络目标和批准范围。参数进入终端、HTML 或 Markdown UI 前还要处理 ANSI/控制字符、HTML、Markdown 注入和双向文本控制字符。

本文安装的 0.0.9 为 Permission Request 生成 approve、reject、approve_always。其源码还会对 execute 做命令类型提取,并对部分危险 Shell 模式避免自动批准。这只是特定版本的缓解措施,不是 Shell 安全证明:同名二进制、PATH、环境变量、脚本内容、子命令和工作目录都可能改变真实效果。高风险操作不应提供永久批准。

企业权限缓存至少绑定用户、租户、Session、Tool Call、参数摘要、工作目录、可执行文件身份、策略版本和过期时间。参数一旦变化,旧批准必须失效;重复响应与跨 Session 响应必须拒绝。

7.3 审计与错误分层

建议记录但不泄露以下关联字段:

trace_id, tenant_id, user_id, session_id, prompt_turn_id,
tool_call_id, permission_decision, policy_version,
acp_protocol_version, adapter_version, agent_version,
client_implementation, model_id, session_mode, sandbox_id

API Key、Authorization Header、Prompt 中的个人信息、文件 Secret、Diff 和 Sandbox 环境变量需要脱敏。可观测性不是把所有内容永久记录下来。

错误至少分五层:

错误层 示例 推荐处理
Transport 子进程退出、Frame 损坏 重启,保留脱敏 stderr
Protocol 未知方法、参数不合法 JSON-RPC 结构化错误
Authorization/Policy Session 越权、Tool 被禁止 稳定拒绝码,不执行副作用
Agent 模型失败、上下文超限 可重试、换模型或结束 Turn
Tool/业务 文件不存在、外部 API 失败 Tool 结果或业务错误

7.4 编辑器/客户端配置

具体字段以客户端官方文档为准,通用原则是使用绝对解释器路径和固定工作目录:

{
  "name": "DeepAgent Python",
  "command": "/absolute/path/.venv_deepagent/bin/python",
  "args": ["-m", "p12_code_acp.acp_server"],
  "cwd": "/absolute/path/source/_posts/deepagent/examples",
  "env": {}
}

不要把 API Key 写进编辑器 JSON。配置可能被同步、备份、提交或收集进错误报告;凭据应由系统钥匙串、客户端 Secret Store 或受控启动器注入。

7.5 常见故障

Client 提示 Connection closed

检查解释器路径、模块导入、stdout 污染和模型初始化错误。直接运行 Server 后没有普通输出是正常的,因为它在等待 stdin 请求;诊断信息应看 stderr。

握手成功但 Prompt 返回 Internal error

确认图有具体 Checkpointer。独立 stdio 进程不能依赖 checkpointer=True 的运行时注入;先用 InMemorySaver() 验证,再接入持久 Saver。

权限窗口没有出现

确认敏感 Tool 在执行前由 HumanInTheLoopMiddleware 产生标准 action_requests,并检查实际持久 Saver 是否保存了 Interrupt。自由格式 interrupt() 不能自动等同于固定权限 UI。

dcode 找不到或模型不符合预期

uv tool dir --bin
dcode --version
dcode doctor
dcode config show --verbose

不要只根据 PATH 中存在一个 dcode 就判断运行的是目标环境。

8. 总结

Deep Agents Code、Deep Agents SDK 和 ACP 位于三个不同层次:Code 是可以直接使用的编码 Agent 产品,SDK 是构建 Agent 的 Harness,ACP 是客户端控制 Agent 会话的协议适配边界。

本篇最重要的结论有七点:

  1. Code 0.1.41 使用 deepagents 0.7.0a7,应与系列锁定的 deepagents 0.6.12 隔离;
  2. ACP 的完整链路包括 Session、Prompt、Update、Permission、Cancel 和 Stop Reason,不只是握手;
  3. Capability、适配器转换和模型真实能力是三件不同的事;
  4. 权限测试必须验证副作用,也必须绑定 Session、Tool Call 与参数;
  5. HITL、Policy、Tool 内访问控制和 Sandbox 是不同防线;
  6. ACP、MCP 与 A2A 分别服务不同连接边,远程 Agent 是否包装为 Tool 取决于是否需要独立任务语义;
  7. 协议版本匹配只代表可以协商 Schema,生产兼容性仍依赖锁版本、源码审查和端到端回归测试。

本文代码位于 examples/p12_code_acp/,配图的 .drawio 与 .png 均保存在 assets/ 并保持同名。下一篇将把 Backend、Sandbox、Skills、Memory、SubAgent、Middleware、Rubric 和人工审批组合成一个可复核的企业级数据分析智能体。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录