本章摘要:本章围绕 DeepSeek Harness 的「可替换能力」展开。先讲清 seam 的定义——接口(Service Definition)、实现(Service Provider)与消费方(Consumer)三者一体;再用「搬走文件系统即搬走 Bash、PTY、LSP」的例子说明依赖倒置带来的常数级改动收益;随后按执行与文件、模型与推理、会话与状态、外部世界与协作、界面与应用五组列出主要 seam 清单,并给出读能力图的方法(先想目标,再问「要替换哪一类」);最后强调一条纪律:一个包可兼任多角色,但必须说清楚,且添加能力意味着把三者一并设计。
这一章讲的是「物理」:这些逻辑背后真正干活的东西(读文件、跑命令、连模型、存数据)是怎么被组织起来的。理解这一章,你就能明白为什么这套架构敢说「每一个都可以从配置替换」。
8.1 什么是 seam:三个角色,一个接口
seam(接缝)是官方文档里一个高频词。它的定义是:
一个seam是一项可替换能力,包含三种角色:声明接口的Service Definition、实现它的Service Provider,以及使用它的Consumer(通常是面向模型的工具)。
8.2 最漂亮的一个例子:为什么搬走文件系统就等于搬走一切
官方文档里有一句话,值得单独成节,因为它是理解这套设计威力最快的路径:
文件系统与进程提供方共享同一个执行世界,因此把它们指向远程沙箱,也就把Bash、PTY 和 LSP 一并搬了过去,无需提供方专用 fork。
这句话拆开看是这样的:
官方还补充了一句关于 subagent 的:subagent 提供方在同一个接口之后同样千差万别——从「新建一个子 agent」,到「把一个轮次委派给另一个产品」。同一个接口,从进程内到跨产品都能覆盖。
8.3 主要 seam 一览
官方提供了一张完整的「能力图」,列出了所有服务、它们的角色、所属包、实现与消费方。下面把最常用的按功能分组摘出来——这张表可以当作你的「可替换性清单」。
执行与文件
| ctx 键 | 角色 | 负责什么 / 已知实现 |
|---|---|---|
ctx.fs | seam | 文件系统访问。实现:本地(fs-local)、沙箱内(fs-sandbox)、远程 SSH(fs-ssh)。消费方:tool-fs |
ctx.subprocess | seam | 进程启动的底层能力。实现:本地、远程 SSH。消费方很多:Bash 执行器、PTY shell 后端、LSP Host、以及进程外的 ACP / Codex / Claude Code subagent 后端 |
ctx.shell | seam | 面向模型的 shell 执行。实现:bash-local、bash-sandbox、pwsh-local。官方明确说:沙箱、远程或 PowerShell 执行器可以替换bash-local,而无需改动消费方 |
ctx.terminals | seam | 持久化的 PTY 会话注册表。实现:terminal-bash |
ctx.lsp | seam | 语言服务导航。只有恰好四种操作的标准化查询,不提供协议逃生口——后端必须转换为标准化请求和结果 |
ctx.sandbox | seam | 进程沙箱。实现:本地、远程 SSH。消费方交出「即将执行 spawn 的确切 argv」,后端按每次调用的策略包装它 |
ctx.sandboxPolicy | core | 统一保存部署默认模式和工作区根目录。只有沙箱执行器和提供方读它 |
ctx.approval | seam | 一次性权限决策。通过approval/requestwaterfall 分派;没有回答方时以unavailable关闭失败 |
ctx.permissionPresets | core | 面向用户的预设表(workspace-write/danger-full-access),把沙箱模式与审批策略选项组合在一起 |
模型与推理
| ctx 键 | 角色 | 负责什么 / 已知实现 |
|---|---|---|
ctx.llm | seam | 模型适配器注册表 + 流式调用。实现:llm-deepseek(官方直连)、llm-pi-ai(库支持)、llm-replay(测试回放) |
ctx.deepseekLlmApiExtensions | seam | 向官方 DeepSeek 请求添加顶层字段的提供方特定注册表。贡献方可以提交「交付状态」,且只会在 HTTP 成功后提交 |
ctx.tokenMeter | core | 按会话隔离的 token 计量。压力消费方共享不可变、带修订版本的测量结果 |
ctx.compaction | seam | 上下文压缩。当前实现compaction-basic。官方明确:不存在面向模型的压缩工具 |
ctx.toolResultPruner | core | 在摘要压缩之前,通过可回放的单节点表层替换来改写过大的当前工具结果 |
ctx.ptcRuntime | seam | PTC 的执行运行时。实现:Node 版、实验性 Python 版 |
会话与状态
| ctx 键 | 角色 | 负责什么 / 已知实现 |
|---|---|---|
ctx.sessions | core | 内存中的会话存储,拥有仅追加的 Session 实例并发出持久的会话事件流 |
ctx.sessionPersistence | seam | 持久化。实现:JSONL 后端(把 SessionEvent 词汇持久化为每个会话一份产物) |
ctx.sessionProjections | core | 状态驱动的折叠单元。各领域注册单元,host 消费方读单个类型化状态 |
ctx.sessionQuery | seam | 会话的精确读取、过滤和追踪。实现:SQLite 后端(还提供全文协调、排序、摘要片段和游标世代) |
ctx.storage | seam | 非会话存储的中枢。实现:JSON、SQLite。各后端以不同名称并列注册 |
ctx.sessionTitle | seam | 会话标题生成。负责确定性回退、最新标题折叠区、唯一的可选异步提供方注册 |
外部世界与协作
| ctx 键 | 角色 | 负责什么 / 已知实现 |
|---|---|---|
ctx.subagents | seam | 子 agent 的提供方注册表 + 可选的延续编排。实现很多:进程内新建、进程内 fork、ACP、Codex、Claude Code、dsh-sdk |
ctx.agentTeams | core | 实验性的 Agent Teams 协作域:隐式 Root roster、持久 peer mailbox、共享任务 DAG、可延续的 child 生命周期 |
ctx.jobs | seam | 后台任务注册表。生产方登记正在运行的工作;tool-jobs是面向模型的控制器 |
ctx.web | seam | web 检索与抓取。实现:Exa、Perplexity、DeepSeek 搜索、HTTP 抓取——都注册到同一个 seam,tool-web负责稳定的面向模型名称 |
ctx.skills | seam | 技能目录。合并提供方的 skill 目录;tool-skill渲染会话前缀目录并加载完整正文 |
ctx.mcpResources | seam | MCP 资源访问。连接所有者提供的操作在调用 agent 的作用域内服务于共享资源工具 |
ctx.browserUse | seam | 浏览器操作。实现:Playwright MCP、Chrome DevTools MCP、Stagehand 原生 |
ctx.computerUse | seam | 计算机操作(桌面自动化)。实现:CUA Driver 的 MCP 版与原生版 |
ctx.webhookRuntime | core | webhook 规则运行时。把可信插件注册的规则结果转换为普通的、由 Workspace 支撑的 Session。不保留交付或完成状态 |
ctx.schedule | core | 独立于 Session 的定时任务:存储任务并把到期消息排入原 Session |
ctx.commands | core | 面向人的命令注册表。这类命令无需模型轮次即可分派 |
界面与应用
| ctx 键 | 角色 | 负责什么 |
|---|---|---|
ctx.agents | core | 实时 Agent 句柄、创建/恢复工厂 seam、进程本地的发起方传播 |
ctx.agentLoop | bundle | **唯一的具体循环插件。**官方特别注明:扩展包依赖dsh-agent的事件和服务,而不依赖此包 |
ctx.agentPresets | core | 按会话的 agent 组合:立即挂载 YAML 声明的 preset 版本,并保留被替换的版本直到最后一个使用者释放它 |
ctx.webServer | core | 普通的node:http载体:具名路由注册表、索引转换 tap、静态 dist 回退 |
ctx.clientModules | core | 客户端插件图宿主。通过增量扫描组合启动入口图,提供插件组合包 |
ctx.planMode | core | 计划模式:折叠已记录的计划/模式状态,在轮次边界刷新用户选择 |
ctx.userQuestions | seam | 人机问答。UI 前端提供当前生效的回答提供方;tool-ask-user在一个与提供方无关的 promise 上暂停工具调用 |
8.4 怎么读这张能力图
官方的能力图(capability-seams.zh.md)是一张大图,规模不小。对零基础读者来说,不必背下来,但要掌握读它的方法。图中每个服务有四类信息,官方在说明里解释了分类逻辑:
| 分类 | 含义 | 你该关心什么 |
|---|---|---|
core | 核心主干服务 | 它一般不被替换,你只是使用它 |
seam | 可替换的能力接缝 | **这是你发挥的地方。**看到 seam,就意味着「这里可以换一个实现」 |
bundle | 组合点 | 它属于某一层组合包,可以被 profile 层替换 |
service | 独立服务 | 单独存在,不被替换 |
官方还说明了这张图的维护方式:「服务从 Cordis 声明中发现;接口、实现和消费方角色在scripts/gen-doc-graphs.ts中分类,并设有完整性守卫。」也就是说,这张图是脚本生成的,并且有测试保证它不会和源码脱节。
一个实用的读法
不要从图的左上角开始读。先想清楚你的目标,然后问:「实现这个能力,需要替换哪一类东西?」
· 要换「在哪里执行」→ 去看
fs/subprocess/shell/sandbox· 要换「用哪个模型」→ 去看
llm· 要换「数据存在哪」→ 去看
sessionPersistence/storage· 要换「谁来做这件事」→ 去看
subagents/jobs/web/skills
8.5 一个纪律:一个包可以兼任多角色,但你要说清楚
官方有一条容易被忽略的规范,值得抄下来:
一个包可以合并承担多个角色,但单一角色本身不是 seam;添加一项能力意味着把三者一并设计。
举个例子:web这个包既声明了ctx.web(Definition 角色),又实现了它的一部分(Provider 角色)。这没问题。但如果一个包只有实现、没有声明接口,那它就不是一个 seam,而只是一个服务——那么别人就无法替换它。
这条纪律的实际意义是:当你想「让这个能力可替换」时,你的工作不是「写一个实现」,而是「拆出接口、写下实现、找到消费方」。
8.6 这一章要带走的三句话
这一章要带走的三句话
- **seam = 接口 + 实现 + 消费方,三者一起设计。**只写实现不叫 seam。
- **替换提供方,而不是修改消费方。**这是所有可替换性的收益来源。
- **看 seam 图找「可以换什么」。**它是脚本生成并有完整性守卫的,可以作为可信清单。
关于这一章
这一章属于《DeepSeek Harness 架构详解手册》。全书的组织方式是:每一章都先讲「它在整台机器里负责哪一环」,再讲细节,最后用三句话收尾——因为对零基础读者来说,最难的不是某个子系统本身,而是找不到子系统之间的接缝。
上一章讲的是工具执行流水线(模型能要求做的事,怎么被五道关卡管住);下一章讲作用域,回答一个相关的问题:同样是注册一个能力,注册在根上下文和注册在某个 agent 内部,语义差别在哪里。