学习笔记 · Obsidian
Deep Agents Python:工具、后端、权限、沙箱与协议
执行能力分层
一个生产 Agent 的“能做什么”至少分四层,不能只看模型工具列表:
| 层 | 作用 | 是否直接接触外部世界 |
|---|---|---|
| 工具 | 调用业务函数、API、数据库或 MCP | 是,由工具实现决定 |
| 文件后端 | 为内置文件工具提供统一命名空间 | 取决于后端 |
| 解释器 | 在 Agent 循环内用 JavaScript 编排工具和数据 | 默认否,显式桥接后才是 |
| 沙箱 | 隔离文件系统并执行 shell | 是,但应与宿主隔离 |
安全判断必须沿完整调用链进行:模型可见工具 → 中间件 → 后端或外部服务 → 凭证与网络。只在提示词中写“不要访问敏感信息”不构成隔离。
工具面
自定义与 MCP 工具
Deep Agents 接受普通可调用对象、LangChain Tool、供应商原生 tool dict 和 MCP 工具。函数签名与 docstring 会形成模型可见 schema,因此名称、何时使用、参数含义和返回约束应写得具体。
框架内置工具:
| 工具 | 语义 |
|---|---|
| ls | 列目录及元数据 |
| read_file | 分页读文本,或返回多模态 content blocks |
| write_file | 创建或覆盖文件 |
| edit_file | 精确字符串替换 |
| delete | 删除文件或递归删除目录;需要 deepagents 0.7+ |
| glob / grep | 路径匹配与内容检索 |
| execute | 仅支持 shell 的后端可见 |
| task | 启动同步子代理 |
| write_todos | 仅加入 TodoListMiddleware 后可见 |
收窄工具优先于靠提示约束:只读 Agent 应使用 FilesystemMiddleware 的工具 allowlist;完全不需要的工具用 profile 的 excluded_tools 移除,既减少风险,也缩小每轮提示的 schema 体积。
后端选择
| 后端 | 持久化/隔离范围 | 适合 | 主要风险 |
|---|---|---|---|
| StateBackend | 同一 thread;由 checkpoint 延续 | 草稿、结果卸载、临时文件 | 大文件进入图状态会放大 checkpoint |
| StoreBackend | 跨 thread;namespace 决定隔离 | 用户记忆、长期资料 | namespace 错误会造成跨用户泄漏 |
| FilesystemBackend | 真实本地磁盘 | 本地开发、受控 CI、挂载卷 | 永久改盘、读取 secrets、路径逃逸 |
| LocalShellBackend | 真实磁盘 + 宿主 shell | 可信的本地开发工具 | 任意命令、资源耗尽、不可逆修改 |
| ContextHubBackend | LangSmith Hub repo | 有版本历史的持久上下文 | 并发提交冲突、依赖远端服务 |
| CompositeBackend | 按路径前缀路由 | 临时区与持久区混合 | 路由/权限作用域配置复杂 |
| SandboxBackend | 隔离文件系统 + execute | 生产代码执行、数据分析 | 上下文注入、网络外泄、资源成本 |
关键规则
- 默认 StateBackend 在线程内持久,父子代理共享其中的文件。
- 多用户 StoreBackend 必须显式用 user_id、tenant_id 或 assistant_id 构造 namespace;不要依赖旧的 assistant 默认命名空间。
- FilesystemBackend 设置 root_dir 本身不是安全边界。需要路径约束时必须启用 virtual_mode;否则绝对路径、上级路径等仍可能越界。
- LocalShellBackend 即使 virtual_mode 为真也不安全,因为 shell 命令仍可访问宿主任意可达路径。
- 单独使用 FilesystemBackend 会把 /large_tool_results/、/conversation_history/ 等内部文件写进真实项目目录。通常用 CompositeBackend:项目路由到磁盘,默认路由仍用 StateBackend。
- CompositeBackend 以最长前缀匹配;聚合 ls、glob、grep 时保留原始虚拟路径。
- 自定义后端应返回带 error 的结构化结果而不是抛出异常;是否实现 delete / execute 决定对应工具是否向模型暴露。
文件权限
permissions 是面向内置文件工具的声明式规则:
- operations:read 或 write。
- paths:glob 路径。
- mode:allow、deny 或 interrupt。
- 从上到下匹配,第一个命中项生效。
- 没有规则命中时默认允许。
因此“只允许工作区”的安全配置必须有末尾全局 deny;具体敏感文件 deny 要放在较宽工作区 allow 之前。
覆盖范围:
- read 包含 ls、read_file、glob、grep。
- write 包含 write_file、edit_file、delete。
- 不覆盖自定义工具、MCP 工具。
- 不覆盖沙箱中的 execute;shell 可以绕过路径级规则。
- 子代理默认继承父权限;子代理声明 permissions 时是整体替换,不是追加。
- interrupt 模式与 interrupt_on 合并,使用同一审批/恢复流程。
目录 delete 会保守检查目标和所有后代:任一路径拒绝,整次删除失败,不执行部分删除。对 CompositeBackend + sandbox 默认路由,权限路径必须落在明确的非沙箱路由下,否则路径规则无法约束 shell,应直接拒绝这种配置。
permissions 适合路径 allow/deny;内容审查、速率限制、审计日志等使用 backend policy hook 或包装器。
沙箱
为什么仍需要沙箱
Agent 生成的命令不可完全预测。沙箱将文件、进程和宿主隔离,并提供 execute。它保护宿主,但不自动解决两类问题:
- 间接提示注入仍可让 Agent 在沙箱内部执行任意动作。
- 如果网络开放,沙箱中的数据仍可通过 HTTP、DNS 等外传。
两种集成模式
| 模式 | Agent 在哪里 | 优点 | 成本 |
|---|---|---|---|
| Agent in sandbox | Agent 与工具都在容器/VM 内 | 与本地运行相似、耦合紧密 | 模型/API 凭证进入沙箱;更新镜像和通信层更重 |
| Sandbox as tool | Agent 在服务端,远程调用沙箱 backend | Agent 状态和执行解耦;凭证留在外部;失败隔离好 | 每次调用有网络延迟 |
官方示例以“沙箱作为工具”为主。生产通常也更容易做凭证隔离和多沙箱并发。
生命周期
- thread-scoped:每个会话一个沙箱;同一 thread 重用,TTL 后回收。默认隔离更强。
- assistant-scoped:同一 assistant 的多个 thread 共用;适合复用已克隆仓库和依赖,但状态会持续累积,必须设置 TTL、快照或清理。
沙箱是计费资源,创建、查找、过期与销毁都要可观测;不要把“对话结束”当作一定会触发清理。
两个文件平面
- Agent 文件工具:模型在任务中读写、搜索、执行。
- upload_files / download_files:应用在边界外播种输入与取回产物。
两者用途不同。应用上传项目、技能脚本和输入数据;Agent 在容器内工作;应用最后下载报告、构建产物或需要回写的记忆。
凭证
不要把 API key、token、数据库凭证放进沙箱环境变量、挂载文件或 secrets。Agent 能读到的秘密,提示注入者也有机会读取并外传。
首选:
- 把认证动作封装为沙箱外的窄工具。
- 使用网络代理在出站请求处注入凭证,沙箱只看到普通 URL。
- 不需要网络时直接阻断网络;需要时做目标白名单。
- 把沙箱产物视为不可信输入,输出进入业务系统前再校验。
解释器
解释器是 QuickJS 内存运行时,为模型增加 eval 工具。它适合循环、分支、重试、聚合和批量编排,不等于操作系统沙箱。
| 需求 | 推荐 |
|---|---|
| 一两个简单外部调用 | 普通工具调用 |
| 纯 JavaScript 计算、循环、聚合 | 解释器 |
| 大量工具调用,先处理再只返回摘要 | 解释器 + PTC |
| 大量独立语言任务或多视角验证 | 解释器 + 动态子代理 |
| shell、安装依赖、测试、真实文件系统 | 沙箱 |
默认 QuickJS 没有文件、网络、shell、包管理器和时钟。外部能力来自两个显式桥:
- PTC:将 allowlist 工具以 tools.* 暴露给 JavaScript。
- task:配置子代理后,在解释器中动态调度。
重要边界:
- PTC 调用不走常规模型工具调用路径,因此 interrupt_on 不会逐次审批。
- 动态 task 调度同样不会让父 Agent 对每次派发执行常规审批;如需审批,应闸住 eval。
- 解释器与 Python 进程同进程,不是宿主内存隔离边界;不可信代码仍应放入独立 worker 或容器。
- thread / turn / call 三种持久模式只保存可序列化内存;恢复快照不会回滚外部工具已产生的副作用。
- 默认有内存、单次执行超时、返回字符数和最大 PTC 调用数限制,应保留并按风险收紧。
多模态
read_file 可把图片、音频、视频、PDF/PPT 等转为标准 content blocks,但最终可用性由模型的 MIME/模态能力决定。
上下文压缩以文本为主:
- 卸载只计算文本 token;不会按图片体积自动压缩图片。
- 摘要会把旧消息压成纯文本,旧媒体块不会保留,只剩摘要模型写出的描述。
长任务应把媒体放入后端或对象存储,以路径/URL 引用;大二进制工具结果返回简短说明和引用;图像密集分析可交给子代理,只将文字结论返回主上下文。
ACP、MCP 与 A2A
| 协议 | 连接对象 | 在 Deep Agents 中的用途 |
|---|---|---|
| ACP | 代码编辑器 ↔ Agent | 通过 stdio 把自定义编码 Agent 接入 Zed、JetBrains、VS Code、Neovim 等 |
| MCP | Agent/客户端 ↔ 工具与上下文服务器 | 加载外部 tools、resources、prompts;支持 HTTP/stdio、认证和会话 |
| A2A | Agent ↔ Agent | Agent Server 的标准 JSON-RPC 通信、SSE 流和 Agent Card |
MCP 要点
- MultiServerMCPClient 默认无状态:每次工具调用建立新 session;真正有状态的服务需显式 client.session。
- stdio 进程本身可持续,但未显式管理时工具调用仍按无状态会话处理。
- 工具业务错误默认转成 status=error 的 ToolMessage,模型可据此调整;传输、session、内容转换错误仍抛异常。
- structuredContent 进入 artifact;需要让模型看到时再通过 interceptor 有选择地加入消息,避免无谓膨胀。
- interceptor 可读取 runtime context、state 和 store,动态加 header、重试或短路;多个 interceptor 按洋葱顺序执行。
- elicitation 允许 MCP 服务在执行中向用户请求输入,结果区分 accept、decline、cancel。
A2A 要点
Agent Server 暴露 /a2a/{assistant_id},支持 message/send、message/stream、tasks/get,并自动提供 Agent Card。兼容 Agent 的 state 必须有 messages。
contextId 表示对话连续性,taskId 表示单个请求。首轮省略,由服务端生成;后续携带。Agent Server 会把 contextId 映射成 LangSmith thread_id。跨框架 Agent 要显式传播同一 thread_id 才能在一条分布式 trace 中观察完整链路。
生产安全基线
- Web/API 服务不直接使用 FilesystemBackend 或 LocalShellBackend。
- 生产代码执行使用独立沙箱,默认 thread 级隔离、TTL 和网络限制。
- 内置工具做 allowlist,路径规则采用“具体 deny → 具体 allow → 全局 deny”。
- 自定义/MCP 工具单独做授权、输入校验、审计和幂等,不能误以为 permissions 已覆盖。
- PTC 与动态子代理视为独立能力桥,审批策略必须覆盖 eval。
- StoreBackend namespace 必须从已认证 runtime 身份生成,不接受模型或用户消息直接提供。
- 所有下载产物、MCP structured content 和沙箱输出在进入下游系统前视为不可信数据。