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 就无法得到相同保证。

设计一条新能力链时问什么

  1. 能力的稳定 API 是什么?
  2. 哪些字段来自调用请求,哪些默认值由 Provider resolve?
  3. 是否允许多个 Provider 并存?
  4. Consumer 是模型 Tool、人类命令、内部插件还是协议入口?
  5. 取消、资源所有权和 teardown 谁负责?
  6. 哪些策略需要事件扩展点?
  7. 怎样做真实组合测试证明 Definition、Provider、Consumer 能一起工作?

本篇练习

  1. 为“天气查询”设计 Definition、两个 Provider 和一个 Tool Consumer,只写职责与接口,不写实现。
  2. 解释为什么 E2B overlay 可以替换 FS/Subprocess Provider,同时保留许多相同 Tool。
  3. 画出 tool-bash → ctx.shell → bash-local → ctx.subprocess → subprocess-local。
  4. 分别给 Permission 和 Sandbox 举一个只属于自己的规则。

验收标准:看到一个 tool-* 包时,能主动寻找它消费的 Service;看到一个 *-local 包时,能找到它实现的 Definition。