news 2026/9/15 12:03:24

11. 能力接缝:文件、命令、沙箱、审批、子代理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
11. 能力接缝:文件、命令、沙箱、审批、子代理

你在哪:运行示例的第 8、10、11 步——真正跟外部世界打交道的那一层。这是最后一个深度篇。

读完你会知道:接缝的三个角色为什么缺一不可、一次 provider 替换如何把 Bash/PTY/LSP 一起搬到远程、SandboxMode三档策略与"部分执行"这个诚实的事实、审批为什么是 fail-closed,以及子代理接缝如何把 Claude Code 和 Codex 变成可调用的孩子。


缩写对照表

缩写英文全称中文
PTYPseudo Terminal伪终端
LSPLanguage Server Protocol语言服务器协议
ACLAccess Control List访问控制列表
ABIApplication Binary Interface应用二进制接口
CIContinuous Integration持续集成
SDKSoftware Development Kit软件开发工具包
APIApplication Programming Interface应用程序编程接口

一、角色回顾

拥有:真正跟外部世界打交道的能力——读写文件、跑命令、关进沙箱、问用户、派子代理。
刻意不做:不互相耦合。
在示例中出场:第 8 步(文件)、第 10 步(审批)、第 11 步(命令 + 沙箱)。

二、什么才算一个"接缝"

术语表的定义非常严格:

seam— aswappable capabilitywith three roles: aService Definition(拥有ctx.<key>和词汇类型的 Cordis Service,抽象类或具体注册表,绝不是 TypeScriptinterface), one or moreService Providers, and one or moreConsumers.”

The seam is the complete capability, never one role; reserve the term for that meaning.”

三个角色,缺一个就不是接缝。仓库给的标准示例是packages/shell

角色
服务定义dsh-shell
提供者dsh-bash-localdsh-bash-sandbox
消费者dsh-tool-bash

服务定义
dsh-shell
拥有 ctx.shell
与词汇类型

提供者
dsh-bash-local

提供者
dsh-bash-sandbox

提供者
远程执行(你写的)

消费者
dsh-tool-bash
(面向模型的工具)

角色通常各占一个包(因为它们独立演化),但当它们是同一件关切时也可以合并——dsh-llm就同时拥有服务定义和消费者(第 9 篇)。

“Adding a capability means designingall three.”

这条对想扩展 DSH 的人是最实用的一句话:别只写一个实现,先想清楚定义和消费者是谁。

三、接缝的复利:一次替换,整片搬家

架构文档里最有说服力的一段:

“Seams are why one provider swap changes the whole product.Filesystem and subprocess providers share one execution world, so pointing them at a remote sandboxmoves Bash, PTY, and LSP with them, with no provider forks.”

换两个 provider

一行代码不改

跟着一起搬的消费者

Bash 工具

PTY 持久终端

LSP 语言服务

远程执行世界

ctx.fs → 远程文件

同一个世界

ctx.subprocess
→ 远程 spawn

本机执行世界

ctx.fs → 本机文件

同一个世界

ctx.subprocess
→ 本机 spawn

这张图回答的问题:为什么"把 agent 的执行环境挪到远程"在 DSH 里不是一个大工程。

之所以能这样,是因为 Bash、PTY、LSP 三个消费者都不认识"本机"这个概念——它们只认识ctx.fsctx.subprocess

四、沙箱:三档策略和一个诚实的事实

ctx.sandbox的职责很窄:把一个同世界子进程的 argv 包进一层文件效果策略里,而不让消费者耦合到平台运行器。

出厂后端dsh-sandbox-local覆盖三个平台:Linux 的 bwrap/Landlock、macOS 的 Seatbelt、Windows 的 ACL 受限令牌。

三档模式

typeSandboxMode='read-only'|'workspace-write'|'danger-full-access'
模式含义
read-only只允许必需的 sink(POSIX 运行器额外放开/dev/null,因为 shell 需要它)
workspace-write还允许写工作区根目录和后端承诺的临时区
danger-full-access绕过限制——注意这个模式的消费者根本不调用ctx.sandbox,它直接 spawn 原始 argv

一个重要的边界声明:

Network and process visibility are outside this vocabulary.

沙箱只管文件效果,不管网络和进程可见性。说清楚自己不管什么,比含糊地暗示"我很安全"要负责得多。

"部分执行"是一个被上报的事实

typeSandboxEnforcement='full'|'partial'

partialmeans an active backend or older kernel ABI cannot govern every promised file effect;callers requiring an absolute boundary must not treat it asfull.”

老的 Landlock ABI、Windows ACL 运行器的 Everyone/硬链接边界,都是当前的 partial 情形。这是很难得的诚实:不是"我们支持沙箱",而是"在这台机器上我能保证到什么程度"

策略是按调用携带的,不是钉在 provider 上

interfaceSandboxExecutionPolicy{mode:SandboxMode workspaceRoot:string// workspace-write 可写的绝对根目录sessionId?:SessionId// 后端按它给每个会话分私有临时目录}

“carriedPER CALL, not fixed on the provider: two consumers may confine under different policies at the same instant (bash underread-onlywhile a confined child agent needs its state directory writable), and an approved escalated retry is a new call with a wider policy.”

同一瞬间,bash 可以是只读,而一个受限子代理需要它的状态目录可写。并发会话、不同消费者、一次性提权重试,可以向同一个 provider 要不同的边界,而不需要修改 provider 状态。

还有一个容易被忽略的细节:workspaceRoot先按文件系统语义规范化(解 symlink),再做词法规范化——所以一个含symlink/..的 cwd 标识的是进程真正运行的那个目录,而不是字符串拼出来的目录。这是一类经典越权的堵法。

五、审批:fail-closed 的三态

ctx.approval回答一个问题:这次具体的动作能不能进行?

typeApprovalOutcome='allowed-once'|'rejected'|'cancelled'|'unavailable'typeApprovalPolicy='ask'|'never'

四条规则:

  1. 只有allowed-once才放行,而且它只授权被问到的那个动作——不是"这类动作以后都行"。
  2. unavailable按拒绝处理。缺失的、不拥有的、抛异常的、不符合形状的答复者,一律变成unavailable不是打开闸门
  3. never策略是确定性的拒绝——不派发任何答复者。这是"严格无人值守姿态(CI、后台运行)",也是"不问就知道结果"的那个策略。
  4. 策略的有效值来自会话日志里最后一条approval/policy事件,回退到服务配置。setApprovalPolicy(session, policy)是唯一写入路径——所以重放能重建这个覆盖(第 6 篇的不变量)。

审批请求故意不带工具参数

“an answerer attaches the prompt to the already-streamed tool call throughcallIdinstead ofrendering a second copy that could drift.”

权限预设:把两个旋钮捆成一个选择器

沙箱模式和审批策略是两个独立旋钮ctx.permissionPresets把它们捆成用户看得懂的一个选择:

预设沙箱审批
workspace-writeworkspace-writeask
danger-full-accessdanger-full-accessnever

这一层自己不做任何强制——它只记录意图,然后通过每个旋钮各自的规范写入器写下去。执行、提示叙述、重放,仍然各读各的旋钮。当前值也是推导出来的:折叠出会话的有效沙箱模式和审批策略,匹配表里的项;都不匹配就返回派生的customcustom只能显示,永远不能作为切换目标)。

配置错误在插件加载时就失败:表里有叫custom的项 → 抛错;组合在一个不限制的 bash 执行器之上 → 抛错(因为预设是要捆绑沙箱模式的)。

六、子代理:把别家产品变成孩子

ctx.subagents是接缝多样性最极端的例子:

“Subagent providersvary just as widely behind one interface, from a fresh child agent to a delegated turn in another product.”

出厂的进程内后端有两个(subagent-spawn-in-processsubagent-fork-in-process),而标准模式 preset 里还有两行:

-id:tool-subagent-codexname:'@deepseek-ai/dsh-tool-subagent'-id:tool-subagent-claude-codename:'@deepseek-ai/dsh-tool-subagent'

同一个工具包,不同配置,就把 Codex 和 Claude Code 变成了可以派活的子代理。这两个 provider 刻意不在dsh-base的生产依赖闭包里——需要的 profile 单独装对应的 bundle,所以默认安装不会拖进 Claude Agent SDK 和 Codex 的平台负载。

子代理还有几个值得知道的概念:

  • lineage(世系):父子关系是数据parentSession、持久的delegationDepth、运行时的subagentDepth),永远不影响可见性。作用域是两层且扁平的——作用域注册不向下继承给子代理,子树行为用世系数据表达。
  • 孩子怎么拿到工具:通过composeFrom()绑定父的那次 preset 挂载(第 5 篇)。文档有一句很硬的话:“a child that joins nothing reaches the model withno tools at all——因为所有面向模型的行都在 agent 平面,全局层是空的。
  • Ralph loop:一个前台的"全新 agent 反复尝试同一个不可变目标"的工作流。每一轮是一个全新的子会话,不带父或前一轮的对话种子;跨轮状态靠共享工作区加一份有界的结构化交接报告(状态、摘要、证据、下一步、阻塞原因)传递。

七、失败行为

出什么事怎么办
没有审批答复者unavailable→ 拒绝
审批策略是never确定性rejected,不派发答复者
沙箱后端只能部分强制上报partial;需要绝对边界的调用方必须区别对待
消费者要danger-full-access根本不调ctx.sandbox,直接 spawn 原始 argv
同时挂了fs-sandboxdsh-fs-local双重注册ctx.fs,加载失败
Windows 上把 bash 栈恢复回来但没禁用 pwsh 栈两个执行器家族注册同一个bash服务 →加载时大声失败
权限预设表里出现custom插件加载时抛错(保留名)

倒数第二条特别典型:不完整的配置改法会在加载时炸掉,而不是在运行时给你一个诡异的行为。

⚓ 回到示例

第 8 步(读package.json):read_file工具是消费者,它调ctx.fsctx.fs在你这个部署里由fs-sandbox提供(dsh-base的默认组合),它在本机文件之上加了一层写入围栏。读操作直接过。

第 10–11 步pnpm lint)是完整的一条链:

ctx.subprocessctx.sandbox(sandbox-local)ctx.sandboxPolicyctx.shell(bash-sandbox)Web UI 答复者ctx.approvalctx.toolsctx.subprocessctx.sandbox(sandbox-local)ctx.sandboxPolicyctx.shell(bash-sandbox)Web UI 答复者ctx.approvalctx.tools会话策略是 askrequest(callId, ...)派发给答复者allowed-onceallowed-once执行 pnpm lintresolve(session)workspace-write+ 工作区根目录把 argv 包一层受限 argv+ 强制程度 full/partialspawn退出码 + 输出结果

这张图回答的问题:一次"跑个命令"到底穿过了几个可替换的接缝。

几个细节:

  1. 策略解析在消费者边界,不在 provider 里。ctx.sandboxPolicy.resolve()拥有优先级和根目录回退逻辑,“所以 bash 和 fs 不用各自重复一遍”。
  2. 工作区根目录来自会话不可变的 cwd(正常工具调用),无 agent 的调用才回退到部署配置。
  3. 假如你在弹窗里选了"这次拒绝",链条在第 4 行就断了——rejected不是allowed-once,工具调用以拒绝结束,模型会看到一个失败结果并且知道是被拒绝了,可以据此改口。

把这条链换掉是这一篇的落脚点。想让pnpm lint跑在公司的远程执行环境里?换ctx.fsctx.subprocess两个 provider —— bash、持久终端、LSP一起搬过去,工具代码、审批逻辑、日志格式,一行都不用改。

这就是"一切皆插件"这句口号,在一次真实的 lint 命令上的兑现方式。


上一篇← 10 · 工具注册表与执行管线
下一篇→ 12 · 完整重演:一句话请求的完整旅程
回到→ 系列索引


📚 返回专栏目录

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 12:02:05

Flutter推送通知技术:本地与云端方案深度解析

1. Flutter推送通知的技术选型与场景分析在移动应用开发中&#xff0c;推送通知是提升用户留存和活跃度的关键功能。Flutter生态提供了两种主流方案&#xff1a;local_notifications用于本地通知&#xff0c;firebase_messaging则处理云端推送。这两种方案并非互斥&#xff0c;…

作者头像 李华
网站建设 2026/9/15 12:01:33

SSM+Vue构建抗疫物资管理系统的技术实践

1. 项目概述&#xff1a;抗疫物资管理系统的现实意义与技术选型2026届计算机相关专业毕业设计选择"抗疫物资管理系统"作为课题&#xff0c;具有极强的现实意义和应用价值。这个选题源于近年来公共卫生事件频发背景下&#xff0c;物资调配效率直接关系到应急响应能力。…

作者头像 李华
网站建设 2026/9/15 12:01:23

基于ECharts的物流大数据可视化平台源码解析

简介&#xff1a;基于ECharts的物流大数据可视化平台源码&#xff0c;定位于物流行业数据分析与智慧仓储监控场景&#xff0c;面向前端开发者、物流信息化学习者及运营管理人员&#xff0c;旨在通过直观图表解决海量物流数据难以理解和决策效率低的问题。这套源码融合ECharts、…

作者头像 李华
网站建设 2026/9/15 11:58:00

Python爬虫实战:美食数据抓取与分析全流程

1. 项目概述&#xff1a;当Python爬虫遇上美食数据最近在做一个有意思的Side Project——用Python爬虫抓取全网热门食谱数据并分析"味蕾趋势"。这个项目源于一个简单的观察&#xff1a;每次想尝试新菜谱时&#xff0c;总发现不同平台推荐的菜谱差异很大&#xff0c;究…

作者头像 李华