夜雨聆风学习资料网

ARTICLE · 987274

DeepSeek-Harness 源码深度解析

DeepSeek-Harness 源码深度解析

DeepSeek-Harness 源码深度解析

基于官方仓库 deepseek-ai/deepseek-harness 的 dsh-v0.1.2-alpha.2(commit 0a53fb55,2026-08-30)源码阅读。项目处于 Developer Preview,文中接口和目录随版本演进可能发生破坏性变化。

1. 先给结论:它不是“一个 Agent”,而是可拼装的 Agent 运行时

DeepSeek Harness(命令名 dsh)把通常被写死在 Agent 框架中的部分——模型适配、提示词、工具、会话、沙箱、UI、调度和子 Agent——全部变成 Cordis 插件。它的核心不是一个巨大的 runAgent(),而是:以插件树组装运行时,以事件日志保存事实,以服务接口隔离可替换能力。

这带来三个直接结果:

  • 同一 CLI 可由 profile 组成 Web、一次性 headless、SDK 和 ACP 服务;
  • 换一个 LLM、文件系统或沙箱提供方,消费这些能力的工具无需修改;
  • 会话日志是事实来源,恢复、fork、回放、UI 和遥测从同一事件流派生。

下面的图概括了这套分层:

源码规模也印证了它的工程定位:packages/ 下有 258 个 npm 包清单,全仓约 3054 个 TypeScript 源文件。这不是一个单体聊天程序,而是一套有明确扩展边界的 Agent 平台。

2. 代码库导航:先找“所有权”,再找实现

顶层目录的职责非常清晰:

deepseek-harness/ ├── apps/cli/       dsh 的唯一受支持 Node 应用入口 ├── packages/       产品插件(258 个 workspace 包) │   ├── core/       session、prompt、tools、agent、agent-loop │   ├── bundle/     base/web/headless/sdk/acp 的组装层 │   ├── llm/        模型抽象与 DeepSeek、pi-ai 等适配器 │   ├── fs/ shell/  文件、命令、终端、子进程能力 │   ├── session/    持久化、投影、查询、标题、遥测 │   ├── host/       Web 服务端 / BFF │   └── client/     浏览器端状态、槽位和 UI 插件 ├── vendor/         随仓固定的 Cordis 等上游源码 ├── python/         Python SDK 与随附运行时 ├── native/         Landlock 相关 Node addon 源码 ├── docs/           架构、事件、流水线与生成目录 └── scripts/        构建、质量门禁与文档/图谱生成器

官方把每个能力放在 packages/<group>/<package>/,包名统一为 @deepseek-ai/dsh-*。阅读某一功能时,最有效的路线是:先看该 group 的 README 找到 Service Definition(接口)、Provider(实现)和 Consumer(通常是模型可调用工具),再进入 src/。这避免把某个本地实现误认为不可替换的“内核”。

3. Cordis:插件机制为什么能做到“可拆可换”

Cordis 源码位于 vendor/cordis/src。其 Context 是 Proxy 化的依赖容器:插件向 ctx 提供服务、注册类型化事件,并通过 ctx.effect() / ctx.on() 注册可撤销副作用。插件卸载时,注册项随 disposer 撤销。

关键设计点是 service scope。Context.isolate(name) 会创建某项服务的独立解析作用域;因此某个 Agent preset 可以拿到自己的工具或模型组合,而不污染全局运行时。Context.intercept() 则把服务配置叠加到子上下文。源码中的类型声明合并(例如向 Context 增加 sessions、tools)让插件 API 在编译期可见,而运行时依旧由装配决定。

这里有一个重要约束:注册本身必须是 effect,而不是直接修改全局单例。它保障了热加载、销毁和 profile 切换时不会残留“幽灵工具”或事件监听器。

4. Profile 与 Bundle:dsh 如何变成不同产品形态

CLI 入口在 apps/cli/src/bin.ts:解析参数后只走 profile、plugin 管理或 --dump-config 三条路径。真正的装配规则在 packages/boot/app-boot/src/profile.ts。

一个 profile 是 Harness home 中的具名目录,声明有序 bundle 列表和用户的 cordis.patch.yml;bundle 则是一个 npm 包,其 package.json 的 dsh.bundle.patch 指向配置 patch。最终配置遵循“后层覆盖前层”的顺序:

内置模板如下:

Profile
组合
用途
patch 生命周期
webdsh-base
 + dsh-web-app
浏览器 GUI
live
headlessdsh-base
 + dsh-headless
单次任务
startup
sdkdsh-base
 + dsh-sdk-app
JSON-RPC SDK 服务
startup
sdk-minimaldsh-sdk-minimal
极简 SDK 运行时
startup
acpdsh-base
 + dsh-acp-app
自动化 ACP 服务
startup

dsh-base/cordis.patch.yml 是最值得读的“运行时物料清单”:它挂载 LLM、session、持久化、设置与凭据、沙箱与授权、filesystem、shell、tools、agent loop 等基础插件。配置行用稳定 id 定位,之后的 patch 替换该行完整 config 或插入新行。换言之,定制 Harness 的常规方式不是 fork 核心,而是写一个 bundle 或 patch。

5. Agent 主循环:一次用户输入如何走到下一轮

核心循环的入口是 packages/core/agent-loop/src/agent.ts 中的 ReactLoopAgent。它维护 inbox 和 idle / maintenance / running 三种 phase;followup()、steer()、inject() 都进入同一 inbox,只是目标边界和是否立即唤醒不同。

一次 turn 的主干如下:

两层概念必须区分:step 是“一次模型请求及其工具调用”;turn 是从领取输入到没有未完成工作的一段连续工作,可包含多个 step。agent/pre-step、agent/request、llm/stream 和工具流水线事件是 waterfall:监听器必须调用 next() 才会继续向下游委托。这是诸如上下文压缩、请求改写、权限策略和重试能够插入而不改 loop 的原因。

6. Session:事件溯源是整套系统的数据骨架

packages/core/session/src/index.ts 定义追加式 SessionEvent 日志及其内存 store。它不把“当前对话”当作可随意覆写的数组:请求历史由日志投影,持久化也是订阅 session/event 的插件责任。

这就是项目的硬约束“模型可见即已记录”:任何会进入模型请求的内容都必须能从日志重建。因此新增模型上下文不能只改 prompt 拼接,还必须扩展 SessionEventMap 并提供日志渲染/投影。它以一点记录成本换来了可审计性与可重放性。

默认持久化提供方是 dsh-session-persistence-jsonl:每个 session 一份仅追加 JSONL,默认采用带校验和的 Zstandard 帧;加载时能保留完整提交前缀并修复中断 turn。SQLite 是可替换后端。需要强调的是,session 记录的是逻辑事件;压缩、分块和文件布局只是 provider 的物理实现细节。

7. Tools:模型调用不是“直接执行函数”

packages/core/tools/src/index.ts 既定义 ToolDefinition,也提供 scoped registry 与执行管线。工具声明输入 JSON Schema、输出 schema、execute()、展示投影,以及可选超时与并发安全元数据;系统把原始结果规范化为可无损 JSON 表示的结果后才交给模型与 UI。

这个设计避免了权限、超时、审计和 UI 逻辑散落在每一个工具里:

  • pre-execute
     可 allow / deny / ask;没有可回答的 approval 时,ask 变为拒绝;
  • execute
     是 around hook,适合超时、重试和 metrics;
  • post-execute
     可以过滤或补充结果;
  • 最终 tools/result 是不可变观察事件,UI 可按同一原始事件重放卡片。

文件编辑还会经过 fs/* 意图事件;shell、文件、web、subagent、workflow 都是各自能力 seam 的 Consumer。所谓 PTC(programmatic tool calling)模式则把直接可见工具压缩为 run_code,由模型生成程序再经同一工具管线调度子调用,而非绕过安全与日志。

8. 能力 seam:替换后端时,为什么上层不用改

项目把可替换功能设计为三角色:Service Definition、Service Provider、Consumer。一个“只有工具没有 provider”的功能不算完成的 seam。

能力族
Definition / Provider / Consumer 的典型关系
带来的替换能力
LLM
ctx.llm
 / DeepSeek、pi-ai adapter / agent loop
切换模型路线与认证方式
FS
ctx.fs
 / local、sandbox provider / read、edit、search 工具
本地或受控文件执行世界
Shell / Subprocess
shell 与进程服务 / bash、pwsh、本地进程树 / bash 工具
平台相关执行被封装
Sandbox
ctx.sandbox
 / Landlock、Seatbelt、bwrap 等 / 启动进程的消费者
在不改工具的情况下改变隔离策略
Web
search/fetch 服务 / 具体 HTTP、搜索 provider / web 工具
换检索或抓取后端
Subagent
provider registry / fork、spawn 等 provider / delegation 工具
将任务委派到不同执行模型
Session persistence
持久化服务 / JSONL、SQLite / session 生命周期
不改变 loop 即更换落盘介质

这是“Everything is a plugin”的实际含义:不是所有代码都可随意动态加载,而是每个可演化能力都有接口、实现和消费方,依赖指向抽象定义而不是某个本地 provider。

9. Web、SDK 与 Python:同一日志,不同投影面

Web 模式的 host/client 分离很彻底:host/ 提供 API gateway、HTTP server 与静态资源;client/ 负责浏览器状态、slot 和各项 ui-* 插件。工具 UI 卡片不是工具随手写 DOM,而是由工具的 presentCall / presentResult 生成纯展示意图;因此实时流与 session 回放可以得到一致界面。

SDK 层通过 JSON-RPC 投影 agent loop;Python SDK 不复刻一套核心运行时,而是启动同版本打包的 dsh --profile sdk。这是一种很务实的取舍:协议对外稳定,Agent 语义只有一份实现,跨语言客户端只消费它。

10. 工程质量与安全:从“能跑”到“可维护”

根 package.json 展示出这是一套严谨的 monorepo:TypeScript strict、Vitest、构建双面(host/client)、生成的工具/配置/模块图谱、文档链接与 Mermaid 校验、依赖闭包检查、发布产物检查。特别值得注意的实践包括:

  • 所有 Node 应用必须经 dsh + profile 启动,避免不可控的旁路入口;
  • 受支持 profile 通过 base bundle 统一挂载 sandbox、权限、approval 与凭据;
  • 凭据优先从环境读取,再回退到受控配置;不会把托管凭据直接 materialize 到进程环境;
  • telemetry 默认采用反馈触发的上传模式;
  • 预发布阶段明确不承诺旧磁盘格式兼容,SESSION_FORMAT_VERSION 仍为 0。

因此部署者应把它当作开发者预览期的可组合基础设施,而不是宣称完全稳定的最终端用户产品。

11. 推荐源码阅读路线

1. README.zh.md 与 docs/architecture.zh.md:建立 vocabulary。<br/>2. vendor/cordis/src/context.ts、registry.ts、events.ts:理解服务、effect 与 waterfall。<br/>3. packages/boot/app-boot/src/profile.ts 与 packages/bundle/base/cordis.patch.yml:看一个运行时怎样装配。<br/>4. packages/core/agent-loop/src/agent.ts:理解 inbox、turn、step 与请求构造。<br/>5. packages/core/session/src/index.ts:理解事件日志与历史投影。<br/>6. packages/core/tools/src/index.ts 以及 docs/tool-execution-pipeline.zh.md:理解安全执行与结果规范化。<br/>7. 选择一个纵切能力(例如 fs/、subagent/ 或 web/),分别追踪 definition、provider 与 tool consumer。

12. 总结

DeepSeek Harness 的真正创新点不在某个工具或某个模型适配器,而在它把 Agent runtime 的可变部分系统化为三个稳定支点:Cordis 插件组合、事件溯源会话、Definition–Provider–Consumer 能力 seam。profile/bundle 解决“如何组装”,agent loop 解决“如何持续工作”,session log 解决“如何可重建”,tools pipeline 解决“如何安全地作用于外部世界”。

如果要扩展它,最佳问题不是“该改哪个核心文件”,而是:“这项能力属于哪个 seam?应该新增哪条可记录的事实?要在哪个事件点插入?”——这正是该仓库源码结构试图强制引导开发者形成的思维方式。


本文依据

  • 官方源码:deepseek-harness/
  • 官方架构说明:docs/architecture.zh.md
  • 生命周期与工具执行图:docs/agent-lifecycle.zh.md、docs/tool-execution-pipeline.zh.md
  • 包总览:packages/README.md

相关学习资料