AI 深度技能之-解读DeepSeek Harness(一)- 初见

DeepSeek Harness 架构分析

仓库:deepseek-ai/deepseek-harness

协议:MIT 发布日期:2026-08-13(上线仅 1 天,developer preview 阶段)

技术栈:Node.js + TypeScript + pnpm workspace + Cordis 框架

代码量:1247 个 TS 源文件 + 684 个测试文件,packages/ 下按领域分目录

一、它到底是个什么东西

DeepSeek Harness(简称 dsh)是 DeepSeek 官方开源的 Agent 框架。dsh 不做产品,只做框架。它的核心口号是 Everything is a Plugin,包括模型适配器、工具注册表、会话日志、Agent 循环本身,全是可替换的插件。

一句话定位:如果你想自己造一个 Cursor / Claude Code / Codex 这样的 Agent 产品,dsh 给你造好了底盘。

二、它解决的痛点

痛点 1:Agent 框架的"核心不可改"

大多数 Agent 框架(LangChain、AutoGen、CrewAI)都有一个 privileged core。你想改 Agent 循环的行为,要么 fork 整个框架,要么继承一个 base class 然后祈祷上游别改。dsh 的做法是:没有核心 。Agent 循环本身就是一个插件(core/agent-loop),你挂一个自己的 Agent 循环插件就能替换它,其他插件不动。

这个设计来自 Cordis 框架。Cordis 源自 Koishi(一个聊天机器人框架)的生态,设计哲学是"插件贡献服务、类型化事件、可逆效应到共享上下文"。dsh 把这套搬到了 Agent 领域。

痛点 2:工具策略作为后补

很多框架的工具执行就是"调一个函数"。dsh 的工具执行管道有三层 waterfall 事件 + 一层 monotonic guard:

  1. tools/pre-execute waterfall:hooks、permission、sandbox 拦截
  2. Monotonic guards:deny 或 abstain,identity protected(不可被其他插件覆盖)
  3. tools/execute waterfall:timeout、retry、metrics 包裹 dispatch
  4. tools/post-execute waterfall:accept、block、replace、add context

这还没完。工具执行完后还有 finalizeContent(同步 content-only invariant)和 tools/result(frozen authoritative outcome)。一个工具调用从头到尾经过 6 道关。

痛点 3:会话状态不一致

Agent 跑久了,内存状态、日志、UI 显示、持久化副本会漂移。dsh 的解法是:Session Log 是唯一真相源。所有模型可见的东西都必须从日志可重建,运行时 invariant 会断言这一点。Fork、resume、transcripts、telemetry、persistence 全从这条流派生。

这条铁律的代价是:你想给模型塞一个新的可见输入,必须先发明一个 session event。不能直接拼字符串。

痛点 4:Subagent 实现五花八门

你想让 Agent 调 Agent,可能是在同进程里跑一个子 Agent,可能是 fork 当前会话,可能是调 Codex CLI,可能是调 Claude Code,可能是走 ACP 协议。dsh 把这些全抽象成 subagent provider 接口,一个接口五种实现:

  • subagent-in-process-driver:同进程子 Agent
  • subagent-spawn-in-process:进程内 spawn
  • subagent-fork-in-process:fork 当前会话
  • subagent-codex:委托给 OpenAI Codex
  • subagent-claude-code:委托给 Claude Code
  • subagent-acp:走 Agent Communication Protocol

加上 subagent-dsh-sdk(子 Agent 自己跑一个完整 dsh 实例),共 7 种 subagent 模式。每种都有完整的 continuation、inheritance、settlement、depth control 测试。

痛点 5:沙箱不可移植

Linux 有 Landlock、Mac 有 Seatbelt、Windows 有 ACL、云上有 E2B。dsh 把沙箱也抽象成 ctx.sandbox 服务,一个 provider 切换就移动了 Bash、PTY、LSP 的执行世界,不用 fork 任何工具。原生 Landlock 实现甚至写了一个 C 扩展(native/landlock-run/packages/linux-x64/),不是简单调 syscall。

三、架构总览

Cordis 框架的五个核心概念

要看懂 dsh,先得看懂 Cordis。五个概念:

  1. Plugin = Service 实现 :一个插件可以是带 injectapply(ctx) 的函数,也可以是 Service 子类。Cordis 把它的生命周期挂载到当前 context。
  2. Context = Service 仓库 :服务从 context 里用稳定 key 认领(ctx.toolsctx.llmctx.sessions),其他插件通过 key 找服务而不是 import 具体实现。
  3. inject 声明依赖:插件说自己需要哪些服务,Cordis 等这些服务都到位了才挂载它。启动顺序靠依赖声明而不是手动排序。
  4. 类型化事件 :服务通过 TypeScript declaration merging 声明事件名,然后按 emit / waterfall / parallel / serial 四种模式分发。分发模式是事件公共契约的一部分。
  5. 注册是可逆效应 :prompt section、tool schema、adapter、provider、listener 全通过 ctx.effect()ctx.on() 安装,reload 和 teardown 时按注册逆序自动撤销。

Profile 和 Bundle 的分层配置

一个运行中的 dsh 是启动时从有序层级组合出来的插件树。

  • Profile :存在 Harness home 里的命名组合,列出它堆叠的 bundle,存放 out-of-tree 插件,保存用户的 cordis.patch.ymlwebheadless 是官方模板。
  • Bundle :Cordis config row 和它们挂载的代码的分发格式。每个 bundle 在自己的 package.json 里用 dsh 字段声明:dsh.profile 列出 profile 的 bundle,dsh.bundle 指向 bundle 的 patch 文件。

层级应用顺序:profile 列出的 bundle 顺序 → profile 的 cordis.patch.yml → home 级 patch → --patch overlay。Patch 通过 id 定位 row,替换整条 config 或插入新 row。

dsh --profile web --dump-config 能打印你机器实际启动的插件树。任何 row 都可以用你自己的 patch 替换。

Turn Flow(回合流)

这是 dsh 的心脏。一个 step 是一次模型请求加它调用的工具。一个 turn 是零或多个 step:它在第一个 input 被 claim 之前打开,在什么都不欠的时候关闭。

bash 复制代码
turn/start
  claim next-step input + 一条排队消息
  组装 prompt section + tool schema
  -> agent/pre-step                   reject | enter(messages)
     reject,或第一次 enter 被重写为空 -> 关闭 turn 不产生 step
     step/start
     把 entered messages 追加为 user/message
     从日志派生模型历史
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
     工具还欠一次请求,或 next-step input 到了 -> claim -> 下一个 step
  -> agent/turn-stopping
turn/end

turn/*step/*user/messageassistant/*tool/* 是持久化 session event;其余是跨三个域的 live extension point。agent/pre-stepagent/requestllm/stream、三个 tools/* 事件是 waterfall(listener 必须 call next() 委托);agent/turn-stopping 是 serial 且没有 next()

六个核心包

职责 ctx key
core/session append-only 的 SessionEvent 日志和内存存储 ctx.sessions
core/system-prompt prompt section 和 tool schema 组装 ctx.systemPrompt
core/tools 有 scope 的工具注册表和受保护的执行管道 ctx.tools
core/agent Agent 接口、live registry、agent/* 事件 ctx.agents
core/agent-loop 实现该接口的默认 driver ctx.agentLoop
core/scope 每 agent 的 scoped-registration 原语 library,无 key
llm/llm message 和 stream 词汇 + adapter seam ctx.llm

Capability Seam(能力接缝)

一个 seam 是一个可替换的能力,三个角色:Service Definition 声明接口、Service Provider 实现、Consumer 使用(通常是 model-facing tool)。一个包可能身兼多职,但只一个角色不算 seam。

Seam 是为什么"一个 provider 切换就改变整个产品"。filesystem 和 subprocess provider 共享一个执行世界,所以指向远程沙箱就把 Bash、PTY、LSP 全搬过去,没有 provider fork。Subagent provider 也是同样道理,背后从 fresh child agent 到委托另一个产品,差异全藏在接口后面。

四、几个有意思的设计

1. 事件分发模式写进公共契约

Cordis 的四种分发模式(emit / waterfall / parallel / serial)不是实现细节,是事件的公共契约。新事件必须用 @mode tag 声明分发模式,生成的 catalog 会检查声明和调用点是否一致。

这比"看着文档写 listener"靠谱。声明 waterfall 的事件,listener 就知道必须 call next();声明 serial 的就知道有返回值且按顺序。类型系统替你检查。

2. Monotonic Guard 不受插件顺序影响

工具执行的 monotonic guard 是一个特别的设计。普通 waterfall listener 的执行顺序受注册顺序影响,但 monotonic guard 的"deny"决策不可被后续 listener 覆盖。Identity protected。

这意味着安全策略可以独立于业务逻辑插入,不用担心某个后注册的插件把你的 deny 给 next() 掉了。这比 Odysseus 的 tool_security.py 靠"先检查后执行"的注释约束强得多。

3. Session Log 的"model-visible means logged"铁律

dsh 在 core/session 里有一个 runtime invariant:任何到达模型请求的东西必须能从日志重建。如果你给模型塞了一个新的可见输入但没发明对应的 session event,invariant 会断言失败。

这逼着开发者把所有模型可见的状态变更都做成显式的、可重放的、可序列化的事件。代价是写新功能时要先扩展 SessionEventMap。收益是 Fork、resume、transcripts、telemetry、persistence 全自动从同一条流派生,不可能漂移。

4. 7 种 Subagent Provider 背后的一致接口

subagent 的差异有多大?同进程跑一个子 Agent 和调 Codex CLI 是完全不同的事。但 dsh 把它们统一到 ctx.agents 接口后面:

  • 父 Agent 通过 tool-subagent 工具发起委托
  • subagent provider 决定委托怎么落地
  • subagent-in-process-driver 在同进程挂载子 Agent
  • subagent-codex 通过 Codex CLI 协议委托
  • subagent-claude-code 通过 Claude Code 协议委托
  • subagent-acp 走 ACP 标准协议
  • subagent-dsh-sdk 让子 Agent 自己跑一个完整 dsh

每种 provider 都有 continuation(继续跑)、inheritance(继承父 Agent 的能力集)、settlement(结算结果回传)、depth control(限制嵌套深度)的测试。subagent-multisubagent-parallelsubagent-mixed 三个测试 snapshot 证明这些 provider 可以混用。

5. 防御性编程模式文档化

docs/defensive-patterns.md 是一份"硬仗来的 bug 类规则"文档。每条规则都是一个真实发生过或差点发生的 defect class,写成防止复现的规则。举几条:

"报告正交结果要独立" :一个进程可能既 timeout 又 exit 0(因为它 trap 了信号)。每个独立事实(timedOutsignalexitCode)要各自上报,不能把一个 flag 嵌进另一个的分支。否则调用方读到截断的 run 当成干净成功。

"异步状态不是同步状态"agent.followup() 没有每消息完成或结果;background job 的完成和 turn boundary 竞态;reader.close() 既可能是 EOF 也可能是 disposal。不要把 agent/statuswhenIdle() 当成一次 follow-up 的结果。如果自动化调用方真的拥有一个 run,必须显式定义自己的 interval(从它的消息 durable inbox receipt 到下一个 whole-agent idle)。

"Dispose 必须到达静默,不是仅请求" :teardown 发了 kill/abort 但在工作停之前返回会留孤儿。cleanup 必须 async 并 await 子进程的 exit(kill → await done),并且在 kill 之前关闭 listener/notification registry,这样迟到的 completion 会保持沉默。

"不要给 untrusted output 递环境变量或可预测路径" :spawned command 拿到的是被擦过的 env(drop *KEY*/*SECRET*/*TOKEN*/*PASSWORD*),harness 凭证不会泄漏到 output、env 或 spill 文件里。Temp/spill 文件用 0700 私有目录、随机名、exclusive owner-only open('wx'0o600),因为可预测的 world-readable 路径会招来 symlink race 和披露。

"Unlink 链接型路径" :可能是 symlink 或 Windows junction 的路径,用 lstatSync().isSymbolicLink() 然后用 unlinkSync 删除。unlink 只删 link 拒绝真实目录,不会跟随 link 进入目标。Windows rmSync(link) 在 junction 上抛 ERR_FS_EISDIR;递归删除可能穿过 junction 进入目标。

这些不是理论规则,是从真实 bug 提炼的。docs/postmortem/ 有 4 份 postmortem 记录具体的失败案例。

6. Landlock 原生 C 扩展

Linux Landlock 是内核级文件访问控制。大多数项目要么不用,要么用纯 JS 的 binding。dsh 写了一个 C 扩展 native/landlock-run/packages/linux-x64/,还提供 arm64 prebuild。这意味着沙箱不是"最佳 effort",是内核强制。

对应 macOS 有 Seatbelt(bash-sandbox/tests/seatbelt.e2e.ts),Windows 有 ACL(pwsh-sandbox/tests/acl.e2e.ts),云上有 E2B(packages/e2b/)。四个平台四种沙箱后端,全走 ctx.sandbox 接口。

7. Typert:类型安全的插件协议生成器

packages/typert/generator/ 是一个 codegen 工具。它分析 Cordis 服务的 TypeScript 类型,生成跨进程的类型定义。这意味着你写一个 Cordis 插件,它的服务接口可以自动暴露给客户端(browser、子进程、远程)使用,类型安全。

packages/typert/protocol/ 是协议定义,packages/typert/registry/ 是服务注册,packages/typert/loader/ 是加载器。这一套让 dsh 的 Web UI 可以类型安全地调用 host 侧的服务。

五、短板和风险

1. Cordis 学习曲线陡峭

Cordis 不是主流框架。它的源头是 Koishi(一个中国 QQ 机器人生态),文档主要在中文社区。dsh 虽然有英文文档和 primer,但开发者要先理解 Service、Context、inject、四种 dispatch mode、reversible effects、waterfall semantics 这些概念,才能写第一个插件。

这比 LangChain 的"import一个chain"门槛高得多。dsh 自己也意识到这个问题,提供了 7 篇 cordis-tutorial 从零教起。

2. developer preview 阶段,不稳定

README 加粗写着:"THERE WILL BE COMPATIBILITY-BREAKING CHANGES." 上线仅 1 天,API 还在剧烈迭代。现在基于 dsh 做产品,要做好跟着改的准备。

3. 代码量巨大但分散

1247 个 TS 源文件分散在 packages/ 下的几十个目录里。一个完整的 Agent 能力(比如"subagent")横跨 7 个 package,要理解全貌得读多个目录。docs/subsystems/ 有 50+ 个文档页,但初学者定位"我要的功能在哪个包"仍需时间。

4. 文档密集但门槛高

docs/ 下有 architecture、cordis-primer、cordis-tutorial(7 篇)、cordis-api(6 篇)、subsystems(50+ 篇)、cookbook(6 篇)、postmortem(4 篇)、defensive-patterns、testing、glossary。文档质量很高,但量太大,对新开发者不友好。

5. 没有内置模型

dsh 不自带 DeepSeek 模型。你要自己接 model adapter(通过 ctx.llm)。虽然 subagent-codexsubagent-claude-code 可以委托给 Codex/Claude Code,但如果你要用 DeepSeek 自己的模型,得自己写 adapter 或等社区贡献。

6. 前端生态复杂

Web UI 在 packages/client/ 下,用 React + Vite。有 ui-primitives(30+ 组件)、ui-cordis(Cordis 运行时可视化)、ui-settings-pluginsui-sidebarui-skillui-subagentui-attachment 等十几个 UI 包。定制 UI 需要理解 Cordis 的 client-runner 架构,不是简单的 React 组件复用。

六、一句话评价

DeepSeek Harness 是近两年 Agent 框架领域架构设计最讲究的一个。它不堆功能,堆抽象。Cordis 的 Service + Event + Seam 三件套,配合 Session Log 铁律、Monotonic Guard、Subagent Provider 接口,把"造一个 Agent 产品"这件事的工程门槛拉到了新高度。

代价是学习曲线陡峭,developer preview 不稳定,文档量大但门槛高。但对于想认真造 Agent 产品的团队,这是目前最值得研究的框架。

MIT 协议是它最大的优势。

相关推荐
IT_陈寒1 天前
JavaScript数组排序踩的坑,差点让我加班到凌晨
前端·人工智能·后端
敢敢是只喵i1 天前
一个本地 AI Agent 要操作多个门店或 SaaS 账号,应该怎样安全切换身份?
人工智能·安全·ai·系统架构·业界资讯
wangchunyu1141 天前
Aider介绍和安装说明
人工智能
SpaceAIGlobal1 天前
AI做PPT 工具哪个好:先搞懂原理,再按 3 个维度挑对那一个(2026)
人工智能·powerpoint
β添砖java1 天前
深度学习28RNN循环神经网络
人工智能·深度学习
2301_818474441 天前
福建中小微企业云 ERP 落地实践:轻量化信息化项目实施指南
大数据·人工智能
咕泡科技1 天前
咕泡科技×创业酵母俞头私享会:AI时代,组织如何长出“破局力”?
大数据·人工智能·科技
啊阿狸不会拉杆1 天前
《自然语言处理:基于大语言模型的方法》第1章 绪论 读书笔记
人工智能·自然语言处理·nlp·easyui·智能体
我是大AI1 天前
实战解析:基于多源交叉验证的AI幻觉治理架构与GEO行业解决方
人工智能·架构
LTD营销SaaS1 天前
22站点智能成功申请“AI 创建业务型网站”发明专利
人工智能·ai建站·站点智能·22集团·ai创建业务型网站