起底 DeepSeek Harness:把 Agent 主循环也做成插件的开源运行时(源码 + 架构图 + 企业级实现)
一句话总结:DeepSeek 开源的这个智能体运行时,把"模型可插拔"这件事做到了骨头缝里——不只是模型能换,连"agent 该怎么思考、下一步干什么"这个决策循环本身,都是一块能拔下来换掉的插件。这篇文章拆开看它的三层配置组合、事件管线和能力接缝设计,代码全部标注真实文件路径;最后聊聊同一套设计哲学,在企业级场景里是怎么落地的。
全文约 6000 字,基于 deepseek-harness 仓库源码与仓库自动生成文档逐条核实,文末附太一企业版与 MateClaw 的开源实现。

目录
- 一、先说个数字:不到 24 小时,5 万 Star
- 二、这是个什么项目
- 三、三层拼装:profile / bundle / patch
- 四、一次对话的八步管线
- 五、能力接缝(capability seam):全篇最值得学的设计
- 六、事件管线:三种"插话权限"
- 七、模型可见 ⟺ 已记录:审计怎么落地
- 八、二十个能力域全景 + 内置工具清单
- 九、同一套设计哲学,企业级怎么落地:太一企业版与 MateClaw
- 十、总结与自检清单
- 参考资料
一、先说个数字:不到 24 小时,5 万 Star
写这篇之前,我先去 GitHub API 上核了一遍数字,没敢直接抄网上流传的说法:
curl -s https://api.github.com/repos/deepseek-ai/deepseek-harness | \
python3 -c "import json,sys;d=json.load(sys.stdin);\
print(d['stargazers_count'], d['created_at'], d['pushed_at'])"
结果:仓库创建于 2026-08-13T11:56:32Z,我核验的时间点是 2026-08-14T01:49:28Z,上线还不到 24 小时,star 数已经冲到 52,959,按 24 小时算,平均每小时涨了将近 2200 颗。fork 数 4279,issue 数是 0——不是没人提问题,是仓库还没开放 issue,一个典型的"先扔出来看看反应"的开发者预览发布。
这么猛的涨势,通常有两种原因:要么是营销做得好,要么是内容本身戳中了工程师的痒点。翻完源码之后我倾向于后者——它解决的不是"模型能不能调工具"这种已经被无数框架讲烂的问题,而是"agent 这套骨架本身要怎么设计,才能撑得住五年后的维护"这个更难的问题。
二、这是个什么项目
DeepSeek Harness,仓库里简称 dsh,是 DeepSeek 官方开源的一套智能体运行时(agent harness)。README 里写得很直白:还在开发者预览阶段,接口会变,包会重命名,别指望现在就稳定。
它构建在一个叫 Cordis 的插件框架之上,核心理念项目自己总结为一句话:everything is a plugin,一切皆插件。
这句话在大多数框架里只是营销用语——留几个回调函数、加个中间件接口,就敢叫"插件化架构"。但 dsh 做得更彻底:连 agent 的主循环本身也是一个插件。模型怎么接、工具怎么注册、会话怎么存、沙箱策略怎么定,乃至"该看什么、该问模型什么、下一步该干嘛"这套决策循环,全部是可以被整体替换的组件,不存在一个焊死的"内核"。
理解这个项目,只需要记住一个前置概念:capability seam(能力接缝)——一套"接口定义 / 可插拔实现 / 消费者"三件套模式。第五节会展开讲。
三、三层拼装:profile / bundle / patch
dsh 的配置组合方式是三层结构,理解它能少走很多弯路。
bundle(预制配置包)→ profile(你自己的组合方案)→ patch(改造单)
- bundle:打底配置包。
dsh-base打的是每个 profile 都要用到的底子——模型适配器、工具箱、持久化、沙箱与审批策略、凭据、遥测。再叠一层模式包:要浏览器界面叠dsh-web-app,只跑一次任务叠dsh-headless。 - profile:你自己的组合方案,决定叠哪些 bundle,再写一份自己的
cordis.patch.yml。 - patch:改造单,按行 id 定位、整段替换目标配置,不是逐字段深合并。四层改造单(bundle → profile patch → 全局 patch → 命令行
--patch)依次盖上去,最后一层生效。

这套设计认真到什么程度?举个实锤——Windows 和 macOS/Linux 用不同的命令行执行器,这种平台差异在大多数项目里会写成 if-else,但在这里它是同一张改造单上两条互斥的行,一字不改摘自 packages/bundle/base/cordis.patch.yml:
- id: bash-sandbox
name: '@deepseek-ai/dsh-bash-sandbox'
disabled: !!js process.platform === 'win32'
- id: pwsh-sandbox
name: '@deepseek-ai/dsh-pwsh-sandbox'
disabled: !!js process.platform !== 'win32'
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
disabled: !!js process.platform === 'win32'
- id: tool-pwsh
name: '@deepseek-ai/dsh-tool-pwsh'
disabled: !!js process.platform !== 'win32'
四行 disabled 条件两两互斥,同一台机器上永远只剩一套 shell 栈生效。平台差异被当成数据处理,而不是揉进代码分支——这是"配置即架构"的一个很干净的例子。
想看最终拼出来的完整配置树,一条命令:
dsh --profile web --dump-config
四、一次对话的八步管线
dsh 里模型和用户的一次交互是个套娃结构:一个 turn(回合),里面套着零到多个 step(步),直到没活儿要干了才收尾。拆开来看是这八步:

其中第 3 步"插件插话"最值得细看,术语叫 waterfall(瀑布链):一堆插件排成队,每个都能看一眼消息、改一改,改完必须显式调用 next() 才轮到下一个;漏调一次,整条链就断在这儿,后面的插件全部收不到消息。这不是没写完的功能,是故意设计成这样——谁截胡了消息,代码里一眼能看出来。
这一步的官方定义,一字不改摘自 packages/core/agent/src/runtime-types.ts:
/**
* @param payload.agent - the agent proposing the step.
* @param payload.messages - messages removed from the inbox for this step.
* @param payload.turn - the turn that will own the step.
* @param payload.step - the step proposed by the loop.
* @param payload.signal - the current turn's cancellation signal.
* @mode waterfall
*/
'agent/pre-step'(
this: Scoped<Agent>,
payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal },
next: () => Promise<PreStepDecision>
): Promise<PreStepDecision>
@mode waterfall 这行注释不是随手写的说明文字,而是会被自动生成脚本扫进 docs/event-producer-consumer.md 的正经元数据——这套体系后面第六节详细讲。
五、能力接缝(capability seam):全篇最值得学的设计
如果整篇文章只让我留一个知识点,我会选这个。
dsh 把几乎所有"能力"都抽象成了一套标准接口,项目文档管这套模式叫 capability seam(能力接缝),拆开是三个角色:
| 角色 | 含义 |
|---|---|
| ① 接口定义 | 只约定能力长什么样,比如 ctx.fs 只约定要有 read / write / edit 三个方法 |
| ② 可插拔实现 | 挑一个装上,同一时刻只挂一个,比如 fs-local(本地磁盘)、fs-sandbox(沙箱围栏)、fs-e2b(云端沙箱容器)三选一 |
| ③ 消费者 | 只认接口,不管背后是哪个实现——模型可见的 read/write/edit 工具只认 ctx.fs,换掉背后的实现,消费者一行代码都不用改 |

项目文档特意强调:只有一个角色,不能叫"接缝"——光有接口没实现不算数,光有实现没有稳定消费者也不算数,三个角色凑齐才是完整的一套能力。这样的三件套,全项目出现了三十多次:
| 接口 | 可插拔实现 | 谁在用 |
|---|---|---|
ctx.llm | llm-deepseek · llm-pi-ai · llm-replay(测试) | agent-loop |
ctx.fs | fs-local · fs-sandbox · fs-e2b | tool-fs |
ctx.shell | bash-local · bash-sandbox · pwsh-local | tool-bash / tool-pwsh |
ctx.subprocess | subprocess-local · subprocess-e2b | bash/PTY/LSP/子Agent 执行器 |
ctx.subagents | spawn/fork-in-process · acp · codex · claude-code · dsh-sdk | tool-subagent · tool-ralph |
ctx.web | web-search-exa/perplexity/deepseek · web-fetch-http | tool-web |
ctx.sessionPersistence | session-persistence-jsonl · -sqlite | agent-loop · session-query |
ctx.approval | acp(ACP 桥应答) | core/tools · tool-bash;无应答即拒绝 |
这套设计不是文档层面的约定,而是被类型系统摁在地上强制执行的。整个工具注册表——所有模型能调用的能力的总管——本身就是一个继承自 Cordis Service 的普通类,摘自 packages/core/tools/src/index.ts:
export class ToolRuntime extends Service {
static inject = ['systemPrompt']
static Config: z<Config> = z.object({
mode: z.union(['native', 'code', 'both'] as const).default('native'),
maxParallelSubCalls: z.natural().min(1).default(10),
})
// ……注册表、around-dispatch 守卫管线都挂在这个类上
}
连工具注册表自己都要在 static inject 里声明依赖谁,不能搞特殊。"一切皆插件"不是宣传语,是编译期约束。
六、事件管线:三种"插话权限"
第四节提到的 agent/pre-step 只是一个例子,dsh 的整套扩展机制都建立在事件上,而且明确区分了三种"插话权限":
| 模式 | 含义 |
|---|---|
waterfall(瀑布链) | 必须显式调用 next() 才能继续往下传,可以拦截、改写 |
serial(串行) | 依次执行,不需要显式放行 |
emit(广播) | 纯广播,谁都拦不住 |
挑几个关键事件列出来(数据来自项目自动生成并在 CI 里校验新鲜度的 docs/event-producer-consumer.md):
| 事件 | 模式 | 干什么 |
|---|---|---|
agent/pre-step | waterfall | 这一步开始前,插件可改写或拒绝要发给模型的消息 |
agent/request-error | waterfall | 请求模型失败时的恢复入口(比如触发压缩重试) |
tools/pre-execute | waterfall | 工具真正执行前的策略检查 |
tools/execute | waterfall | 环绕执行——超时、检查点挂在这一层 |
tools/post-execute | waterfall | 结果出来后的策略——超长结果溢出存储、重复调用提醒 |
agent/turn-stopping | serial | 回合要收尾了,插件可以阻止真正停下 |
session/event | emit | 每条日志广播出去,23 个包在监听 |
fs/write-intent | waterfall | 写文件前的门禁——"没读过就不让改"策略挂在这 |
approval/request | waterfall | 一次性权限决策,没人应答默认拒绝 |
一个事件到底能不能被插件拦下来改主意,看它标的是哪种模式,源码里一眼能看明白,不需要翻十八层调用栈猜。这份事件矩阵本身也不是手写维护的——它由静态分析 TypeScript 源码自动生成,少声明一个 @mode,生成脚本就报错。
七、模型可见 ⟺ 已记录:审计怎么落地
dsh 有一条贯穿全局的硬规矩,项目里给它取了个学名叫 “Model-visible ⟺ logged”:模型看到的每一句话,必须能从日志里原样重建出来。
翻译成工程语言:模型不能"偷偷看到"任何没被记下来的东西。想让模型多看到一种新信息,必须先在日志格式(SessionEventMap)里正式开一个位置,不能走后门直接塞进去。
具体实现上,整个对话过程被当成一份只增不改的日志(session log)来记录:每一条模型看到的消息、每一次工具调用、每一个状态变化,都追加写进这份日志。它不是"记个日记",而是重放整场对话、给会话分叉、断点续跑、生成遥测统计的唯一真源。
这条规矩换来的直接好处是:一个智能体半夜自己续跑了三个小时,理论上你能把整场对话原样倒带出来看,而不是对着一堆"看起来结果没错"的最终产出猜过程。做过线上排障的工程师应该懂这种价值——最怕的不是系统坏了,是坏了但说不清怎么坏的。
八、二十个能力域全景 + 内置工具清单
仓库有 300 多个 workspace 包,按功能收拢一下,大致是这二十个能力域,每一块都是独立插件,能单独开关、单独替换、单独测试:

| 能力域 | 干什么 | 主要包 |
|---|---|---|
| Shell / 终端 | 跑命令行,一次性或持久 PTY 两种 | shell/*, terminal/* |
| 文件系统 | 读写编辑 + 内置 ripgrep 搜索,改前强制先读 | fs/* |
| 进程沙箱 | bwrap / Landlock / Seatbelt / Windows ACL 围住能碰哪些文件 | sandbox/*, native/ |
| 子 Agent | 6 种委派方式:进程内、ACP、Codex、Claude Code、SDK | subagent/* |
| 工作流 / 后台作业 | 脚本编排 + 新鲜循环 Ralph;统一的后台任务控制 | workflow/*, jobs/* |
| Code Mode | 模型写一段 TS 程序,一次性并发调用多个工具 | core/tools, code-runtime/* |
| 技能 Skill | SKILL.md 目录,先看名字/描述,匹配上再加载完整说明 | skill/* |
| Web 访问 / LSP | 网页搜索抓取;四种归一化的代码导航查询 | web/*, lsp/* |
| 计划 / 目标 / 定时 | 先出方案审过再动手;长任务自动续跑;会话本地到点提醒 | plan/*, goal/*, schedule/* |
| 人机审批 | 一次性权限决策,没人应答默认拒绝(fail-closed) | interaction/* |
| 自我修改 | 智能体自己写代码,动态挂/卸自己的插件(vm 沙箱执行) | extensions/* |
| Hook 桥接 / MCP | 忠实执行既有 Claude Code / Codex hooks.json;接入外部 MCP 工具 | hooks/*, mcp/* |
| Web GUI / 对外协议 | 30+ 插件拼出浏览器界面;ACP / JSON-RPC SDK 给别的程序驱动 dsh | client/*, host/*, acp/, sdk/* |
这张表来自项目自己的 docs/tool-catalog.md——生成方式挺硬核:不是拿正则扫代码文本,而是真的把每一个工具插件启动起来,读它运行时暴露出来的 schema,因为有些工具名和参数是运行时拼出来的,静态扫描扫不出来。少注册一个工具包,脚本直接报错不给过。
拿最常用的 bash 举例,它真正发给模型的 JSON Schema(原样摘自生成文档):
{
"properties": {
"command": { "type": "string" },
"description": { "type": "string" },
"timeoutMs": { "type": "number" },
"workdir": { "type": "string" },
"run_in_background": { "type": "boolean" }
},
"required": ["command", "description"]
}
留意 run_in_background 这个参数——设成 true,调用立刻返回一个任务 id,真正的输出靠 job_output 另外去取。这是"后台作业"能力域和"Shell"能力域怎么勾连起来的实锤:一个工具身上的一个参数,背后调用了另一整个能力接口,模型完全不用知道这层弯弯绕。
九、同一套设计哲学,企业级怎么落地:太一企业版与 MateClaw
讲了这么多 dsh 的架构,聊点题外话。dsh 从 agent 运行时这个方向,把"模型是可替换的模块,而不是绑死的地基"这条设计原则做到了极致;这条原则我们在企业级场景里,从另一个方向也做了两年——太一企业版(mate-hive 引擎)和它的开源底座 MateClaw。
两边技术栈完全不同:dsh 是 TypeScript + Cordis 插件框架,走的是"agent 运行时"路线;MateClaw 是 Spring Boot + Spring AI Alibaba,走的是"企业级治理平台"路线。但殊途同归,核心判断是同一句话:
| dsh 的设计 | MateClaw / 太一企业版对应的实现 |
|---|---|
ctx.llm 能力接缝,模型是可插拔实现 | Purpose 路由:业务只声明"要什么能力",不声明"要哪个模型";14+ 家模型厂商可切换、主备熔断 |
| profile / bundle / patch 三层配置组合 | starter 化交付:装什么 starter、有没有 license,决定是开源版还是企业版 |
| session log:模型可见 ⟺ 已记录 | 全链路审计:Thought / Action / Observation 全轨迹落库,runId 贯穿网关到模型调用 |
| capability seam 三件套(接口/实现/消费者) | Filter 链治理:ToolGuardFilter → AuditFilter → DesensitizeFilter,加一层治理 = 加一个 Bean |
| 一切皆插件,连 agent-loop 本身也是插件 | Agent 定义落库,是组织资产不是散在 prompt 里的字符串;草稿/发布双槽,可回滚 |
MateClaw 是这套企业级实现的开源版本:
git clone https://github.com/mateaix/matecloud # DDD 微服务底座
git clone https://github.com/mateaix/mateclaw # AI Agent 平台,Apache 2.0
它基本对应上面表格右列的实现:14+ 家模型厂商路由与故障切换、数字员工(ReAct + Plan-and-Execute 编排)、Skills 体系、MCP/ACP 双协议接入、RBAC + 审批流 + 审计留痕 + 内容合规扫描,外加 LLM Wiki(带引用溯源)、工作流引擎(7 种步骤模式)、8 个 IM 渠道接入。技术栈是 Spring Boot 3.5 + Spring AI Alibaba + MyBatis Plus + Flyway,前端 Vue 3 + TS + Vite + Element Plus。协议是 Apache 2.0,README 上有句话我很喜欢:free, no metered tokens, no seat billing——免费,不按 token 计量,不按坐席收费。
至于太一企业版,干的就是把这套能力做成生产级:租户隔离、审计回放、工作流和知识沉淀这些长期值钱的东西留在企业自己手里。它和 MateClaw 是同一份核心代码,装什么 starter、有没有 license,决定它是开源版还是企业版——丢 jar 即点亮,拔 jar 即降级。商业咨询与私有化部署入口:mate.vip/enterprise。
一个 TypeScript 生态验证了这套架构范式,一个 Java 生态把它做进了企业级治理——这大概说明,"模型可替换、能力可插拔"这条路不是谁拍脑袋想出来的,做 agent 基础设施的人,走到最后都会走到这儿。
十、总结与自检清单
核心观点回顾
- 循环本身也要设计成可替换组件——大部分框架把主循环焊死在内核里,dsh 把它做成一个服务,扩展包只依赖抽象接口,不依赖具体实现
- 能力用三件套接口隔离——接口定义 / 可插拔实现 / 消费者,三个角色缺一不可,这是"一切皆插件"真正能落地的原因
- 模型可见即已记录——把"必须能重放"提到架构约束层面,而不是"最好记个日志"这种口头建议
- 文档由代码生成,不是手写的——工具目录靠真启动插件读 schema,事件矩阵靠静态分析源码,两者都在 CI 里做新鲜度校验
- waterfall 事件强制显式放行——没有隐式的"默认往下传",想拦截就拦截,想放行必须显式调用
- 审批默认拒绝(fail-closed)——没人应答,请求直接判定为不可用,而不是放行
六个自检问题
不谈架构美学,全是能当场对照自己项目验证的:
- 你的 agent 主循环,能不能不改内核代码就换掉?
- 模型供应商明天涨价一倍,你的业务代码要不要跟着改?
- 出了问题,你能不能把整场对话原样倒带出来看,而不是猜?
- 你的事件/中间件机制,插件想拦截和想放行,代码上分得清楚吗?
- 你的工具文档,是手写的还是自动生成校验的?多久没更新过了?
- 高风险操作没人审批的时候,你的系统默认是放行还是拒绝?
六个问题如果有一半答不上来,那不是 agent 能力不够,是运行时骨架还没设计对。
参考资料
- deepseek-ai/deepseek-harness 仓库 README.md / AGENTS.md
- 仓库自动生成文档:
docs/architecture.md·docs/capability-seams.md·docs/tool-catalog.md·docs/event-producer-consumer.md - 源码:
packages/core/agent/src/runtime-types.ts·packages/core/tools/src/index.ts·packages/bundle/base/cordis.patch.yml - GitHub API star 数据,核验于 2026-08-14 01:49 UTC(仓库创建于 2026-08-13 11:56 UTC)
- MateClaw · MateCloud README
- 太一企业版产品页:mate.vip/ee
声明:本文架构图均为原创绘制。deepseek-harness 相关代码片段均标注真实文件路径,摘录用于说明架构,非完整源码复制,仓库仍在开发者预览阶段,具体接口以你核对时的源码为准;本文与 DeepSeek 官方无隶属关系,属第三方技术解读。GitHub star 数据经 API 实时核验,具体数字请以你阅读时刻的最新数据为准。MateClaw / 太一企业版相关功能点取自项目 README 与产品页。
如果这篇对你有用,欢迎点赞收藏。具体架构问题,评论区聊。
更多推荐



所有评论(0)