BotOf TechAI / IoT / Full-Stack / 植物养护知识分享
返回首页DeepSeek Harness 代码级分析:『一切皆插件』怎样落成可恢复 Agent 运行时

DeepSeek Harness 代码级分析:『一切皆插件』怎样落成可恢复 Agent 运行时

DeepSeek Harness 不是又一层“提示词 + 工具调用”包装。它更激进的选择,是把模型适配器、Agent 循环、工具、会话持久化、审批、沙箱、Web 界面,甚至运行时自我扩展都做成 Cordis 组件。官方把它概括为 Everything is a Plugin;真正值得分析的不是口号,而是当一切都可替换时,系统靠什么维持顺序、恢复、安全和可观测性。

本文固定在 2026 年 8 月 13 日主分支提交 47f9438,对应 dsh@0.1.0-rc.5。这是 Developer Preview,不应把当前接口当作稳定公共协议。

先给结论

DeepSeek Harness 最有价值的部分并非“支持很多模型或工具”,而是把 Agent 产品拆成三种可以独立替换、又有清晰生命周期的对象:

  1. Definition:某项能力的稳定语义,例如 session、tools、filesystem;
  2. Provider:能力的具体实现,例如 JSONL 持久化、worker code runtime;
  3. Consumer:只依赖 Definition,不直接绑死 Provider 的功能插件。

Cordis context 把三者在运行时连接起来;effect 记录插件安装的每个可逆动作,coeffect 声明插件启动前必须满足的依赖。于是“换一个实现”不再等于“重写 Agent loop”,而是卸载旧 Provider、恢复它的 effects,再装载新 Provider,让 consumer 按依赖变化重新进入生命周期。

但模块化没有消灭复杂度,只是重新分配了复杂度。该提交下 packages/ 内有 219 个 npm package;理解系统必须同时掌握插件树、能力 seam、session 事件契约和工具流水线。它更像一个 Agent 操作系统内核,而不是开箱即用的单体 SDK。

产品不是一棵依赖树,而是一组 profile

dsh-base 是所有产品 profile 的第一层,提供模型适配器、工具、持久化、沙箱/审批、设置、凭据和 telemetry;dsh-web-app 再加浏览器应用;dsh-headless 加一次性无服务 runner。用户最终启动的不是固定二进制能力表,而是若干层配置 patch 叠出的插件组合。

这个分层有两个现实收益。第一,同一个 loop 可以跑在 Web、headless 或 SDK host 内,不需要把 GUI 条件分支塞进核心。第二,安全策略不是散落在 bash、fs、MCP 各工具中的 if,而能挂在 capability event 与统一工具 pipeline 上。

Agent loop:事件日志在前,模型请求在后

packages/core/agent-loop/src/agent.ts 的主链路可归纳为:

inbox 取 user/followup/steer/inject
  → 写入 turn/start 与用户消息事件
  → 从 session log 派生模型可见 messages
  → 调用模型、流式记录 assistant/chunk
  → 解析工具调用
  → 执行工具并按模型顺序持久化结果
  → 有后续步骤则重建请求,否则 turn/end

这里最重要的不变量是:模型可见即已进入日志。Session log 是 source of truth,deriveMessages() 只是从事件流投影模型历史;fork、resume、transcript、telemetry、持久化与 UI 重放都读同一条流。它避免了常见的双状态问题:屏幕看见一套消息、重启恢复另一套、下一轮模型又吃第三套。

Steering 也不是偷偷修改当前 prompt。新的 steer/followup 进入 inbox,并以事件形式加入下一次安全重建点;工具执行结果同样先形成 durable event,再进入后续模型上下文。Agent 的“连续性”因此不是一条长期存活的 Promise,而是一段可重放、可检查的状态机历史。

为什么工具可并发,但结果仍确定

模型可能在一条响应中产生多个工具调用。tool-calls.ts 使用有界滚动池并行执行允许重叠的调用,同时识别 barrier;但提交到 session 的结果仍保持模型声明顺序。后面的工具即使先完成,也不会越过前面的 durable result。

这兼顾了吞吐与可重放性:真实开始/完成时间可交错,模型历史的因果次序却稳定。若 turn 被中止,运行时还会为没有正常结果的调用合成明确的 abort 结果,避免 session 留下“有 call、无 result”的破损配对。

工具不是函数表,而是策略流水线

Harness 的工具路径比 name → handler(args) 多得多。官方的 tool-execution-pipeline.md 描述了一条跨工具族的统一链路:

定义解析 / schema 校验
  → pre-policy waterfall
  → approval
  → monotonic guards
  → around-dispatch(超时等)
  → 工具执行
  → post-policy
  → lossless JSON snapshot
  → finalizeContent
  → tools/result 观察与持久化

“monotonic guard”意味着后面的插件可以进一步拒绝,却不能把前面已经拒绝的动作重新放行。审批先处理需要询问的分支,文件系统的“先读后改”则位于 tool-fs 下层,通过 fs/* 事件守卫具体目标版本。这比在 prompt 中提醒模型“改文件前先读”可靠,因为最终写入仍受版本与观测状态约束。

结果会先做无损 JSON 快照,再进入只允许改呈现内容的 finalize 阶段。这样 session、telemetry 与 UI 观察的是不可变规范结果,插件不能在持久化之后悄悄改变事实。

Code Mode 没有绕过安全管线

Code Mode 把许多工具暴露为 tools.<name>(args) 异步 binding,让模型生成一段程序来编排调用,减少多轮结构化输出的 token 和延迟。风险是“代码解释器成为万能后门”。Harness 的设计是让 run_code 内每个子调用重新进入完整工具 pipeline,继承父 token、审批与 guard,并分别记录 tool/code-dispatch。程序能并发编排,却不能因为套了一层代码就跳过策略。

当前实现仍有工程接缝:Code Mode 的选择存在临时 process-wide 环境变量路径;同一进程混跑不同配置时,这类全局状态应继续收敛为 session/profile scoped service。

Session 持久化:append-only 不等于“随便追加”

基础 profile 采用 JSONL session persistence。协调器不只是把事件 appendFile:它要处理 live session 的权威内存前缀、落盘队列、冷启动检查、破尾修复、HMR adoption 和仍在进行的 open turn。

对 live id,读取先快照内存日志并等待对应前缀 durable;若 turn 尚未闭合,就拒绝伪造“已中断”的结尾。对 cold id,才允许检查并修复 torn tail 或 interrupted turn。HMR 新 Provider 接管时会验证已存前缀与 live history 一致,再补齐内存领先的 suffix。

边界同样明确:存储格式仍是 v0,只兼容少数已知旧记录形状,并不承诺通用迁移。现在适合快速演进,不适合把日志格式当成第三方长期集成协议。

Cordis 如何兑现论文中的时空可组合性

配套论文《A Programming Paradigm for Spatiotemporal Composability》提出两种局部机制:

  • revertible effect:组件每次改变 context 都给出 inverse,卸载时按 LIFO 恢复;
  • reactive coeffect:组件声明所需能力,provider 出现、退出或替换时重新判定生命周期。

Harness 里的对应不是概念类比,而是直接使用同一运行时。工具注册、命令、模型路由、HTTP route、设置项和事件监听都绑定到插件 fiber 的 effect;插件卸载时注册自动消失。consumer 通过 inject 等依赖声明等待 capability provider,依赖尚未满足时不进入 Active。

这解释了为什么 Harness 敢让持久化、模型、tool、UI 都成为插件:可替换性不仅要求“有 interface”,还要求旧实现退出时把注册、listener、route、后台任务和状态观察者全部撤干净,并让依赖者在 provider 真正消失前完成 teardown。Cordis 给的正是这段生命周期协议。

不过论文能力与产品能力之间仍有距离。仓库有 HMR 基础设施和大量 HMR 安全测试,但 Web/headless 的共享产品组合中部分 HMR 目前被显式关闭;能可逆注册不代表每个最终 bundle 已经支持无缝在线换芯。

自修改:能动态长出工具,但不是安全容器

可选 tool-cordis 提供 cordis_define / inspect / run / stop / undefine。Agent 可以定义一段动态 Cordis package、启动它并注册新的模型可见工具;停止或 undefine 会触发生命周期恢复。这正是论文结尾所说的 self-evolving agent harness 原型。

必须同时看到三条限制:

  1. 动态 package 只存在于当前进程内,重启即消失,不会自动晋升为正式源码或持久插件;
  2. Node vm 只是隔离执行上下文,官方明确不把它当安全边界;动态代码应按 bash 同等级别授权;
  3. 该工具集不在默认产品树中,需要显式启用。

因此合理闭环应是“运行时实验 → 记录行为与测试 → 生成持久源码 → 人工/策略审查 → 纳入 profile”,而不是让进程内临时代码无门槛变成永久供应链依赖。

安全模型:默认较稳健,但边界不能误读

基础配置把 telemetry 默认关闭,全文搜索默认不自动打开;工作区预设组合 workspace-write + ask,全权限预设则是显式的 danger-full-access。Web search 可启用,而通用 fetch 因 SSRF 风险默认不进入产品能力。这些默认值说明项目在“功能存在”与“默认开放”之间做了区分。

文件沙箱支持 read-only、workspace-write 和 danger-full-access,并尽量 fail closed;Linux 依赖 bwrap/Landlock,macOS 使用 Seatbelt,Windows 有 ACL 路径与明确的能力缺口。但它治理的是文件系统 effect,不是完整进程安全边界:网络、进程可见性、运行时对象和凭据还要由各自 Provider、审批与宿主隔离共同约束。把 workspace-write 理解成“整个 Agent 已被安全容器化”会高估保证。

这套架构真正难的地方

维度强项当前代价或风险
可替换性loop、host、model、tool、persistence 都经 seam 解耦219 个 package 带来导航和版本协调成本
恢复effect-bound 注册与 LIFO dispose,HMR 测试覆盖广inverse 正确性最终仍由原子插件作者负责
一致性session log 单一事实源,结果按模型顺序提交v0 存储格式尚无通用迁移承诺
安全审批、单调 guard、沙箱、先读后改统一进流水线文件沙箱不覆盖网络/进程;Node vm 不是安全边界
自演化动态 package 能注册新工具并可撤回只在内存存在,持久晋升和治理闭环仍待产品化
HMRCordis 原语天然适合替换,关键子系统有 adoption 测试部分产品 bundle 暂时关闭共享 HMR

最大的工程挑战会是“跨插件不变量”。插件越多,越不能靠每个 consumer 理解所有 provider。Harness 已经用 capability seam、durable event 和 invariant service 把一部分规则上收;下一步需要持续把临时全局开关、格式兼容、网络权限和动态插件晋升也变成可声明、可测试的正式 seam。

适合怎样采用

如果目标只是做一个固定三工具的聊天 Agent,直接采用这套 219-package 架构很可能过重。它适合的是另一类系统:多宿主、多模型、多策略、长会话、频繁增删工具,并且需要在不重启整个进程的情况下替换能力。

稳妥的落地顺序是:

  1. 先把 session log 和工具 pipeline 当作不可绕过的内核;
  2. 只为确实需要替换的边界建立 Definition/Provider/Consumer;
  3. 用 effect 绑定所有注册型资源,并为卸载、失败、HMR 写真实路径测试;
  4. 把 sandbox、approval、network 与 credential 分开建模,逐项验证;
  5. 最后才开启动态 Cordis 自修改,并要求实验产物经过持久化与审查闭环。

DeepSeek Harness 的意义,不是证明“插件越多越先进”,而是展示了 Agent 系统可以拥有一套比主循环更底层的组合内核:模型输出可以变化,工具和 Provider 可以变化,Host 也可以变化;只要事件事实、能力依赖、effect 恢复和策略管线保持稳定,系统就仍然可理解、可重放、可替换。

资料与快照

本文所有源码结论均以提交 47f943859bef60e4160492346772ded9b24f765a 为准;Developer Preview 后续很可能快速变化,版本号、默认 profile 和临时接缝应以新提交重新核对。