DeepSeek Harness 深度拆解:事件溯源、插件架构与 Agent 运行时
从 Cordis、Agent Loop、Session Log、工具治理、Subagent 与沙箱边界出发,分析 DeepSeek 官方 Harness 的架构亮点与生产风险。
先说结论
DeepSeek Harness,命令名 dsh,不是一个简单的 DeepSeek API 客户端,也不只是“LLM、Tools 和一个 while loop”。
它是一套完整的 Agent 运行时,试图统一模型调用、工具治理、会话持久化、上下文压缩、Subagent、沙箱以及 Web 和 Headless 等产品执行面。
它最值得关注的不是某个内置工具,而是三个底层约束:
- Everything is a Plugin:模型适配器、工具注册表、Session 和 Agent Loop 都是插件;
- Model-visible means logged:模型看见的内容必须能够从事件日志重建;
- Capability Seams:接口、Provider 和 Consumer 分离,执行环境能够整体替换。
我的判断是:它的架构完整性明显高于典型轻量 Agent SDK,尤其适合研究可重放 Agent、插件平台和复杂工具治理。
但固定版本仍是 0.1.0-rc.5,官方明确标注 Developer Preview。现在更适合固定版本做 PoC,不适合直接假定 API 和持久化格式已经稳定。
本文基于官方仓库提交 47f9438,核验日期为 2026-08-14。
Harness 到底解决什么问题
模型只是 Agent 系统中的一个部件。要把它变成可以持续工作的 Agent,宿主还需要完成很多工作:
- 组装 System Prompt、历史消息和工具 Schema;
- 调用模型并处理流式输出;
- 将 Tool Call 送入权限、调度和执行流水线;
- 保存 Turn、Step、Message 和 Tool Result;
- 处理取消、重试、压缩、恢复和分叉;
- 管理 Subagent、后台任务和动态 Workflow;
- 向 Web UI、Headless Runner 或 SDK 暴露一致状态。
DeepSeek Harness 把这组宿主职责放进同一个可组合运行时,而不是让每个产品界面各自实现一套 Agent Loop。
官方提供的最小启动命令是:
npx @deepseek-ai/dsh web
默认 Web UI 地址是 http://127.0.0.1:3080。固定提交要求 Node.js ^22.19.0 || >=24.0.0,仓库使用 pnpm@11.7.0。
来源:
总体架构:产品只是插件树的不同组合
可以把系统理解为以下几层:
Web / Headless / SDK / API
↓
Profile + Bundle + Patch
↓
Cordis Context 与插件生命周期
↓
Agent Loop
↙ ↓ ↘
Session LLM Tools
↓ ↓
Replay Capability Seams
dsh-base 提供模型、工具、持久化、沙箱、审批、设置、凭据和遥测等基础能力。
dsh-web-app 在基础层上增加浏览器应用;dsh-headless 则增加无服务器的一次性 Runner。
因此 Web 和 Headless 不是两个平行实现,而是在同一插件树上叠加的不同 Bundle。
启动时,系统从空插件树依次应用 Profile 指定的 Bundles、Profile Patch、Home Patch 和命令行 --patch。
Patch 通过稳定 id 定位配置行,并替换该行的完整配置。下面的命令可以显示机器最终实际启动的插件树:
dsh --profile web --dump-config
这种结构让部署差异停留在组合层,减少为某个环境 Fork 核心代码的需求。
代价是排查问题时必须同时理解包、插件、配置行、覆盖顺序和作用域。
来源:Architecture:Profiles and bundles。
Cordis:插件为什么能够真正卸载
DeepSeek Harness 底层使用 Cordis。官方论文把动态组合拆成时间和空间两个维度。
时间可组合性要求组件移除时,能够完整撤销它注册的副作用。
空间可组合性要求组件能够声明依赖,并随上下文能力的出现或消失响应式加载。
在 dsh 中,插件向共享 ctx 注册服务、事件监听器和 Effect。例如工具服务位于 ctx.tools,模型服务位于 ctx.llm,Agent 注册表位于 ctx.agents。
注册本身就是可撤销 Effect。插件卸载时,它注册的工具、监听器和服务会自动反向清理。
插件还可以用 inject 声明依赖。依赖不存在时保持 Pending;依赖出现后加载;依赖消失时卸载;依赖恢复后重新加载。
这使 HMR 不只是替换模块代码,而是带生命周期一致性的卸载与重装。
Cordis 还提供广播、并行、串行、Bail 和 Waterfall 等事件语义。dsh 使用 Waterfall 实现模型请求和工具执行的 Around Middleware。
Waterfall Listener 必须调用 next() 才会继续下游。漏调可能吞掉后续流程,这是灵活性带来的调试成本。
Agent Loop:Turn 和 Step 不是一回事
dsh 对执行边界做了明确区分:
- Step:一次模型请求,以及该请求产生的工具调用;
- Turn:零个或多个 Step,从领取本轮输入开始,到没有待处理事项为止。
一次典型执行路径如下:
turn/start
→ 领取 Inbox 输入
→ agent/pre-step
→ step/start
→ 写入 user/message
→ 组装 System Prompt 与 Tool Schema
→ agent/request → llm/stream
→ assistant/chunk* → assistant/message
→ tool/call*
→ tools/pre-execute → execute → post-execute
→ tool/result*
→ step/end
→ 如仍有工具结果或新输入,进入下一 Step
→ agent/turn-stopping
turn/end
一个用户请求经常需要“模型判断、调用工具、读取结果、再次调用模型”。它属于一个 Turn 中的多个 Step。
这种边界让重试、Token、延迟和错误能够归因到单次模型请求,也让取消和恢复知道自己停在什么阶段。
Inbox:Followup、Steer 和 Inject
Agent 只有一个 FIFO Inbox,但不同输入具有不同投递语义。
followup 是普通后续消息,会创建或唤醒一个新 Turn。
steer 尽量进入最近的 Step,并立即唤醒 Agent。
inject 为下一个 pre-step 注入上下文,但自身不会唤醒 Agent。
这比直接向 messages 数组追加内容更严格。是否唤醒、进入当前 Step 还是下一 Turn,以及是否成为模型可见输入,都被显式编码。
默认 Loop 由 Agent Factory 和 Registry 发布,Loop 自身也是插件。新的执行策略可以替换 Driver,而无需重写 Session、Tools、LLM Adapter 和 UI 协议。
Agent 发布前还会在 Scoped Context 中完成 Setup Transaction。任一环节失败就回滚,避免出现“注册表里能发现,但只初始化了一半”的 Agent。
来源:
Session:Append-only Log 是唯一事实源
Session 是类型化 SessionEvent 的追加日志。LLM 消息历史不会单独保存为一份可变数组,而是通过 deriveMessages() 从日志投影。
核心事件包括:
turn/start、turn/end;step/start、step/end;user/message;assistant/chunk、assistant/message;tool/call、tool/result;request/header、request/context。
原始 assistant/chunk 也被保留,所以 UI 可以重放流式过程。组装后的 assistant/message 则用于推导模型历史。
Model-visible means logged
这是整个项目最值得借鉴的约束:任何进入模型请求的输入,都必须能从日志重建。
完整 Request Header 会记录 Provider、Model、Reasoning Effort、Adapter 默认值、渲染后的 System Prompt 以及实际 Tool Schema。
一次历史请求不只回答“用户当时说了什么”,还可以回答“模型当时看见了哪些指令、工具和配置”。
这对审计、回放、错误复现和模型切换非常关键。
来源:Session 子系统。
Surface:压缩上下文但不删除事实
Session Log 不可变,但“当前模型可见表面”可以投影。
正常消息通过 Append 加入;压缩摘要可以 Replace 一段旧 Surface。旧事件仍保留在 Canonical Log,只是不再进入当前模型历史。
如果直接删除旧消息,就会失去审计和回放能力。如果只在尾部追加摘要,旧消息和摘要又会重复占用上下文。
dsh 的选择是:原始事实不删,模型视图可替换。
Token Meter 会从 Durable Log 折叠当前 Surface。它优先使用 Provider Usage,没有 Usage 时使用约四个字符一个 Token 的启发式估算。
上下文压力出现后,可以先进行无需模型的 Tool Result Pruning。若仍超阈值,再让模型生成摘要并替换旧 Surface 区间。
它的限制也很明确:字符估算对中文、代码和 JSON 可能偏差较大,压缩也无法缩小 System Prompt 与 Tool Schema 这部分固定 Envelope。
来源:
持久化:失败时宁愿报错,也不静默误读
持久化是独立 Capability Seam,官方实现包括 JSONL 和 SQLite。
会话事件先同步进入内存日志,再由批处理控制器写入后端。session/flush 充当顺序检查点。
异常退出后,如果日志停在未闭合 Turn,恢复逻辑会补写原因是 Interrupted 的 turn/end,而不是截断历史。
持久化格式还区分 Required 和 Ignorable 事件。遇到无法理解的必要事件时,读取器会拒绝静默恢复。
这个选择偏保守,但 Agent 恢复最危险的失败不是“明确报错”,而是“看似恢复成功,实际漏掉一条改变后续语义的事件”。
来源:Persistence 子系统。
Capability Seams:替换的是执行世界
dsh 把一项能力拆成三个角色:
- Service Definition 定义接口与语义;
- Service Provider 实现接口;
- Consumer 使用能力,常见形式是模型工具。
它不只是普通依赖注入。Provider 的边界围绕“共同执行世界”设计。
例如切换远程沙箱时,不应只替换 Bash。Filesystem、Subprocess、PTY 和 LSP 也应该同时指向相同远端环境。
否则模型会遇到文件位于 A 环境、命令却在 B 环境执行的状态错乱。
来源:Capability Seams。
Agent Scope:每个 Agent 可以拥有不同能力
工具和服务不必只有进程全局层。dsh 可以用 Agent 对象作为不透明 ScopeKey,组合全局、父级和精确 Agent 作用域。
Scoped Registration 可以遮蔽同名全局注册。
这样可以给某个子 Agent 单独配置 Persona、工具集和限制,而不修改进程全局注册表。
Agent 销毁时,属于其 Context 的注册行为也会一并撤销。
来源:Scope 子系统。
工具系统:Tool Call 是受治理的事务
每个工具包含模型可见 Schema、强制的 Canonical Output Schema、execute,以及宿主专用的超时、并发和展示元数据。
注册表通过白名单投影模型 Wire Schema,因此 execute、Timeout 和 UI Presenter 不会泄露给模型。
统一 Schema DSL 同时服务 TypeScript 推导、JSON Schema 和运行时校验。成功输出也必须通过 Output Schema。
很多轻量框架只验证模型输入,不验证工具实际输出。dsh 则阻止不可序列化对象或不稳定结构直接进入会话历史。
工具执行流水线
tools/pre-execute
↓
monotonic guards
↓
tools/execute
↓
tools/post-execute
↓
finalizeContent
↓
tools/result
pre-execute 可以组织 Allow、Deny 和 Ask 策略。
Monotonic Guard 只能不表态或 Deny,没有 Allow 返回值。后注册的监听器无法把已经拒绝的操作重新放行。
工具参数只做一次 Lossless JSON 物化,随后冻结。策略、日志、UI 和实际执行看到同一份参数。
这避免中间件偷偷改参,导致审计记录与真实行为不一致。
来源:Tools 子系统。
并发采用安全默认值
工具默认是 Exclusive。只有 isConcurrencySafe(args) 明确返回 true,调用才进入 Parallel 组。
缺省、异常或非 true 结果全部按 Exclusive 处理。Agent Loop 使用 Rolling Pool 执行并发安全调用,让 Exclusive 调用形成顺序屏障。
并发不是因为模型一次生成多个 Tool Call 就自动发生,而需要工具作者对共享状态安全性作出明确承诺。
工具可见性和执行权限同源
Scope Restriction 可以用 Allow/Deny 过滤继承工具,并将同一个结果同时用于 Prompt Schema 与执行检查。
这避免模型“看不见但能调用”,或“看得见但执行层忘了允许”的双权限表漂移。
Code Mode:减少模型往返,但不是安全沙箱
Code Mode 向模型提供 run_code,让模型写 JavaScript 或 TypeScript,通过 Async Binding 调用工具。
它适合循环、过滤、聚合和批量调用,也能把大量中间结果留在执行态,只将最终压缩结果放回模型上下文。
嵌套调用携带 Parent Token 和 Root Call ID,因此仍然经过工具权限、取消、日志与关联机制。
Worker Thread 后端提供空环境变量、计算和墙钟预算、输出上限以及 Worker 强制终止。
但官方明确说明:Node Worker 不是安全边界。
代码可能利用 Node 能力逃出预期限制。Worker 被终止后,它创建的操作系统子进程也不一定自动退出。
Code Mode 应理解为受资源约束的编排运行时,而不是执行不可信代码的隔离沙箱。
来源:
DeepSeek Adapter:重试不会被藏在 SDK 里
官方 DeepSeek Adapter 直接使用 fetch 和 SSE Parser,注册的 Provider Route 是 deepseek-official。
模型、Base URL 和 API Key 在每次请求前解析。配置变更后下一次请求就会生效,已经开始的流式请求继续使用自己的快照。
Adapter 只使用流式响应,并提供 Idle Watchdog 和稳定 AbortSignal。
错误会被归一化为认证、配额、限流、上下文超限、非法请求、服务端、传输、取消、超时、流关闭、畸形响应和空响应等类别。
一次 Adapter Call 就是一次 Provider Attempt。重试由独立插件在 Durable Step 边界重新发起,而不是在 Adapter 内部静默重试。
因此失败尝试也会进入事件日志,事故排查时可以看见真实过程。
带 Tool Call 的 Assistant Turn 会在后续请求中回传 reasoning_content。不带 Tool Call 的旧推理内容可以省略,以减少 Token 消耗。
固定版本仍有若干限制:tool_choice 尚未映射,直接 Fetch 路径没有共享代理支持,插件新增的富内容 Block 也可能被跳过。
来源:
Subagent:持久子会话,而不是临时 Promise
Subagent 是 Provider-neutral Seam。官方实现和桥接方向包括进程内 Spawn/Fork、ACP、Codex、Claude Code 和 dsh SDK。
一个 Child 拥有持久 Session,但同一时间最多只有一个 Live Activation。
子会话可以停止运行,后续 Followup 再冷恢复。这比将 Subagent 建模为一次 Promise 更适合长生命周期任务。
父子和祖先权限依据持久化 Lineage,而不是当前消息来自谁。
Delegation Depth 也写入元数据,因此 Resume 或 Fork 不能把深度限制重置为零。
Interrupt 会取消当前 Activation,但不等于删除子会话、未处理 Inbox 或后代。
来源:Subagent 子系统。
动态 Workflow:强大,但还不是 Durable Engine
Workflow 允许模型生成 JavaScript 编排脚本,通过 agent、parallel、pipeline、phase 和 log 等 Hook 扇出子 Agent。
脚本在 Worker Thread 中执行,实际子 Agent 仍由 Host 创建。
它适合动态数据并行、分阶段研究,以及根据上一阶段结果决定下一阶段 Fan-out。
运行时会限制最大并发、总 Agent 数和 Item 数,也处理取消与有界释放。
固定版本的边界包括:
- 不支持 Workflow Journaling 和 Resume;
- 不支持保存或嵌套 Workflow;
- 没有 Token Budget;
- 主要以前台方式执行;
- Worker Thread 仍然不是安全沙箱。
所以它更像实验性的动态 Orchestrator,还不是可靠的 Durable Workflow Engine。
来源:
沙箱:限制文件访问,不等于完整隔离
Sandbox Service 定义 Read-only、Workspace Write 和 Full Access 三档文件效果模式。
confine(argv, policy) 返回需要执行的包装命令。平台沙箱不可用时应该 Fail Closed,而不是静默改为无隔离执行。
本地实现按平台选择:
- Linux 使用 Bubblewrap,必要时使用 Landlock;
- macOS 使用 Seatbelt 或
sandbox-exec; - Windows 使用 ACL 和 Restricted Token。
这些机制主要治理文件访问,并不等同于容器或 MicroVM。网络、完整 Syscall 和进程树隔离不在同一保证范围。
后端还会报告 Full 或 Partial Enforcement。Windows 和旧 Landlock 环境可能只能提供部分保证。
来源:
Read-before-edit:用 CAS 防止覆盖别人的修改
文件系统观察策略要求 Agent 修改前先读取目标文件,并记录观察到的文件版本。
写入时执行“文件不存在才创建”或“版本相同才替换”的 Compare-and-swap。
如果 Agent A 读取文件后,Agent B 或用户已经修改,A 的旧版本写入会变成可见冲突,而不是静默覆盖新内容。
这是 Agent 并发修改真实代码仓库时非常重要的安全属性。
八个最值得借鉴的设计
1. 可逆插件生命周期
很多插件系统只规范如何注册,不保证如何完全撤销。Cordis 将注册建模为可撤销 Effect,减少 HMR 和 Scope 销毁后的残留状态。
2. 模型上下文可重建
系统不仅保存 Message,还保存 Request Header、Tool Schema、Adapter Defaults 和 Raw Chunks。可观测性被提升为运行时不变量。
3. 策略只能单调收紧
Tool Guard 只能 Deny,不能放宽此前拒绝。权限结果不会因为 Listener 顺序变化而被重新打开。
4. 可见性与执行权限同源
同一 Scoped Registry 同时决定模型能看见什么工具,以及执行时允许什么工具。
5. 失败尝试也是事实
重试发生在持久 Step 边界。失败、重试和恢复都会进入事件流,而不是被 Adapter 隐藏。
6. 原始日志与模型视图分离
Compaction 不破坏原始证据,只替换模型 Surface,在审计需求和上下文预算之间建立结构化边界。
7. Provider Swap 以执行世界为边界
Filesystem、Subprocess、Shell 和 LSP 围绕同一能力世界组合,降低远程沙箱扩展时的分支爆炸。
8. 质量门禁覆盖面广
仓库脚本包含类型检查、Lint、单测、E2E、Web Snapshot、性能和压力测试、包发布校验、Node 兼容、文档链接及生成目录一致性检查。
这只能证明项目设置了这些门禁,不代表本文独立验证了官方仓库在所有平台上的全部 CI。
现实风险
API 和配置尚未稳定
官方明确标注 Developer Preview。版本仍是 0.1.0-rc.5,短期升级可能影响插件接口、配置和持久化格式。
学习曲线很陡
开发者需要同时理解 Cordis、Effect、插件树、Profile、Bundle、Patch、Scope、多类事件、Session Surface 与 Capability Seam。
对于只有固定模型和少量函数调用的应用,这套体系明显过重。
模块粒度很细
固定提交的 packages/ 下可以找到 226 个 package.json。
细粒度包有利于替换与测试,也增加依赖图、发布、导航和跨包重构成本。这个数字不代表最终用户需要手动安装 226 个包。
插件化增加组合期问题
强解耦减少硬依赖,但服务缺失、Scope 错误、Waterfall 漏 next() 和 Patch 覆盖错误,可能只在特定运行组合中出现。
Worker 不是不可信代码隔离
Code Mode 和 Workflow 都使用 Worker Thread,但官方明确说明它们不是安全边界。
Durable Session 不等于 Durable Workflow
Session 可以持久化、恢复和 Fork,不代表任意动态 Workflow 都能从中断位置继续。固定版本的 Workflow 没有 Journaling 和 Resume。
适合什么项目
DeepSeek Harness 更适合:
- 可重放、可审计、可恢复的长生命周期 Agent;
- Web、Headless 和 SDK 需要共享同一运行时的平台;
- 不同 Agent 需要不同工具、Persona、权限和执行环境;
- 本地与远程 Provider 需要切换;
- 多模型 Adapter 和多 Subagent Provider 需要共存;
- 愿意围绕插件体系开发内部能力的团队。
它暂时不适合:
- 只有固定模型和少量函数调用的轻应用;
- 强依赖长期 API 兼容保证的核心系统;
- 直接执行任意不可信代码的多租户平台;
- 要求动态 Workflow 精确断点续跑的业务;
- 无力维护复杂 TypeScript Monorepo 和事件溯源状态的团队。
建议的采用路径
如果准备评估,建议按以下顺序推进:
- 固定 Commit 或精确 RC 版本,不跟随
latest漂移; - 用 Web Profile 跑通单 Agent 与工具调用;
- 保存
--dump-config输出,建立升级 Diff; - 验证 Session Fork、Resume、取消、拒绝和异常恢复;
- 只替换一个 Seam,例如自定义 Tool 或 Persistence;
- 最后再引入 Subagent、Workflow 和 Code Mode。
生产评估至少要覆盖:
- Stream、Tool 和 Flush 阶段崩溃后的恢复;
- Deny、Ask、Timeout 和 Cancel 在嵌套调用中的一致性;
- 目标操作系统的 Sandbox 是 Full 还是 Partial;
- HMR 和 Patch 是否影响正在运行的 Agent;
- 中文、代码和大型 JSON 压缩后的信息保真度;
- 企业代理、限流、断流和上下文超限行为;
- 升级对自定义插件和持久化格式的迁移成本。
最终判断
DeepSeek Harness 的真正亮点不是“支持 DeepSeek”,而是将充满隐式状态的 Agent 循环拆成一组可验证结构:
插件树决定能力
事件日志决定事实
Surface 决定模型当前所见
Scope 决定能力归属
Policy 决定能否执行
Provider 决定在哪里执行
Adapter 决定如何与模型通信
最值得借鉴的原则是:模型可见即持久化、重试不隐藏、权限只能单调收紧、压缩不破坏原始事实、插件卸载必须撤销副作用。
这些约束直接针对 Agent 工程中最难复现的状态漂移、权限漂移和审计问题。
但它仍处于 Developer Preview。动态 Workflow 还不 Durable,Worker 不是安全沙箱,Adapter 与跨平台 Sandbox 也存在明确边界。
现阶段最合理的做法是固定版本做深入 PoC、吸收架构原则、谨慎评估生产接入,而不是把 RC 项目当作已经稳定的通用标准。
来源与版本
- 官方仓库:deepseek-ai/deepseek-harness
- 核验提交:
47f9438 - 核验版本:
0.1.0-rc.5 - 核验日期:2026-08-14
- 项目状态:Developer Preview
- 许可证:MIT
- 架构文档:Architecture
本文只使用 DeepSeek 与 Cordis 官方仓库作为事实来源。源码静态审计和文档链接已核验,但没有在本机执行官方 Monorepo 的完整跨平台 CI。