拆解 dsh|能力接缝:底层实现替换的影响边界

简介: 本文介绍 dsh(DeepSeek Shell)v0.1.2-rc.1 中基于“seam”架构的横向能力替换机制:通过统一契约解耦 Service Definition、Provider 与 Consumer,仅替换 `ctx.fs` 和 `ctx.subprocess` 两个底层 Provider,即可将文件操作、Bash、PTY 终端及 LSP 全面迁移至远程沙箱(如 E2B),实现执行环境一致性与低侵入升级。

摘要:替换两个底层 Provider,文件、Bash、PTY 和 LSP 一起迁到远端。

本文更换了版本基线。前几篇基于 0.1.1-rc.2,当前 npx @deepseek-ai/dsh 获取的是 0.1.2-rc.1(2026-09-03 发布),对应仓库标签 dsh-v0.1.2-rc.1,commit a66e470204。文中的能力关系来自仓库生成的 docs/capability-seams.mddocs/event-producer-consumer.md,可以通过 pnpm run gen-doc-graphs 重新生成核对。dsh 仍处于 developer preview。

前面几篇的 dsh 拆解内容是从纵向结构拆解:怎么装配插件树、主循环怎么推进、如何记录状态。这一篇换个方向,看一个横向的问题,如果把一个 Provider 换成新的实现,会影响哪些上层能力

假设一个 Agent 原本运行在本机,现在要把它的执行环境搬到远程沙箱。如果 Bash、文件系统、终端和 LSP 各自持有一套本地的执行逻辑,这次迁移就需要在多个模块里分别修改。Bash 要换进程启动方式,文件工具要换读写后端,终端要换 PTY 的创建方式,语言服务器也要重新处理进程启动问题。

这种结构的问题不只在于修改点多。同一类执行能力的实现细节分布在多个上层模块里,每增加一种执行环境,都需要重新检查相关的调用链。

dsh 会先为一项可替换的能力定义统一契约,再让不同 Provider 按这份契约提供实现,上层 Consumer 也只依赖这份契约。因此,判断一次替换的影响,需要同时看两个范围:

依赖关系
→ 变化会传到哪些模块

契约边界
→ 这些模块能否继续使用新的实现

seam 的三个角色

dsh 把这种可替换能力称为 seam

1.PNG

一个完整的 seam 包含三个角色:

  • Service Definition:声明这项能力的契约

  • Service Provider:提供具体实现

  • Consumer:使用这项能力

以 shell 为例,可以简化成:

              Service Definition
                   ctx.shell
                  /         \
                 /           \
          Provider         Consumer
         bash-local        tool-bash
         bash-sandbox      hooks
         pwsh-local

这组关系中,关键点是三者之间的依赖方向。

Consumer 依赖的是 Definition 声明的能力契约,Provider 则负责实现这份契约。只要新的 Provider 继续满足相同的契约,Consumer 就不用感知具体实现发生了什么变化。

这也是 Service Definition 在 dsh 中以运行期 Service 形式存在的原因。稳定的服务入口让 Consumer 可以声明「我需要 shell 能力」,而无需绑定到 bash-local 这样的具体实现。

同模块的两条 seam

seam 之间并非完全彼此独立。比如本地 Bash(bash-local)执行器向上提供 ctx.shell,同时向下消费 ctx.subprocess

ctx.subprocess
      ↓
  bash-local
      ↓
   ctx.shell
      ↓
  tool-bash

ctx.subprocess 的实现发生变化时,bash-local 仍然能提供相同的 shell 能力,只是启动进程的位置从本地换到了新的执行环境。上层的 tool-bash 可以继续使用 ctx.shell,不需要知道更底层的进程实现发生了变化。这样一来,执行环境的变化可以沿着 Service 逐层传递,同时把修改范围控制在更底层的实现层。

相比让每个工具分别处理本地和远程执行,这种结构更容易控制一次环境切换涉及到的修改点。

沿着能力链传导的变化

dsh 生成的能力图里一共记录了 29 个 seam。以 ctx.subprocess 为例,它位于多项执行能力的下层,适合用来观察 Provider 替换后的传导路径。

    Bash tools       Terminal tool      LSP tool
        ↑                ↑                ↑
     ctx.shell       ctx.terminals      ctx.lsp
        ↑                ↑                ↑
   Bash executor    Terminal backend    LSP host
        └────────────────┼────────────────┘
                         ↑
                    ctx.subprocess

除了 Bash、Terminal 和 LSP,一些进程外的子 Agent 后端也依赖 ctx.subprocess。上图直观地体现了一次 Provider 替换,会沿着哪些依赖关系继续向上传递。

如果只替换 ctx.subprocess 的 Provider,上图中的变化会先到达 Bash executor、Terminal backend 和 LSP host;这些模块如果同时还向上提供其他 Service,执行行为的变化还会继续传到更上层。

例如:

subprocess Provider 被替换
        ↓
bash-local 的进程运行位置改变
        ↓
ctx.shell 的外部行为随之改变
        ↓
tool-bash 执行的命令进入新的环境

tool-bash 全程只依赖 ctx.shell,不需要知道更底层的 subprocess 运行在本机还是远程环境。这也是 seam 带来的一个核心工程收益:底层实现可以替换,原有依赖关系和上层调用方式仍然保持不变。

共享执行世界

只替换 subprocess 还不够。如果命令已经迁到远端,而文件工具仍然在读写本机目录,两边看到的就会是不同的执行环境:

Bash → 远端 /workspace
FS   → 本机 /workspace

所以,dsh 会让文件系统和子进程落在同一个 execution world 里,保证命令执行和文件读写面对的是同一套环境。

dsh 这里以 E2B 作为远程沙箱实现。E2B 提供隔离的 Linux 执行环境,文件系统和子进程分别通过 ctx.fsctx.subprocess 两个 seam 接入:

ctx.fs         → fs-e2b ─────────┐
                                 ├→ 共享一个 E2B 沙箱
ctx.subprocess → subprocess-e2b ─┘

两边共用同一个远程工作目录和沙箱生命周期。于是上层看到的是:

文件读写 ───────┐
Bash ──────────┤
Terminal ──────┼→ 同一个远程 Linux 环境
LSP ───────────┘

文件工具通过 ctx.fs 切到远端环境。Bash、Terminal 和 LSP 则沿各自的 Service 间接使用 ctx.subprocess,因此不需要分别增加一套 E2B 专用实现,上层也不用额外维护「本地 / 远程」两套分支。

这样一来,执行环境的差异主要收敛在底层 Provider。切到 E2B 后,进程启动需要经过远程环境的准备过程,延迟会高于本机执行;环境变量也需要显式传入,宿主环境不会自动带过去。这些运行特性由 Provider 这一层处理,上层工具仍然沿用原来的 Service 接口。

契约边界

到这里很容易得出一个过于简单的结论:

接口一样
→ Provider 就可以随便换

dsh 自己恰好提供了一个反例。

运行语义

ctx.subprocess 的能力图里包含多个 Consumer,但这并不代表它们都能无条件使用任意 subprocess Provider。

subprocess-e2b 创建远程进程时存在异步过程,进程 handle 会先返回,真实 PID 稍后才能得到。

Bash、Terminal 和 LSP 不依赖启动后立即获得有效 PID,因此可以沿用原来的调用方式;ACP 子进程后端则需要在启动后立刻取得有效 PID,所以无法原样切到这个 Provider。

于是这里出现了两种范围:

能力图
→ ACP 依赖 ctx.subprocess

运行契约
→ ACP 依赖的即时 PID 语义
   subprocess-e2b 当前无法满足

这个例子说明,Provider 的可替换性不只取决于接口形式,还取决于契约里约定的运行语义。除了函数名、参数和返回值,调用时序、生命周期、进程身份等行为也属于契约的一部分。

如果 Consumer 依赖了某个 Provider 特有、但没有写进契约的行为,这些隐含依赖同样会增加后续替换的成本。

错误语义

本地沙箱里也出现过类似问题。不同平台的沙箱后端,需要让上层区分几类结果:

命令自身失败
策略拒绝了操作
沙箱执行器自身异常

这三类情况表面上都可能表现为「命令没有成功」,但对应的原因和处理方式完全不同。

dsh 的一次 Landlock 故障就出在错误归因上:ripgrep 返回「没有匹配结果」时,本来只是一次正常的命令退出,却因为沙箱后端输出的信息,被误判成了沙箱不可用。

修复之后,错误判断从简单的字符串匹配改成了更明确的失败规则,让 Consumer 能进一步区分:

runner failure
→ 沙箱执行器自身出错

denial
→ 沙箱工作正常,但策略阻止了操作

这个案例也说明,Provider 的可替换性还取决于错误语义是否被纳入契约。

如果对同一种失败场景,不同的实现给出不同的错误含义,上层就得识别具体 Provider 并分别处理,替换成本也会随之增加。

策略与执行约束分层

权限和沙箱这部分也采用了类似的分层方式。

dsh 把「策略怎么定义」和「约束怎么执行」拆成两层处理。工作区范围、沙箱模式等策略由统一入口维护,Bash、文件系统等执行能力读取同一套约束,再交给具体的沙箱 Provider 落地。

可以简化成:

权限 / 沙箱策略
       ↓
统一的执行边界
       ↓
Bash / FS / Terminal
       ↓
具体沙箱实现

这样一来,工具只用消费权限结果和执行能力,无需自己维护一套沙箱规则。更换底层沙箱实现时,权限策略也不用随着每个工具重新设计。

seam 之外的扩展点

还有一类需求,不适合通过替换 Provider 来实现。

比如文件修改里的「先读后改」。文件系统 Provider 负责的核心问题是:

read
write
edit

至于「写入前是否读取过文件」「当前文件版本是否仍然匹配」这类要求,更接近文件操作的策略约束。

dsh 没有把这些规则放进文件系统 Provider,而是通过独立的 policy 插件接入文件操作流程。整体关系可以简化成:

文件工具
   ↓
策略检查
   ↓
文件系统 Provider

这样设计有两个好处。

第一,文件系统仍然保持完整的基础能力,不安装这层 policy 也可以工作。

第二,策略可以独立增加和调整,不需要为了增加「先读后改」规则再造一套文件系统 Provider。

这也给 seam 的边界提供了一种比较清晰的判断方式:如果变化的是能力本身的实现,适合放在 Provider;如果变化的是这项能力在什么条件下可以使用,更适合交给独立的策略层。

窄接口与替换成本

seam 的接口应该收多窄,是另一个需要考虑的问题。

ctx.lsp 是一个比较典型的例子。它没有把完整的 LSP 协议暴露给 Consumer,只保留四类只读导航能力:

  • 跳转到定义

  • 查找引用

  • 跳到实现

  • 读取 hover 文档

同时,它也没有提供通用的 JSON-RPC 通道,让上层绕过这层抽象。因此,不同的语言服务器都要把自身的能力适配到这几类标准操作里。某个语言服务器提供的额外功能,也不会自动通过这层 seam 暴露给上层。

相应地,这层接口的替换范围也更容易控制。对于 Consumer 来说:

Go LSP
Python LSP
TypeScript LSP
   ↓
统一的导航能力
   ↓
模型工具

Provider 发生变化时,上层工具仍然可以沿用相同的请求方式和结果结构。

如果以后要增加新的导航能力,就需要扩展 Definition,并重新检查现有 Provider 和 Consumer 是否都能适配。这也体现了 seam 设计中的一个基本取舍:

接口更宽
→ Provider 可以表达更多差异
→ 共同维护的契约面更大

接口更窄
→ 替换成本更容易控制
→ 一部分实现特有能力无法暴露

所以 seam 应该收多窄,最终取决于这项能力准备容纳多大的实现差异。

替换范围的判断方法

回到开头的问题:换掉一个 Provider,影响范围有多大?

沿着 dsh 的能力关系,可以从三个层面判断。第一步,看依赖图:

Provider
   ↓
哪些 Consumer 依赖这项能力

这决定变化可能传到哪里。第二步,看 Consumer 同时提供了什么能力:

底层 Provider
      ↓
   Consumer
      ↓
上层 Service
      ↓
更多 Consumer

这决定行为还会不会继续向上传导。第三步,看契约里的运行语义:

方法签名
调用时序
生命周期
错误语义
进程身份
……

这决定了新的 Provider 能否继续适配这些 Consumer。

因此,想降低 Provider 的替换成本,需要满足两个条件:Consumer 依赖稳定的 Definition,同时不依赖契约之外、某个实现特有的行为。

E2B 的例子正好把这两层关系串了起来。替换文件系统和 subprocess 两个底层 Provider 后,文件、Bash、Terminal 和 LSP 可以沿原有能力链迁到同一个远程环境;ACP 由于依赖即时 PID,则停在了当前 Provider 的兼容边界之外。

能力接缝的作用也体现在这里:一次底层实现发生变化后,可以沿着明确的依赖关系判断变化会传到哪里,再结合契约判断哪些 Consumer 能继续工作,从而把替换范围控制在可预期的边界内。

下一篇是这个系列的最后一篇,我们会继续看这套设计带来了哪些额外复杂度、适合哪些场景,以及其中哪些设计方法可以脱离 Cordis 单独使用。

参考源码

以下路径以 dsh-v0.1.2-rc.1 为基线:

docs/capability-seams.md
docs/event-producer-consumer.md
docs/architecture.md
docs/glossary.md
docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md

packages/shell/
packages/subprocess/
packages/fs/
packages/e2b/
packages/sandbox/
packages/interaction/
packages/lsp/
packages/code-runtime/
相关文章
|
13天前
|
缓存 资源调度 开发工具
dsh 怎么更新?DeepSeek Harness 更新到最新版三种方式:npx 自动更新、npm update -g、源码 git pull
更新 DeepSeek dsh 分三步:先查当前版本,再按安装方式更新——npx 自动用最新、全局安装用 npm update -g、源码用 git pull 重新构建,最后验证版本号。本文覆盖三种安装方式的更新命令、更新前备份、更新后验证与失败兜底排查。
1099 2
dsh 怎么更新?DeepSeek Harness 更新到最新版三种方式:npx 自动更新、npm update -g、源码 git pull
|
12天前
|
存储 Shell API
拆解 dsh 系列:从源码和版本变化看 DeepSeek Harness 的设计取舍
DeepSeek Harness(dsh)发布仅两周,实则历经两个月内部打磨。本文基于Git历史与npm发布数据,梳理其从0.0.1-rc.1到0.1.1-rc.2的演进:命名重构、定位升维(Coding Agent→Agent Harness)、核心与扩展边界持续厘清,展现一个开源智能体运行时如何审慎定义自身能力疆界。
162 1
拆解 dsh 系列:从源码和版本变化看 DeepSeek Harness 的设计取舍
|
19天前
|
存储 JavaScript 安全
dsh 拆解系列 Vol.01:没有特权内核的 Agent 运行时
DeepSeek Harness(dsh)是DeepSeek开源的Agent运行时框架,秉持“一切皆插件”理念,将模型适配器、工具、会话、主循环等全部解耦为可配置、可替换、可卸载的插件,基于Cordis元框架实现时空可组合性。当前v0.1.0-rc.7为开发者预览版,MIT协议,强调工程可扩展性而非仅功能堆砌。
188 2
dsh 拆解系列 Vol.01:没有特权内核的 Agent 运行时
|
1月前
|
存储 Shell 数据库
Agent 小知识|长任务不重来:Agent 状态保存的工程设计
本文详解Agent状态的核心概念与工程实践:它不是简单记忆,而是任务执行的实时快照,涵盖进度、环境、内部判断与资源约束。通过结构化Schema、分层检查点和状态栏机制,实现可靠暂停恢复、高效决策与长程连贯性。
150 0
Agent 小知识|长任务不重来:Agent 状态保存的工程设计
|
1月前
|
人工智能 缓存 安全
DeepSeek V4-Flash 正式版接入 Codex,OpenAI 新模型 Astra 浮出水面,Google DeepMind 明星团队被拆
本期「周一上线」聚焦AI两大演进方向:模型加速迈向多模态与机器人,Agent则从“写代码”升级为长期协作、端到端交付与自我改进。DeepSeek V4-Flash、MiniMax H3、Gemini Robotics 2等密集发布,OpenAI Astra、Lilian Weng的RSI团队、贾扬清Intent Lab齐探AI自主进化;行业层面,AlphaFold团队拆分、字节整合飞书/豆包/火山引擎,技术与组织同步重构。
233 1
DeepSeek V4-Flash 正式版接入 Codex,OpenAI 新模型 Astra 浮出水面,Google DeepMind 明星团队被拆
|
2天前
|
测试技术 API 开发者
|
2月前
|
数据库 开发工具 知识图谱
从 DeepWiki 到 OpenWiki:Agent Wiki 到底有什么用?
Agent Wiki 是一种新型知识管理范式:将RAG中“查询时实时检索”改为“导入时预编译”,用模型自动生成并持续维护结构化Markdown Wiki。它解决传统RAG重复推导、无法积累的问题,适用于稳定文档集,但需区分“文档知识”与“用户记忆”。
286 0
|
2月前
|
运维 监控 安全
Coding Agent 规则管理:CLAUDE.md、Skills、Hooks、Subagents 到底怎么选?
Claude Code 用分层设计把 Coding Agent 的规则拆成多种机制,让约束在合适的时机生效
269 1
Coding Agent 规则管理:CLAUDE.md、Skills、Hooks、Subagents 到底怎么选?
|
2月前
|
自然语言处理 安全 API
Agent Graph Engineering:从线性 Workflow 到可扩展 Agent 系统
本文介绍Agent图编排(Agent Graph Engineering)的核心思想:摒弃简单串行流程,以数据依赖关系构建节点(Agent/代码)与边(数据流)组成的有向图。强调清晰输入输出约定、并行执行、故障隔离、验证机制与动态循环设计,提升系统可组合性、稳定性与成本效率。
284 0
Agent Graph Engineering:从线性 Workflow 到可扩展 Agent 系统
|
3月前
|
Web App开发 存储 Linux
旧手机如何组建集群,跑点云计算?
UC San Diego 与 Google 合作推进“手机集群计算”:拆解退役手机主板,组建低碳小型计算集群。首期将部署 2000 台 Pixel 手机主板,替代传统服务器,为教学科研提供低成本云资源,兼顾环保与算力复用。
345 1
旧手机如何组建集群,跑点云计算?