跳到主要内容

内容阅读

能力接缝:dsh 换一个 provider 等于换整个产品

能力接缝:dsh 换一个 provider 等于换整个产品

dsh 把每个能力做成一条可整体替换的"接缝":接口定义、提供者、消费者三个角色齐备,能力才算落地。文件系统和子进程这两个接缝共享同一个"执行世界",所以把它们指向远程沙箱时,bash、终端、LSP、子 agent 整体跟着搬走。"换一个 provider 等于换整个产品"说的就是这件事。 这一篇只讲设计和原理:三个角色为什么缺一不可,共享执行世界为什么是最值钱的一条洞察,每条接缝背后各藏着什么设计原则,以及哪些东西被刻意设计成不可替换。

先把"接缝"这个词说准

很多项目自称"插件化",但它们的插件只能在外围加点东西:换换主题、加加面板,核心能力换不掉。dsh 的"接缝(capability seam)"走得更远:一个接缝是一个可以被整体替换的能力。

理解它最省力的类比是 USB。接口标准本身、按标准造设备的厂商、按标准使用设备的软件,三者齐备,"鼠标"这个能力才成立。你可以换任何一家厂商的鼠标,甚至换成轨迹球,主机和软件一行不改;反过来,只有标准没有设备,或者只有设备没人用,这个能力都不成立。

dsh 里对应的三个角色:

  • 服务定义:声明这个能力的接口,比如"文件系统读写"长什么样。
  • 服务提供者:实现这个接口,比如同一个文件系统接缝的本地实现、沙箱实现、远程实现。
  • 消费者:使用这个能力,通常是模型面向的工具。

架构文档的关键规矩:一个角色单独存在不构成接缝;增加一个能力,意味着三个角色都要设计。 这条规矩排除了两类半成品:只写了接口没人实现,或者只有实现没人消费。

核心命题:文件系统和子进程共享一个执行世界

整篇标题的来源就是这一条。架构文档原话:

Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them, with no provider forks.

文件接缝和子进程接缝共用一个"执行世界"。这个设计凭什么值钱?看子进程接缝的消费面:凡是需要启动子进程的能力,全都走它。bash 命令执行走它,终端的后端走它,LSP 宿主走它,进程外的子 agent(委派给 Codex、Claude Code 这些产品的后端)也走它。也就是说,"在哪里启动进程"这个变化点,被收敛到了一个接缝上。

再看这两个接缝的远程实现:它们不是各连各的远程机器,而是住进同一个远程沙箱运行时里,文件操作和进程启动发生在同一个 Linux 世界。本地实现同理,发生在同一台宿主机上。整个设计围绕一个默认:文件和进程待在同一个底座上,本地时是宿主机,远程时是共享的沙箱运行时。

把两件事拼起来,推论就出来了:把文件接缝换成远程实现、把子进程接缝换成远程实现,文件读写去了远程沙箱,所有子进程也在同一个沙箱里启动。因为 bash、终端、LSP、子 agent 全走子进程接缝,它们就整体跟着搬走了。你换的是两个提供者,移动的是整个执行世界。

"一次只有一个执行世界生效"还有机制兜底:文件、子进程、命令执行这几个接缝,同一个 context 里各只允许装载一个实现,装载第二个会直接报错。不存在两套文件实现或两套进程实现同时跑、各说各话的状态。

没有这个设计会怎样?做远程执行就得为 bash、终端、LSP、子 agent 各写一套远程分支,而且它们之间随时可能不一致:文件在远程、命令在本地,agent 无从察觉两个世界已经脱节。共享执行世界让一次替换搬运一切,且天然一致。

每条接缝背后是一条设计原则

把主要接缝逐个过一遍,会发现每条接缝都在回答一个设计问题。抽出五条原则。

模型中立。模型接缝的消费者调的是一个 provider 中立的流式契约,不依赖任何具体实现。换模型提供者,agent 的驱动逻辑和压缩逻辑一行不改。原则:把"用哪个模型"这个决策从消费者代码里完全挤出去。

边界显式。agent 能动哪些文件、能跑什么命令,是接缝加沙箱策略显式管理的边界,不是"当前目录随便扫"。这里有条一致性约束:命令的围栏和文件的围栏必须指向同一个根。一个围 A、一个围 B,沙箱就漏了,所以两个强制家族被钉在同一个策略来源上。

封闭操作集。LSP 接缝只提供恰好四个标准化导航操作,没有走原始 LSP 协议的逃生口。后端必须把请求翻译成这四个操作,翻译不动的功能就只能放弃。牺牲后端的全部能力,换来的是所有后端行为一致:消费者永远只看到四个操作,不管底下是什么实现。

形态自由。子 agent 接缝的同一个接口背后,形态差异可以极大:可以是在本进程里开一个全新的子 agent,也可以是把一次任务委派给另一个产品里的一次执行。差异被接缝吃掉,消费者看到的都是"派一个子任务出去,拿一个结果回来"。

缺席即拒绝。审批接缝的安全语义是 fail closed:一个审批请求发出去没人回答,结果不是放行,而是拒绝。安全机制的默认方向必须是关,"没人管"不能被解释成"默许"。

换一个 provider 等于换整个产品:三个推演

设计讲完了,做三个具体推演,看"换 provider 换产品"怎么发生。先交代换这个动作发生在哪:dsh 启动时按层组装插件树,profile 点名 bundle,bundle 插入配置行,上层的 patch 按 id 定位一行、整行替换。换 provider 就是把某一行的实现包换成另一个,重新组装后,所有消费者解析到的自动是新提供者。后面说"工具层一行没改",指的就是字面事实:改的是一行配置,不是消费者的代码。

从本地切到远程沙箱。文件接缝和子进程接缝各换一个远程提供者,上一节的推论完整生效:文件、命令、终端、LSP、进程外子 agent 全部进入同一个远程运行时,工具层一行没改。

换模型。模型接缝的实现从一家换到另一家,消费者调的流式契约没变,模型层整个换了,agent 的行为逻辑不变。

换协作形态。子 agent 接缝从"进程内开新 agent"换成"委派给 Claude Code 里的一次执行",同一个工具接口,产品形态从单打独斗变成多产品协作。

三个场景的共同点:消费者代码不变,换的是提供者,变的是执行位置、模型、协作形态这种产品级的东西。 这不是换配置项那种小改,是一次替换换掉半个产品的形态。

什么被刻意设计成不可替换

读完上面的推演容易产生一个误解:harness 里所有服务都能换?不能,而且"不能"本身是设计。官方把所有服务分成三类。

可替换的是接缝:模型、文件系统、命令执行、子进程、沙箱、联网、LSP、子 agent、审批这些能力,天生为替换而设计,有多个提供者实现。

不可替换的是核心脊柱:会话日志、工具注册表、提示组装、agent 服务、不变量断言这些。它们各有单一 owner,整个 harness 依赖它们行为稳定。想换模型、换执行环境,设计上欢迎;想换会话日志的模型,那是动脊柱,不是替换,得改源码。

组合点只有一个:那个具体的循环驱动器。整个 harness 就这一个位置装着真正的循环逻辑,其他一切都挂在它周围的扩展点上。

这个三分法的实用价值,是它明确告诉你变化点的边界:变化被欢迎的地方(接缝),和稳定被强制的地方(脊柱)。

权衡:接缝的成本和回报

回报是从"一个产品"变成"一族可组合的产品"。同一套消费者代码,靠换不同的提供者组合,能拼出本地 agent、远程沙箱 agent、委派到别的产品的 agent、不同模型的 agent。

代价也是具体的。一是抽象层多、调试链长:一次工具调用从模型发出,要穿过接缝、提供者、事件若干层,出问题时定位要沿着接缝和提供者走。二是三角色齐备的纪律:增加一个能力不是写个函数,而是定义、提供者、消费者三件都要设计,还要守住各接缝的语义约束,比如 LSP 只有四个操作、审批缺席必须拒绝。

权衡下来:如果 agent 只在一种环境里跑、只配一个模型,接缝是过度设计。但如果要的是"一套代码、多种执行环境、多种模型、多种协作形态",接缝就是把所有变化点收敛到提供者替换上的唯一手段。dsh 选这条路,赌的是 agent 这个场景值得为可组合性付抽象的代价。

结论

接缝是 dsh 组织能力的方式:定义、提供者、消费者三件齐备,能力才落地;变化被收敛到提供者替换上,脊柱服务被刻意钉死不可换。最值钱的一条设计是文件系统和子进程共享同一个执行世界,让"换两个提供者"等价于"搬走整个执行世界",而且一次只有一个执行世界生效,有机制兜底。每条接缝背后各有一条原则:模型中立、边界显式、封闭操作集、形态自由、缺席即拒绝。代价是抽象层多、调试链长,加上三角色齐备的工程纪律。

延伸阅读

上一篇:事件系统:dsh 的四种派发模式与 waterfall 短路 下一篇:工具执行管线与守卫:dsh 从 tool_call 到结果的七道关卡


GitHub 原文:12-capability-seams-swap-provider-swap-product.md

评论

0
登录后可以参与评论和讨论。

EMPTY

还没有评论

欢迎留下第一条评论,帮助这篇内容更快形成讨论。