Deepseek harness全景与可能的风险
由三块组成:dsh 本体(主战场)、Super-agent 原型(研究分支)、插件生态(外包出去的部分)
架构:没有特权内核,一切都是插件
dsh 跑在 ==Cordis ==之上。
官方架构文档的原话是:没有需要打补丁的特权内核——你通过「在别的插件旁边挂一个插件」来扩展 dsh,而注册动作是 effect,插件卸载时自动回退。模型适配器、工具注册表、会话日志、agent loop 本身,全都是可替换插件。
这点在包结构上能验证。
核心包各占一个 ctx 键:
core/session 拥有只追加的 SessionEvent 日志与内存存储(ctx.sessions);
core/system-prompt 负责提示词分段与工具 schema 装配(ctx.systemPrompt);
core/tools 是带作用域的工具注册表与受保护执行管线(ctx.tools);
core/agent 定义 Agent 接口与注册表(ctx.agents);
core/agent-loop 只是实现该接口的默认驱动器(ctx.agentLoop)——这句话是「连 loop 都能换」的工程依据。
core/scope(按 agent 的作用域注册原语)与 llm/llm(消息与流词汇表加适配器接缝)。
组合方式值得单独说,因为它是后面插件冲突问题的根。 运行中的 dsh 是一棵启动时按有序层组合出的插件树。
层序是:profile 列出的有序 bundle → 该 profile 的 cordis.patch.yml → home patch → --patch 覆盖层。
关键在语义:patch 按 row 的 id 定位,然后整行替换掉它的全部配置,或者插入新行。这是行级替换,不是深度合并。preset patch 在同一优先级体系内、作用于该声明自己的 config.plugins。
这个设计本身是合理的——行级替换让「上层覆盖下层」的语义清晰可预测。但它同时意味着:两个插件若争用同一个界面挂载点或同一个 row,结果不是合并,而是后者整行盖掉前者。这不是偶发的 bug,是组合模型的必然。
官方文档甚至把这个语义写得很清楚,说明他们预见到冲突,但没有提供任何检测工具。
主循环:事件化的 ReAct,以及一个必须注意的扩展点
主循环是标准 ReAct,但被拆成事件而非硬编码的 while。
一次 step = 一次模型请求加上它触发的全部工具调用;
一次 turn = 零或多个 step,从认领第一条输入开始、到没有欠账为止。
turn/start
认领下一步输入 + 一条排队消息
快照 Session 的模型选择;装配提示词分段 + 本步准入的工具 schema
-> agent/pre-step
step/start
agent/request -> prepareCall
绑定适配器后提交 system / user 消息
记录 request/header 与 request/context
从日志冻结模型历史
llm/stream -> assistant-stream
tool/call -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result
step/end
还有欠账或新输入 -> 下一个 step;否则 -> agent/turn-stopping
turn/end
事件分三个域,用途不同:
session 事件是写进日志的持久事实(跨重载存活);
agent 事件(agent/)携带活着的 Agent,用于观察或拦截在途工作;
capability 事件(fs/、tools/、telemetry/)把策略与适配器接到接缝上,而不必导入主循环。
其中 agent/pre-step、agent/request、llm/stream 与三个 tools/* 是瀑布式事件,监听者必须调 next() 才会委派下去;agent/turn-stopping 是串行的,没有 next()。
这里有一个必须单独指出的事实,因为它决定了整个安全模型的上限:
== agent/pre-step 的监听者可以接受、改写或拒绝已认领的消息。也就是说,任何插件都能在模型看到之前改写用户消息、或者改写装配好的系统提示 ==
再加上插件本身就拥有 harness 的全部权限,
结论是:「模型可见的一切」不仅可以被日志记录,也可以被任意插件改写。
官方在两处刻意做了收敛,值得肯定。
一是模型选择在 agent/pre-step 处快照,重试保留同一次选择,保证一个 turn 内模型不会中途漂移;
二是 重试不重复装配、也不重跑 agent/pre-step。
后者是一条清晰的幂等边界:
重试是「同一请求再发一次」,不是「重新走一遍流水线」。这类边界往往是一个 agent 运行时是否可靠的真正分水岭。
沙箱:词汇表只覆盖文件效果
这一节是全章最重要的部分,因为几乎所有关于 dsh 安全的讨论都建立在对这三档的误读上。官方接口定义写得很清楚:
/**
* File-effect policy for confined processes. `read-only` permits only required
* sinks such as `/dev/null`; `workspace-write` also permits the workspace,
* explicit extra roots, and backend-defined temp areas; `danger-full-access` bypasses confinement.
* Network and process visibility are outside this vocabulary.
*/
type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
逐句读。
- 第一,SandboxMode 管的是文件效果(file-effect policy),不是「安全」。
- 第二,read-only 只放开 shells 必需的 sink(如 /dev/null);workspace-write 再放开工作区根、显式附加目录、以及后端承诺的临时区。
- 第三,也是最常被忽略的一句:网络与进程可见性不在这个词汇表的范围内。
这就解释了一个看似矛盾的现象:为什么在最严的 read-only 档下,一个探针插件仍然能连上外网。不是实现有 bug,而是「连外网」从来不在这三档的管理范围里。把这三档当成安全等级、以为选了 read-only 就安全了,是对接口的根本误读;而 UI 把它们并排展示成三档权限,客观上鼓励了这种误读。
第二层误读在 danger-full-access。
接口注释写的是 bypasses confinement,而实现上更彻底:只有前两档能发给 provider;danger-full-access 的消费者直接 spawn 原始 argv,根本不调用 ctx.sandbox。“spawn 原始 argv”意思是:直接把调用方给的命令和参数数组原样拿去启动一个子进程
也就是说,所谓的「三档」实际是「两档强制 + 一档旁路」——最右那档不是「最宽松的沙箱」,而是「不做沙箱」。
第三层是强制力本身有等级。
SandboxEnforcement 只有 full 与 partial 两个值,partial 意味着当前后端或较旧内核 ABI 无法覆盖该档承诺的每一个文件效果。
官方点名的 partial 情形包括:较旧的 Landlock ABI,以及 Windows ACL 后端的硬链接、未受限读、AppContainer ACL 边界。这一点能解释一个社区现象——有插件在 Windows 上把权限强制拉到 danger-full-access,作者的注释理由是「Windows 上官方没有可用的沙箱后端,不设成完全访问 bash 根本跑不了」。
结合这段文档看,这个说法有其技术依据:Windows 路径的强制力本来就是 partial,read-only 也不完全。
设计上也有做得细的地方,值得如实记下。
策略是按调用携带的(SandboxPolicy per call),不在 provider 上固定,所以两个消费者可以在同一时刻用不同边界(一个 bash 走 read-only,一个受限子 agent 需要写自己的状态目录),而批准后的升级重试是一次新调用、带更宽的策略。
另外,不同沙箱后端在“拦住命令”时,报的错不一样:
后端 只读绑定被拒时典型错误
bwrap(bubblewrap) EROFS(Read-only file system)
Landlock EACCES(Permission denied)
Seatbelt(macOS) EPERM(Operation not permitted)
注释明确说:不能用跨后端的并集。因为如果统一匹配 EROFS|EACCES|EPERM,在 bwrap 后端上就可能把某个本来不是“沙箱拒绝”的 EACCES 误报成拒绝——而这个后端根本不会用 EACCES 表达沙箱拒绝。误报方向反了。
——注释明确说不能用跨后端并集,否则会误报某后端根本不会产生的拒绝。
还有一处容易被忽略的严谨:命令跑完,退出码非零,可能是两种情况:
沙箱正常拦住了命令(这是能力,预期行为);
沙箱 runner 自己挂了(这是故障),
必须先过信息行排除再匹配致命签名,从而把「沙箱 runner 挂了」与「沙箱正常拦住了命令」分开。后者是能力,前者是故障,混为一谈会让排障方向完全错。
Super-agent 原型:把系统层的老办法搬进 Agent
问题意识:
单任务编码智能体已能用工具解决复杂问题、人的角色上移到决策与验收,但现有 Agent 以单 Session 为单位,多任务能力受人的注意力限制;简单堆 Session 又会碰上同步阻塞、上下文割裂、设备离线即停。
目标是四个特征:连续、高效、主动、持久。
解法是双层分离。
常驻 Router 层承接人类交互、汇总跨任务决策与记忆、充当全局序点,但不执行具体任务,甚至可不配 Bash;
Worker 层上下文隔离、挂满执行工具、不直接对外说话,只向 Router 汇报,由 Router 统一回复。
事件用快慢双路径过滤:程序层做确定性准入,语义层再调模型判断;
已订阅 Thread 的后续事件由程序直通 Worker,绕过 Router。
Router 会不会成瓶颈?
做法是让积压变批处理:上一轮推理期间到达的事件先排队,下一轮一次性注入,Prefill 阶段并行计算,单次请求耗时增长远慢于批处理规模。在一定范围内,积压越多,单个决定分摊的推理时间反而越低。
数据:一半任务 12.4 分钟完成、95% 在 15.7 分钟内完成;去掉订阅直通后 Router 决定数增加约 30%、读缓存 token 从 2323 万涨到 3177 万;批处理把单个决定分摊的推理时间从 6.4 秒降到 1.6 秒。要连着边界读:合成负载、简化环境、团队自认初步参考;而且本质是拿更多 token 换时间与稳定性,读缓存涨近四成就是账单上的证据。
README 明示:研究原型、非官方产品;代码来自团队日常开发、相当一部分工程由编码智能体完成;与文章描述的实现并不一致。
功能也窄:面向单个 GitHub 仓库的主动型编码智能体,打包成 dsh 的 bundle,监听 GitHub(可选接飞书),有人提需求时开工作项、在自己的会话与目录里干活、回原会话回复(比如提 PR),另有网页面板显示等人处理的事项。
它的原则写得很好——「尽可能少占用人注意力,尽可能多交付价值」:只在被要求、或团队写下的规则允许时才行动,只在必须有人处理或结果可用时才说话。
Workflow:扇出编排,以及两个结构性缺口
执行层还有一条比 Super-agent 更少被讲的编排路线:workflow。
模型通过 tool-workflow 提交一段受限 JS 脚本,脚本里只有几个原语——agent() / pipeline() / parallel(),外加 phase() 分组、log() 与 args;agent() 可带 object-rooted JSON Schema 拿结构化输出。上限都写在工具描述里:
| 限制项 | 值 |
|---|---|
| 并发 agent() | min(16, CPU-2),0 表示自动 |
| 单次运行总量 | 1000 个 agent |
| 单次 parallel / pipeline | 4096 项 |
| 脚本同步片 VM 超时 | 5000 ms |
有一个设计值得记:
误用是 fatal,不是变 null——坏参数、未知选项、不支持的 schema、撞上限都直接终止整个脚本。
它把「我的脚本写错了」和「子 agent 失败了」分开,否则调试时会把配置错误误判成任务失败。
发布方式是软发布:系统提示词写明 ONLY when the user explicitly asks,模型不会主动推。
它的两个结构性缺口来自数据流设计。
- 事件流只有六种(workflow/start、phase、log、agent-start、agent-end、end),全部是「运行发生了什么」,没有一种是「得出了什么」;
子 agent 的产出走脚本变量、状态走事件流,两条管道互不相通——所以进度视图在结构上不可能显示「这一步判断是什么」,这不是 UI 没做完,是判断根本不在这条管道里。- 上下文同样不共享:
workflow 默认 spawn,每个 agent() 都是全新子会话;
对照的 fork 才继承父会话前缀,而 agent() 的 opts 里没有这个选项(那个 provider 是 LLM provider,不是 spawn 还是 fork),fork 只能靠插件配置整体切换,且 seed 只到上一个 turn 结束。
数据传递只剩返回值一条通道,每步交接只留结论、丢掉推理过程——「电话传话游戏」的结构,下游永远不知道上游为什么这么判。推论:事件流是观察日志,不是流程轨迹;依赖关系、分支条件、阶段完成与否、阶段产出,都只存在于脚本变量里,引擎不可能推断出来。
于是有两个缺口。
缺口一:没有审阅界面。
前端 ui-workflow-run 拿到的只有身份与状态(seq / label / childId / status),能跳进子会话,但看不到「这一步结论是什么」,也没有驳回重做——监控回答「谁在跑、成没成」,审阅回答「结论是什么、依据是什么、我认不认」,两者粒度不同。
缺口二:没有固定评测流程。
只有端到端口径:端到端评测只能告诉你 26 题对了 11 题,分阶段口径才能告诉你某阶段 73% 全错——前者是复盘,后者才能改进。
痛感取决于任务形状:五十个 agent 各扫一个文件的扇出场景,中间任何一路只需要成败,监控界面就够用;判断链式任务才是被这个结构排除在外的形状。
它为什么没被推广,三个信号合起来看比较公道。
延期项记录在案:无 registry、无 slash command、无 resume / journal,进度视图无暂停 / 重启 / 停止 / 保存——一个没有「保存与恢复」的编排能力,用完一次还得重新描述一遍,还没到能被评价的阶段。
提示词限死更像怕误用:1000 agent 一次误触发消耗的配额不小。而社区至今没长出「workflow 可视化 / 保存 / 恢复」插件,指向用例本身窄——多数人的任务十几个 agent 到顶,用普通 subagent 就够,不需要「写 JS」这道门槛。
代码质量信号则不指向引擎差:workflow-ptc 的测试 46.9 KB 对着 15.3 KB 的 runtime、受限 JS 运行时实现 41.7 KB、误用即 fatal 是踩过坑才有的设计、VM 明示不是安全边界、PTC 进程隔离加六种 subagent provider。
合起来:不是引擎不行,是没做完、怕误用、用例窄。
缺口能不能用插件补——这恰好是「内核开放、边界外包」的又一次实测。
插件侧的钩子是齐的,以下均经代码核实:ctx.subagents 六种 provider 派生子 agent(spawn-in-process / fork-in-process / claude-code / codex / dsh-sdk / acp)、tool-subagent-control 的 wait-agent 有明确等待语义、ctx.userQuestions 的 await wait.done 是真挂起而非轮询、ctx.tools.register() 可注册工具、ctx.slots 五种 UI slot(single / list / keyed / chain / hoc)、ChatNodeDataMap 声明合并可自建聊天节点类型。
但真前置校验在 workflow 脚本里做不到,三个硬约束:脚本无 I/O(无文件、无网络、无定时器、无 Node API,无法在运行中等一个「已批准」信号);phase 只是分组标签,引擎不校验顺序;事件里没有结论,只能靠脚本作者给每个 agent() 传 schema——而这是约定不是引擎保证,插件必须处理没有结构化结论时的降级显示。
| 方案 | 做法 | 代价 | 真前置校验 |
|---|---|---|---|
| A. 只做审阅面板 | UI 插件 + 脚本 schema 输出结论 | 低 | ❌ 只能事后审 |
| B. 阶段拆成多次 run | 一次 run 只跑一个阶段,人在 run 之间裁决 | 中:丢 run 内共享上下文与缓存 | 🔸 有校验但上下文断 |
| C. 自写编排工具 + UI 插件 | 用 ctx.subagents + ctx.userQuestions 实现阶段状态机 | 中高:自写状态机、持久化、UI | ✅ 完整 |
A 是事后看,B 是断上下文换前置,C 是自己当编排层换“前置 + 连续”。C 值得做,但它的价值边界是“让错误可检测”,不是“让判断变对”;它的成立前提是那两个接口假设经得起实测。
路 C 的本质值得点破:userQuestions 的 await wait.done 证明引擎层本来就有「等外部信号再继续」的基础设施,只是没接到 workflow 脚本上——所以路 C 不是手搓 agent,是手搓阶段控制器,模型循环、会话、工具、UI 全部复用 dsh。
动手前有两个前提要验证:
插件 API 不是稳定契约,workflow 自己还压着一批延期项,你依赖的接口可能变;userQuestions 从代码看更像给模型的 ask_user_question 工具用的,插件直接调用是否被允许需要实测。这是路 C 能不能成立的关键前提,不是设计问题,是权限/契约问题
另外两条经验同样来自这份调研的踩坑记录:
固定阶段只对「前一阶段失败则后阶段必然无意义」的任务成立,开放式任务套四阶段是形式主义——阶段不是越多越好,是依赖关系真的存在才成立。
分阶段能约束「它在回答哪个问题」,约束不了「它答得对不对」——它买到的是让判断错误变得可检测,而不是判断质量本身。
把这条线并进本章图景:
执行层现在有两条正交的编排路线——Super-agent 解「连续与主动」,workflow 解「量大」,而「中间判断可复核」两条线都没解,三家对照也确认阶段结论物件与逐阶段前置校验谁都没有(第七章)。
补缺口需要的钩子(subagents / slots / userQuestions)全部开给插件,这既是本章反复批评的治理缺位的另一面,也是它作为平台可扩展性的证据——同一个事实,取决于你想用它,还是想依赖它。
Subagent provider:六种后端的隔离与继承语义
前两节的 Super-agent 与 workflow 都站在委托之上,这次把委托本身拆到底。
ctx.subagents 是一个具名 provider 注册表——委托是接口,不是实现。管理器独占启动、消息准入与整个生命周期:
spawn 与 fork 只贡献「分离的创建输入」(prepareContinuable,产出有持久会话、可续可中断的本地子代理),
四个外部 provider 只贡献「一个执行句柄」(start,一次性、拒绝追问)。
provider 契约本体极薄:名字、四个能力位(agentOptions / agentPreset / outputSchema / depthLimit)、一个 inheritsParentContext 描述位——文档特意写明后者「对工具注册、注入服务或权限继承一无所知」,它只决定给模型的措辞:fresh 后端说「它看不到本对话」,fork 后端说「它看不到进行中的 turn」,让模型永远不必复述不存在或不缺失的上下文。
| Provider | 进程与运行时 | 子会话拿到的上下文 | 可续 / 可中断 | 起始能力位 |
|---|---|---|---|---|
| spawn | 本进程,共享 agent factory、LLM 与工具服务 | 空会话;默认继承父的 cwd、lineage、provider / model / effort 与输出上限 | ✅ 可续(持久会话、send_message、interrupt) | 四项全开,且是唯一支持独立选 agentPreset 的后端 |
| fork | 本进程 | 父已完成 turn 的前缀——一次性快照,进行中 turn 永不进入;首个 turn 之前等同于 spawn | ✅ 可续 | agentOptions + outputSchema + depthLimit;不支持独立 preset(并入父当前组合);官方工具刻意禁用子模型选路 |
| dsh-sdk | 子进程,完整 dsh 运行时:独立 home、profile、有序 patches;每次一进程,无池化 | 无父上下文,只有任务文本与 cwd 跨界 | ❌ 一次性 | 仅 agentOptions 的路由四字段(provider / model / effort / maxTokens);preset、结构化输出、深度上限一律拒绝 |
| acp | 子进程,任意 ACP agent(command 自配);每次一进程,无池化 | 无父上下文,只有任务文本与 cwd | ❌ 一次性 | 全部拒绝(不声明任何可选能力位) |
| codex | 子进程,官方 app-server 协议:每次新进程 + 一个 ephemeral thread + 恰好一个 turn | 任务文本;原生配置与认证保持权威 | ❌ 一次性 | 全部拒绝 |
| claude-code | 子进程,官方 Agent SDK:每次一条 fresh query,独立 CLI 进程与非持久产品会话 | 任务文本;原生配置与认证保持权威 | ❌ 一次性 | 全部拒绝 |
继承语义值得逐条抠,因为这里就是「上下文隔离」四个字的全部内容。
fork 只继承对话历史:官方原话是「seed 只携带对话历史,子代理拿到全新的工具作用域,拿不到父代理的任何权限」;父的 agent-local 工具限制也不被复制。
spawn 的「继承」只是环境参数:cwd、lineage、模型路由、输出上限,对话为空。
外部 provider 的世界里父只剩两样东西:任务文本和 cwd——dsh-sdk 与 acp 都写着同一句话:「JSON-RPC wire 是序列化边界」,同进程的值不做防御性拷贝,协议边界才是校验敌意输入的地方。
所有外部 provider 还共享一条凭据纪律:先洗掉凭据形状的环境变量、再叠加显式 env,所以示例里给子进程传 API key 必须显式写在配置里——洗掉之后不会静默继承。
隔离的细节里有三个可以直接拿去审计的硬约束。
第一,诊断有安全边界。
结果里的 diagnostic 上限 4096 UTF-8 字节,且契约明令不得含工具输入、文件内容、环境变量值、凭据与原始协议 payload——五个外部 README 各自复述这条,失败细节回父会话,但不带走子进程的敏感内容。
第二,原生配置权威。claude-code 后端故意不传 SDK 的 settingSources:子 Claude Code 会读宿主正常的 user / project / local settings(相对子 cwd),dsh 不复制也不过滤这些文件、不落登录态、绝不回退宿主 claude 可执行文件——平台 payload 钉死 Agent SDK 0.3.263,codex 后端同样钉死 @openai/codex 0.153.4。
第三,权限全部机器应答、无人路径。
acp 的 permission 默认 reject、可选 allow(取第一个 allow_once / allow_always 选项);
codex 默认 never,可选 approve-for-me(auto_review + workspace-write)与必须显式选择的 dangerously-bypass-approvals-and-sandbox;
claude-code 默认 dontAsk,一路到 bypassPermissions。
四个外部后端全部「unattended by design」:未知请求 fail closed,不挂起等人。
清理也没有商量余地:acp 与 dsh-sdk 的 dispose 是 stdin EOF → SIGTERM → SIGKILL 的三段升级链,claude-code 与 codex 走分层终止(3000 ms 档);没有墙钟超时,也没有副作用回滚——官方明说取消前已改的文件与外部系统不会还原。
结果的词汇表同样是封闭的。
stopReason 只有五个值:completed / aborted / error / max-tokens / refusal,刻意镜像 harness 自己的 turn-end 词汇,「工具层能把非 completed 结果映射成 isError 工具结果」;
各家原生终止各有映射——dsh-sdk 把 blocked 映射成 refusal,acp 把 end_turn 映射成 completed。
模型侧工具不等结果:一次调用只返回 started subagent ,结果走 completion notice。
最值得记的是持久化里的一处区分:子代理的结算通知与子代理自己发的消息是两种不同的 source kind,官方注释写得很直白——结算消息是「管理器陈述孩子的下场」,若与孩子亲笔的消息合并进同一份 transcript,「会把孩子没说过的话记在孩子头上」。审计视角下,这是把「谁说的」这件事在数据结构层面做对了。
深度与缓存两件小事也写得很诚实。
委托深度默认取 Host 设置(初始 1,0 为禁止),外部后端用 provider-managed 把递归预算交给子进程自管;
到顶时模型不靠猜——一条 runtime-context 快照直接告诉它「当前 1、上限 1、剩余 0,别再委托,直接干」。
fork 侧则有一条少见的工程取舍:子会话可复用父请求的字节相同前缀(同 provider / model 下 KV cache 命中),所以官方 fork 工具刻意禁用子模型选路——选路一变前缀复用即断,官方留了一句「选路保持禁用,直到改动能保住复用、或能暴露有界的重算成本」。
最后是这套 registry 的战略含义:
四个外部 provider 用的是对手自家的官方 kit——Anthropic 的 Agent SDK、OpenAI 的 app-server 协议。这不是重新实现竞品,而是把竞品挂成自己的一个执行后端;「把 Claude Code 当 worker 用」。而治理层的一条新配置面随之出现——外部 provider 的 permissionMode 是独立于 dsh 沙箱的第二套权限面,展开在第十章。
插件生态:治理缺位是结构性的
生态是外包出去的部分。官方对生态的全部治理,就是 README 与 CONTRIBUTING 里各一句「给仓库打上 dsh-plugin 标签」。
零门槛的后果可以用漏斗量化:打标仓库 11,439 → 按 dsh 规矩可装 2,143 → 真有人用 955,前一段掉 81%,后一段掉 55%。
更值得琢磨的是这件事本来不必如此。团队在设计记录里留了一篇发布前四天(2026-08-09)的笔记,逐项列着被移除的分发机制:
repository 插件包、插件专用编写格式、prepare 可执行文件、生成的包装层、不可变 repository 缓存、以及一条专用 GitHub 验收流水线,并明确不保留兼容或迁移机制。理由是与 profile 组合包路径重复实现了安装与组合。
这个取舍从工程角度站得住:两套安装机制确实冗余。但值得指出取舍的范围——被删的是「分发路径」,「发现」与「信任」这两层从来没有被实现过,也压根没出现在那篇记录里。一个留下上千篇设计记录、连被否决方案都公开归档的团队,在关于插件分发的那篇取舍里,几乎没有讨论用户怎么找到插件、凭什么信插件。这不是没想清楚,而是默认这些属于用户侧。
结合上面的组合模型,缺位的后果可以推出三层:
发现层靠 GitHub 标签而安装靠 npm,两套命名之间没有映射也没人负责对齐(有抽样发现 12 个未发布正式包的插件里有 5 个在 npm 上存在同名包,且都是另一个作者的另一个项目);
仲裁层缺失,因为 patch 是整行替换、又没有冲突检测,下载量前十装一起直接起不来(冲突的第 2、3、6 名,UI 全家桶自带侧边栏与独立侧边栏抢同一路径);
信任层缺失,没有签名、审核、权限声明或安全上报通道。
11 月 5 日团队披露的数据,第一次给这个生态补上官方数字:
截至 11 月 4 日,Harness 日活 154 万、月活 548 万(官方 API 口径),日消耗约 92 万亿 token;第三方插件约 3.5 万个,约六成活跃用户创建或安装过至少一个。
数字要连着口径读:3.5 万是官方说的「第三方插件」总数,与八月普查的 11,439 个 dsh-plugin 打标仓库差了三倍,官方没有解释差值来自哪里,但时间上正好落在 Creator Mode 放开用户自产插件之后。
更值得注意的是计划本身:官方宣布将建插件市场,配套创作者支持计划(token 激励、技术交流、官方推荐),并改进插件 API 的稳定性、兼容性与安全。
这是「发现」层第一次从 GitHub 标签收回官方手里,但「信任」层——签名、审核、上报通道——仍未出现在计划里。
而市场一旦官方化,分发集中了,责任也跟着集中:护栏问题会从「GitHub 标签靠不靠得住」变成「官方推荐靠不靠得住」,后者的杀伤力只会更大。
插件生态里已经长出来的东西:记忆层与多 bot
上一节讲的是治理缺位。但生态里也真的长出了两个方向的产品,它们恰好补上了官方刻意不碰的那一块,值得单独看——因为这两个方向都不是"再来一个 coding agent",而是把 dsh 当成一个可常驻的运行时来用。
方向一:记忆层被做成外挂插件。
代表是 deepseek-bot(包名 ds-bot,MIT,约 205 星),自我定位直接写着 agentic memory 加消息网关。它把记忆分四层,并明确写了每层由谁写、agent 何时看到:
| 层 | 存在哪 | 谁写 | agent 何时看到 |
|---|---|---|---|
| 工作记忆 | 当前会话日志 | harness 自己 | 始终 |
| 身份 | SOUL.md、AGENTS.md | 人(安装时脚手架生成) | 每轮 |
| 语义 | USER.md、MEMORY.md、memory/ 下按主题分文件 | agent(人可编辑) | 每轮 |
| 情节 | ~/.dsh/recall//recall-index.jsonl | 宿主侧索引器 | 当消息指向过去时 |
这张表里有三个设计决定比"分层"本身更值得记,而且每一个都直接对应 dsh 自身的架构缺口。
- 第一,它利用了 dsh 的会话日志这个底料。dsh 的 session log 是只追加、事件溯源的,插件作者的原话是"harness 给了记忆一个真正的基底"。但他也点出了一个事实:dsh-base 自带的会话全文检索默认是关的(path: ‘:memory:’、openAt: never),而且那个索引的消费者是 Web UI 与 @session 引用源,agent 侧既没有工具也没有注入上下文。所以记忆不是"官方给了你不会用",而是"官方把底料备好了,消费者自己接"。
- 第二,召回索引故意让 agent 读不到。索引放在 agent home 之外,沙箱 runner 与工具的读范围都拒绝它,agent 只能拿到注入的那一个块。这条设计是踩坑踩出来的——这个索引最初就放在 agent home 里,结果agent 用自己的工具就能读遍所有频道的历史。这是真实的横越泄漏,不是理论风险。换成 dsh 的语言说:只追加日志是它最被称赞的优点,而把优点暴露给 agent 本身就是漏洞。
- 第三,召回被限制在频道内,且默认不插话。每条索引记录都带它所属的频道,注入时只排本频道那部分;排序按词的稀有度(IDF),只在消息明确指向过去时才把候选交给模型挑,最多四条。这里的取舍值得注意:它放弃了向量检索,改用"词面匹配 + 模型挑选"的混合方案,作者给的实测是同一批事实在五个近义词干扰下,纯词面 8 次答对 4 次,加上模型挑选后 8 次全对(一条路由、小样本)。这是一个很小但很诚实的数字。
还有一个容易被忽略的成本细节:
SOUL.md 6,000 字符、AGENTS.md 12,000、USER.md 与 MEMORY.md 各 4,000——超预算的文件是静默截断的,尾部直接不再进入 prompt、且不写任何日志。
所以作者专门写了个 persona-check 工具来报溢出的确切数字、报警同一事实被写进两个文件(会被注入两遍)、在发现密钥时直接拒绝。这条经验对所有自建记忆的人都适用:记忆的失效往往不是"忘了",而是"被挤掉了却没人告诉你"。
方向二:bot 变成了多 agent 的组织形态。
另一个 DS-Bot(Apache-2.0)走的是反方向:它把 dsh 变成"一支有名字的 bot 团队",每个 bot 有自己的记忆与人格,一个主 bot 负责分派,群里还有 bot 与 bot 之间的互相委派。它明确写了群聊成本明显更高,所以默认只让一个管理员 bot 回答,需要时才把别的 bot 拉进上下文。
把这个方向和第五章的 Super-agent 并排看,会发现两者在解同一个问题的两端:Super-agent 用 Router 加 Worker 的双层分离解"上下文隔离与人的注意力",DS-Bot 用"每个 bot 一份记忆 + 显式委派"解同一个问题。
差别在于信任模型:Router 是程序化的、由 harness 决定谁被唤醒;bot 团队是社交化的、由人和 bot 相互 @ 决定。前者可预测,后者可解释。
还有一个把两个方向合在一起的项目:DeepSeekBot(品牌名 Tobs,MIT,2026 年 9 月中上线、10 月中仍在发版),自我定位「基于 DeepSeek Harness 的开源 GrokBot 平替」。
它本身就是一个 dsh 插件:装一个包同时引入核心运行时、Web 客户端与消息接入,开启 Bot 模式后可以创建多个 PersonaBot——每个 bot 有独立身份、权限和 Git 存档的长期记忆,既能单独对话,也能拉进同一个团队协作,还能再接 DSH 插件生态扩展能力。
它的价值不在技术——记忆与多 bot 走的都是前两个项目铺过的路——而在信号:dsh 正在被当作「个人助理运行时」使用,而不只是编码智能体;「GrokBot 平替」的定位说明这个生态开始对标消费级产品。
风险面也随定位一起变化:身份与权限意味着它会长期持有用户的消息、日程与文件,而它运行的官方插件体系,至今仍然没有签名与审核。
这两条线合起来说明一件对判断有用的事:dsh 把内核开放到极致,恰好给生态留出了官方不做的那一层。官方不做记忆、不做治理、不做多 bot 编排,全交给插件——这既是它被批评的点,也是这些插件能存在的条件。代价同样清楚:这些插件都建立在 dsh 的未发布 API 上,作者自己在 README 里写着"dsh 还是 release candidate,本项目依赖它的内部实现,请钉版本"。
执行层的技术风险
把上面这些摊开后,五种风险可以按「根因在哪一层」重排,这比按症状罗列有用得多。
- 风险一:权限语义与用户理解之间的鸿沟(根因:词汇表设计)。
三档实际是「文件效果策略」,网络与进程不在范围内,最右那档是不做沙箱。用户看到的 UI 是三个并列的「权限等级」,读到的是「read-only 最安全」;
而真实语义是「文件写不写」。这不是实现缺陷,是接口命名与展示方式误导了使用者,而误导的代价在安全上是最高的。
也因此,社区那个探针实验(三档下九项试探全部成功,read-only 档仍读到 ~/.ssh、环境变量里的 API key、写 /tmp、连外网)并不说明「dsh 的沙箱失效了」——更准确的说法是:插件根本不经过沙箱,因为它就是 harness 本身;而沙箱本来也只管文件写。
- 风险二:控制面认证错置(根因:信任边界画错)。
CVE-2026-82533,CVSS 4.0 = 9.4,CWE-807(安全决策依赖不可信输入),影响 0.1.2-alpha.1 之前全部版本,2026-09-08 公开,OX Security 发现。
漏洞本身一句话:本地 HTTP 控制面只校验客户端自送的 Host 头是否为回环,不校验 TCP 连接来源。
危害路径现在能讲得比「可以逃逸」精确得多:默认配置下,一个被沙箱约束的工具执行进程可以够到回环 API(无需暴露端口、无需凭据),进而以 danger-full-access 调 commands/execute、把会话审批策略提为不受限、取走全部已存会话。
而由于 danger-full-access 是「不调 ctx.sandbox」,这一步的实质是把一个受限执行转换成完全不受限执行。它击穿的恰好是「沙箱内的东西无法与控制面直接对话」这个前提。v0.1.1-rc.1 另有 /proc//root 绕过 Bubblewrap 的逃逸,问题形态不同(这次是 confinement 本身有缺口),指向同一类风险:沙箱边界在设计上承担了它承担不起的重量。
- 风险三:插件即 harness,且可改写模型可见的一切(根因:架构本身)。
这条是结构性的,无法靠打补丁消除。插件拥有宿主进程的全部能力(读凭据、写任意路径、连外网),三档权限管不到它;叠加 agent/pre-step 可改写消息与系统提示,意味着恶意插件不只可以外传数据,还可以在源头改变模型看到的世界。
这类风险传统上靠签名与审核控制,而生态两样都没有。官方的自我描述应当被尊重——设计笔记写着「安全与权限不是设计目标」,自指工具集 README 自称「沙箱不是安全边界,请把这套工具当作 bash 权限对待」——但没人会在装插件时去读设计笔记。
- 风险四:成本结构为能力上限付费,不为单位任务成本优化(根因:前缀缓存 + 架构取舍)。
三个机理叠加:
一是确认过的 bug——同时读 CLAUDE.md 与 AGENTS.md,内容相同时指令被重复注入两遍、system prompt 直接翻倍;
二是结构性的——缓存按前缀命中而工具 schema 在前缀里,装一个插件等于往前缀插十几段新说明,从变动那个字往后全部作废;
三是 Super-agent 那类设计自带的——用 token 换速度与稳定性。
初步实测:同模型下轻量 harness 未缓存输入约 4.5K token,dsh 约 47.6K,差一个数量级(测试者自标存在干扰因素、当时仍是预览版);对其它框架约三倍。
官方给了对策(profile / preset 按会话挂最小插件集、Code Mode 把二十份 schema 压成一段 run_code、按轮次与步骤的成本统计),但都要求用户自己知道、自己配。
- 风险五:会话层的韧性跟不上它的宣传(根因:可解释性设计不足)。
社区把官方仓库 321 个 bug 讨论读完后归成八类,超三分之一是 Windows 专属——最典型的是原生文件夹选择器读 UTF-16 路径时只判低字节,会在任何低字节为 0x00 的汉字(开、一、上、下、耀)处静默截断,目录存在却报 ENOENT。
更反讽的是会话层:只追加日志是它最没有争议的优点,也是最脆的环节——重复的 seq 或一段截断的块就能让整个会话加载失败,界面几乎不给提示;上下文压缩失败有两种典型形态(摘要生成不出、摘要达到输出上限),一旦摘要不比被压缩内容短,使用率反而继续涨到爆满。共同点是失败时缺乏可解释性:用户看到「加载失败」,而不是哪一条日志坏了。
还有一个尖锐的自证问题,属于架构争议而非缺陷:
有开发者逐行对照 dsh 与 Codex 源码后指出,Codex 是声明式的(插件就是磁盘上的文件夹、不进进程、改配置重启两三秒),dsh 是命令式的(插件带状态跑在进程内、互相注册,要支持运行时热替换就得处理悬空引用、后台任务终止、依赖链协同、崩溃回滚),为此引入 Cordis,仅管理插件生命周期的核心模块就有 750 行。
用装修打比方:Codex 给精装房加一面洞洞板,挂错摘下来重挂;
dsh 给能在不断水断电的情况下改承重结构的房子。
问题是——你多久改一次承重结构?普查给出了答案:2,143 个可装插件里,真正换掉主循环的只有两个;而换模型的插件有 69 个、头部周下载约六千。最独特、最需要重型运行时支撑的能力,恰好是生态里最少人用的一层。
执行层的深层风险:从一次注入到持久后门
Creator Mode:它到底做了什么
前面几节里,创造模式只以"持久化后门的入口"这个身份出现过。这不完整——它是 11 月 2 日官方主推的产品能力,值得单独讲清它本身是什么,再讲它为什么危险。
官网口号是「你的 Harness,由你定义」(Your Harness. You Define It)。你用自然语言描述想要的功能,agent 把它写成插件、装进自己的运行环境。官方演示里要做个悬浮番茄钟,harness 会依次:加载 Cordis 插件开发技能、检查当前运行环境和可用接口、写插件配置与代码、调用插件管理工具安装、再验证能否正常运行。从开发到安装验证约 5 分 24 秒。
官方同步发布了七组中英文演示提示词,覆盖的范围比"小工具"大得多:
| 示例 | 做出来的东西 |
|---|---|
| 3D 地标建筑展厅 | 独立插件,用 3D 模型展示埃菲尔铁塔、长城、紫禁城 |
| Transformer 三维可视化 | 交互式 3D 演示,讲 Transformer 内部计算过程 |
| 应用像素风界面 | 切换整个 Harness 应用的视觉风格,含像素风在内五种 |
| 海报设计工作台 | 会话内直接调用,改标题、正文、背景 Shader 颜色 |
| 视频编辑工作台 | 类专业剪辑软件的深色界面,多视频上传、时间轴剪辑、导出 |
| 论文阅读器 | 保留原始排版与图片,选中文字可高亮、记笔记、直接提问 |
| 会话统计工具 | 基于真实对话数据,展示 Token 消耗、会话数、成本预算、用量时间线 |
关键在于它的产出形态。它不是"给你一段代码"或"给你一个独立网页",而是让 agent 直接参与自身运行环境的扩展——生成的东西要接进 harness 现有的插件体系,能被安装、能被调用、能改界面。这是它和普通聊天式代码生成的根本区别,也是只有 dsh 能先做出来的原因:它的插件是进程内全权限代码、连主循环都可替换(见第五章架构一节),这种自由度别家架构上给不了。
官方自己给的分寸也要记下:提示词汇集里明确写了,部分演示插件经过多轮对话调整,仅凭一条提示词不一定能复现视频里的最终效果。它降低的是起点门槛,不是保证一次成功。
然后是它为什么危险,而且危险和好用是同一件事。前文列过那条链路,这里把它和 Creator Mode 的产品形态对齐:官方开发文档《通过提示词配置持久化插件》写着,创造模式下 agent 可以写纯配置组合包、在 patch 里插入 MCP client、再通过 plugin_manager install_bundle 安装,且「插件配置属于当前 profile,影响其会话,并在进程重启后保留」。于是完整链路是:一次提示注入 → agent 在创造模式下被诱导写一个插件 → 落盘到 profile → 重启后依然生效 → 之后每次会话都被它控制。
这条链路比传统漏洞棘手的地方在"合规":用的是官方提供的插件管理工具、官方文档教的流程,界面上就是一个日常操作。而 Creator Mode 恰恰是把"让 agent 自己装插件"从冷门功能变成了主推卖点的动作——演示里那个番茄钟装成功了,同样一条路径也能装别的。
所以对它的使用规则要写得比别的功能更硬:不在创造模式下处理不可信内容(外部网页、他人仓库、第三方文档);造插件时在一次性环境里造,造完迁出去;定期 diff profile 目录与 cordis.patch.yml。它降低了"造工具"的门槛,同时也降低了"在你机器上装一个你不完全理解的工具"的门槛——后者才是这一节真正要记的。
前面列出的风险是"当时可知的",这一节是我反复追问"还有什么没找到"之后补出来的四条。它们的共同特征是:不是某个函数的 bug,而是设计组合出的性质,因此更难通过打补丁消解。
- 风险六:提示注入可以变成跨重启的持久代码执行。这是我认为最被低估的一条,因为有官方文档背书而非安全问题。官方开发文档里有一篇《通过提示词配置持久化插件》,讲的是在创造模式(Creator mode)下,agent 可以写一个纯配置组合包、在 patch 里插入 MCP client、再通过 plugin_manager install_bundle 安装,并且明确写着「插件配置属于当前 profile,影响其会话,并在进程重启后保留」。创造模式本身的产品形态见下一节。
把这句话和另外两个事实叠起来,性质就变了。
第一,插件本身就是 harness、拥有宿主全部权限(前面已论证)。
第二,agent/pre-step 允许插件改写模型看到的输入。于是攻击链变成:一次提示注入 → agent 在创造模式下被诱导写一个插件 → install_bundle 落盘到 profile → 重启后依然生效 → 之后每次会话都被它控制。这不是"这一轮被带偏",而是在用户机器上装了一个常驻后门,且安装动作由用户自己的 agent 完成。
为什么它比传统 RCE 更棘手:
传统漏洞的利用链需要攻击者自己动手,而这里执行者是那个被信任的 agent。而且安装是"合规操作"——用的是官方提供的 Plugin Manager、官方文档教的流程。当前唯一的结构性约束是"必须在创造模式下",这使标准模式相对安全;但创造模式恰恰是官方在 11 月大推的卖点,推广本身在扩大这个面。可操作的缓解很少但很具体:不要在创造模式下处理不可信内容(网页、外部仓库、他人文档);定期检查 profile 目录与 cordis.patch.yml 的变更;把创造模式隔离在一次性环境里。
- 风险七:插件安装阶段的 prepare 脚本在沙箱外执行。这一条是我原本完全没看到的。官方 CLI 在安装从 GitHub 来的插件时会提示:插件可能在安装阶段执行 prepare 构建脚本,这一步由 pnpm 在插件所在的 profile 目录中完成,不属于 Agent 任务的沙箱执行流程。pnpm 默认会拦住这类构建脚本,只有把插件显式加入 allowBuilds 才继续。
这个细节很关键,因为它把上一章"沙箱只管 agent 执行"的结论又推进了一层:安装插件这个动作本身,就已经在沙箱之外了。用户在"装个插件"的心理预期下做了一次 allowBuilds 授权,实际授权的是一次任意代码执行——而这一步连 ctx.sandbox 都不经过。所以"装插件等于把代码放到你机器上跑"这句话可以更精确:比"跑"更早,在"装"的那一刻就已经跑了。
缓解方式是明确的,也应该写进任何评估清单:只装来源可信的插件、锁定具体 commit 而非跟踪分支、在 allowBuilds 之前先读一遍 package.json 的 scripts。allowBuilds 本质是一张"允许在本机执行任意构建脚本"的名单,它不该被当成一次无痛点击。
- 风险八:MCP 工具描述投毒,与"致命三要素"。dsh 支持通过 MCP client 接入任意第三方 MCP 服务器。MCP 生态在 2026 年已经形成一套明确的风险分类,其中最相关的是 tool poisoning:工具描述本身被 agent 当作可信指令,攻击者可以在描述里埋入不可见指令,劫持 agent 行为——用户看不到这些文字,但模型会读到。配套的还有 rug pull(先给一个干净版本获取信任,之后更新为恶意版本)。
更值得警惕的是"MCPTox 测了 45 个在线 MCP 服务器、353 个真实工具"这类实测所揭示的普遍性——这不是理论风险。而 dsh 在该风险上的位置比通用应用更差,原因有两条:一是它的插件与 MCP 工具都是全权限的,没有工具级的最小权限约束;二是有研究者总结的"致命三要素"(lethal trifecta)——访问私有数据 + 暴露于不可信内容 + 具备对外通信能力,三者同时具备时数据外泄几乎无法阻止。dsh 默认三者全占:能读你的文件与凭据、要处理外部内容(网页/仓库/文档)、且网络访问不在沙箱管辖内。
这就是为什么"网络不在 SandboxMode 词汇表内"这条设计事实的后果比单看严重得多。它意味着切断外泄通道这件事,在 dsh 里需要用户在外层自己做(容器出口限制、代理白名单、独立网络命名空间)。评估清单上这应该是第一条,而不是最后一条。
- 风险九:商业化压力与安全投入的结构性错配。这一条不是技术漏洞,但它决定了前八条风险会被多快修复。DeepSeek 在 2026 年完成了首轮外部融资(约 500 亿元人民币、投后估值数千亿元量级,创始人梁文锋保持绝对控股);10 月下旬至 11 月初又被密集报道新一轮融资:多家媒体称总额接近千亿元、宁德时代与腾讯领投、投前估值约 5000 亿元,路透 11 月 2 日称将超 800 亿元,资金主要投向乌兰察布 GW 级智算中心与 16 万颗昇腾 950DT,公司同步筹备 2027 年科创板上市,同时把 API 价格压到极低、把 V4-Pro 打折到原价的四分之一。这套"低价换规模"的打法需要巨量算力与持续迭代来支撑,而安全治理、插件审核、生态运营都是纯成本中心。
从公开证据看,这个判断不是猜测:一个能写下上千篇设计记录、连否决方案都归档的团队,在插件分发的取舍记录里几乎没讨论"发现"与"信任";一个能两周内给 CVE 打补丁的团队,插件生态至今没有签名、审核或上报通道。这不是能力问题,是资源分配的优先级问题——而融资后的增长压力会让这个优先级更难扭转。对使用者的含义很直接:不要指望官方在短期内补上治理层,自己先把边界建起来。
迭代十轮之后,我认为最该记住的三件事【插件安全,风险,治理】
最有价值的不是那九条风险清单,而是三个判断。
- 第一,dsh 的风险几乎都能追溯到"插件即进程内全权限"这一个事实。权限管不住插件、注入可变持久后门、MCP 描述投毒、缓存前缀失效、插件冲突、一键拖垮整个 harness——这些问题形态各异,根因是同一个。这也意味着它们不可能被逐个修好,只能靠架构层增加隔离(子进程、权限声明、签名)来整体缓解,而那是与"一切皆插件"的哲学正面冲突的。
- 第二,最危险的不是漏洞,是"合规操作"。CVE 有编号、有补丁、有公告,反而好防。真正危险的是那些用官方文档教的流程就能完成的动作:创造模式下让 agent 装一个插件、为安装给一次 allowBuilds 授权、接一个看起来正常的第三方 MCP 服务器。这三件事在界面上都像日常操作,但它们各自等价于一次任意代码执行或一次信任边界让渡。
- 第三,能不能用,取决于你愿不愿意自己补上治理层。把 dsh 放进容器、限制出口网络、用独立账号跑、只装锁了 commit 的插件、不在创造模式处理不可信内容——这五件事做完,它就是一个能力很强且风险可控的工具;不做,它就是一个把内核和边界一起交给你的实验品。官方从未隐瞒这一点(SAFETY.md 写得很清楚),只是这个前提在星标爆炸的传播路径里被稀释了。
更多推荐




所有评论(0)