Skip to content
My Blog
Go back

Capability Seam:让 shell、文件系统和沙箱都可替换

读 dsh 源码最大的一个疑问:为什么 packages/shell/ 这一个「能力域」下面有九个包,一个跑 bash 的活儿至于拆这么细吗?看到 docs/glossary.md 里的一个词才反应过来:seam(接缝)。

一条接缝 = 三个角色

词汇表里 seam 的定义很硬:

a swappable capability with three roles: a Service Definition … one or more Service Providers, and one or more Consumers.

三个角色各管一摊:Service Definition 拥有这个能力的接口(注意,是一个 Cordis Service,占住 ctx.<key>,「never a TypeScript interface」);Provider 是实现;Consumer 是使用者。根目录规约补了一句:一条接缝必须三角色齐全,「It is complete, never one role」,只有当角色要独立演化时才允许拆包。

官方钦定的样板就是 shell。把 packages/shell/ 展开看:

角色说明
Service Definitiondsh-shell定义执行器服务 ctx.shell,仓库里每个 shell 执行器都实现这同一份契约
Providerdsh-bash-local、dsh-bash-sandbox、dsh-pwsh-local、dsh-pwsh-sandbox本地 bash / 沙箱内 bash / 本地 pwsh / 沙箱内 pwsh
Consumerdsh-tool-bash、dsh-tool-bash-persistent、dsh-tool-pwsh、dsh-tool-pwsh-persistent给模型用的 shell 工具

文件系统的接缝同款:dsh-fs 定义 ctx.fs,README 里直说「deliberately leaves storage mechanics to the backends」——后端有 fs-localfs-sandboxfs-e2b 三种,消费侧是 tool-fstool-fs-searchtool-str-replace-editor 这些文件工具,另有一个 fs-observation-policy 管访问策略。仓库里还有脚本生成整张接缝图谱(docs/capability-seams.md),每条接缝的 SD、实现、消费者一览无余。

这个设计带来的实际效果:换后端只动 Provider,消费者一行不碰。换掉 shell 执行器,模型端的 bash 工具毫无感知。

三角色的两条约束

接缝不是随便画条线就行,仓库里有两条明确的反模式约束:

沙箱:三层防线

危险能力的接缝背后是沙箱,dsh 的做法是叠三层。

第一层:内核级,一段 300 行的 C

native/landlock-run/ 里有个叫 landlock-run 的工具——「self-restrict-then-exec」的 Landlock 启动器,大约 300 行 C11,直接对着内核 UAPI 写,musl 静态链接。设计原则是 fail-closed:内核没法强制执行政策时,直接退出,不跑命令。

第二层:策略抽象

packages/sandbox/ 下的 dsh-sandbox 把「同世界子进程」限制在三档文件效果策略里:read-onlyworkspace-writedanger-full-access。请求的模式强制不了时,调用以 SANDBOX_UNAVAILABLE 失败——同样是 fail closed,宁可拒绝不放行。具体实现有两个:sandbox-local(Linux Landlock)和 sandbox-windows-acl(Windows ACL)。

第三层:远程沙箱(POC)

packages/e2b/ 把 agent 的文件和命令操作整体搬进远程 Linux 沙箱,内含共享沙箱生命周期管理加 fs-e2bsubprocess-e2b 两个适配器。README 明确标注是实验性 POC,没有任何出厂组合默认启用它。这正是接缝威力的展示位:换个组合配置,整套文件与命令能力就搬到远端去了。

审批:工具执行前的最后一道闸

沙箱管「能碰到什么」,审批管「这次让不让」。工具执行管线本身就带策略:每个模型发起的工具调用都过一条受保护的管线——allow/deny/ask 策略、单调性守卫、around-dispatch 包装、结果检查。

人机交互的部分收在 packages/interaction/,五个包:

职责
user-approval一次性的 allow/reject 审批,ctx.approval
permission-presets把沙箱模式 + 审批策略打包成可选的 Permissions 预设
user-questionsagent 暂停、等人回答的问答接缝
tool-ask-userask_user_question 模型工具
commands斜杠命令

审批的结果只有四种:allowed-oncerejectedcancelledunavailable。注意 allowed 是「one-time」的,一次一授权,不发长期通行证。缺应答方、应答方不归属、应答方抛异常,一律落回 unavailable——还是 fail closed。per-session 策略只有两档:ask(默认)或 never。出厂的两个预设正好对应两种姿态:workspace-write = 工作区可写 + 每次询问;danger-full-access = 全权限 + 不询问。

值得抄走的东西

三角色模式本身不新鲜,接口和实现分离谁都会说。dsh 做得扎实的地方在两点:一是接缝必须三角色齐全才算数,杜绝「只有接口没有消费者」的纸面抽象;二是每条接缝的图谱由脚本生成并纳入校验,接缝不是文档修辞,是持续被检查的结构事实。

下一篇是这个系列的收尾:一个 alpha 阶段的项目,凭什么守住这些设计——质量门禁篇。

本系列


Share this post:

Previous Post
第 02 课 · 一切皆插件:Cordis 骨架二十分钟
Next Post
声明即授权:dsh Web 客户端的 slot 体系