CHAPTER 10 · STAGE 02 · 追踪完整对话
Capability Seam:为什么 Bash、FS 等能力能替换实现
如果 Tool 直接调用 Node childprocess,那么把执行环境迁移到 E2B 或受限沙箱时,所有 Tool 都要重写。DeepSeek Harness 把执行能力放在抽象 Service 后面,让 Tool 依赖能力接口,而不是具体环境。从“在本机运行 Bash”到“在远程沙箱运行 Bash”
如果 Tool 直接调用 Node child_process,那么把执行环境迁移到 E2B 或受限沙箱时,所有 Tool 都要重写。DeepSeek Harness 把执行能力放在抽象 Service 后面,让 Tool 依赖能力接口,而不是具体环境。
一条完整 Capability Seam 通常有三种职责:Service Definition 声明能力;Service Provider 实现能力;Consumer 使用能力,常见 Consumer 是面向模型的工具。
只有接口而没有 Provider 和 Consumer,不算完成一条能力链。只有一个 Tool 内部硬编码实现,也没有形成可替换能力。
Bash 的完整纵向切片
Service Definition:dsh-shell
它声明 Shell 请求、解析后的执行规格、结果和 ctx.shell API。消费者知道怎样请求执行,却不知道最终由哪个进程后端完成。
源码:packages/shell/shell/src/index.ts 与 packages/shell/shell/src/types.ts。
Service Provider:dsh-bash-local
它把 Shell 请求解析成本地执行规格,再通过 Subprocess 能力创建进程树。Provider 拥有本地环境、进程退出、信号与结果收集细节。
源码:packages/shell/bash-local/src/index.ts。
Consumer:dsh-tool-bash
它向模型注册 Bash Tool Schema,校验模型参数,通过 ctx.shell 执行,并把结果渲染成模型内容和 UI intent。
源码:packages/shell/tool-bash/src/index.ts。
替换 Provider 时,Tool 继续调用相同 ctx.shell。AgentLoop 更不需要知道 Bash 在哪里运行。
显式 resolve 与 run
跨包边界中的默认值应该由拥有实现的 Provider 在 resolve(request) 阶段显式物化,再由 run(spec) 执行。
这样调用日志、策略和测试都能看到实际生效的规格。把 request.timeout ?? default 隐藏在执行函数深处,会让配置、诊断和重放难以确认真实参数。
Shell 的 request/spec 分离是仓库推荐模板。
Filesystem 能力链
FS 同样分为 Definition、Provider 和 Consumer:
- Definition:
packages/fs/fs - 本地 Provider:
packages/fs/fs-local - 模型工具 Consumer:
packages/fs/tool-fs - 观察策略:
packages/fs/fs-observation-policy - 沙箱适配:
packages/fs/fs-sandbox
文件 Tool 不应该假设路径一定属于宿主机。Provider 和沙箱层决定实际执行世界与访问规则。
Subprocess、Shell、Terminal 为什么分开
Subprocess 是通用进程创建与进程树管理能力。Shell 在它上面提供一次性 Bash/Pwsh 执行语义。Terminal 则拥有可持续多次交互的 PTY session。
虽然三者都涉及进程,但生命周期和消费者不同。把它们合并成一个巨型 Service,会让一次性命令、持久终端和底层进程控制互相泄漏细节。
Sandbox 不等于 Permission
Permission 决定某个 Tool Call 是否允许、拒绝或询问用户。Sandbox 决定已经允许的进程在操作系统层面能访问哪些资源。
二者可以同时存在:权限策略允许“运行测试”,沙箱仍限制命令只能访问工作区;权限拒绝则根本不会进入执行。
因此不能把“有用户确认”当成“无需沙箱”,也不能把“有沙箱”当成“所有 Tool Call 都应该自动允许”。
单 Provider 与多 Provider 注册表
Shell 在一个 Context 中通常只需要一个当前 executor,因此通过单 Service 表达。
LLM、Subagent 和 Web Search 等能力可能需要多个 Provider 同时存在,注册表按名字保存实现,再根据请求中的 provider id 路由。设计能力时要先判断部署是否需要并存,而不是机械复制某个现有包的结构。
Capability 事件与核心循环事件
能力自身的策略应该挂在能力事件上。例如文件观察策略监听 fs 域,工具权限监听 tools/pre-execute。只有确实属于 Agent admission、请求或 Turn 生命周期的行为才挂 agent 事件。
这让能力可以被其他调用者复用。若所有 Shell 限制都写进 AgentLoop,非 Agent 的 Shell Consumer 就无法得到相同保证。
设计一条新能力链时问什么
- 能力的稳定 API 是什么?
- 哪些字段来自调用请求,哪些默认值由 Provider resolve?
- 是否允许多个 Provider 并存?
- Consumer 是模型 Tool、人类命令、内部插件还是协议入口?
- 取消、资源所有权和 teardown 谁负责?
- 哪些策略需要事件扩展点?
- 怎样做真实组合测试证明 Definition、Provider、Consumer 能一起工作?
本篇练习
- 为“天气查询”设计 Definition、两个 Provider 和一个 Tool Consumer,只写职责与接口,不写实现。
- 解释为什么 E2B overlay 可以替换 FS/Subprocess Provider,同时保留许多相同 Tool。
- 画出
tool-bash → ctx.shell → bash-local → ctx.subprocess → subprocess-local。 - 分别给 Permission 和 Sandbox 举一个只属于自己的规则。
验收标准:看到一个 tool-* 包时,能主动寻找它消费的 Service;看到一个 *-local 包时,能找到它实现的 Definition。