这个周末我给 DeepSeek Harness 做了一次解剖手术,结果惊呆了
"
453K 行 TypeScript,219 个工作区包,我一行行扒开 DeepSeek Harness 的内脏,看到的不只是一个 Agent 框架——是一台可拆可装、热插拔的「Agent 外科手术台」。
—— 亿智扬AI

这个周末,我做了一件可能只有技术疯子才干的事:把 DeepSeek 刚开源的 Harness(dsh)仓库 clone 下来,从 vendor/cordis/src/context.ts 一路读到 packages/core/agent-loop ,453,000 行 TypeScript,一个文件都没放过。
结果?说实话,惊呆了。
不是因为它有多华丽,而是因为它的 工程审美太超前了 。当别的 Agent 框架还在纠结"怎么让模型多调几个工具"的时候,DeepSeek 直接把问题升了一个维度—— Agent 的每个零件都该是可拆可换的插件,包括 Agent 循环本身。
这不是"又开源了一个 Agent 工具"的故事。这是一篇关于"如何把 Agent 运行时做成操作系统"的工程教科书。
今天这篇,我把解剖台上的发现掰开揉碎讲给你听。不卖弄术语,只讲大白话——但该硬核的地方,一行代码都不含糊。
📌 本文看点
01
Cordis:能让插件"后悔药"生效的引擎
02
会话日志:Agent 的行车记录仪
03
创造模式:Agent 自己造 Agent
01THE ENGINE
Cordis:能让插件"后悔药"生效的引擎
先把核心矛盾说清楚。
做 Agent 框架的人都知道一个痛点:插件系统。你写了一个 Plugin A,它注册了 3 个工具、5 个事件监听器、1 个定时器。然后你想把它换成 Plugin B——问题来了: A 注册的那堆东西,谁来清理?
传统方案靠人肉:每个插件作者自己写 deactivate(),记得把当初注册的东西一个一个注销。听起来简单,实际上——随着系统越来越复杂,这种"靠自觉"的模式迟早崩盘。漏掉一个监听器?内存泄漏。忘关一个定时器?幽灵进程。少删一个工具?模型还能调到不存在的工具,然后报错。
VS Code 就是典型例子。你禁用一个扩展,它让你 重启整个宿主 。因为 VS Code 自己都没把握扩展的副作用能被完整撤销。
DeepSeek 的解法叫 Cordis ——一个被 DeepSeek 整个 vendor 进仓库(不是 npm install,是源码直接拷进来改)的插件元框架。它的核心杀手锏就四个字: 可逆副作用。
453K
行 TypeScript 代码
219
个工作区包
97%
TypeScript 占比
ctx.effect():每一句副作用都配了"撤销键"
Cordis 里最核心的 API 就一个: ctx.effect() 。你做的任何"有副作用"的事——注册工具、开定时器、监听事件——都得通过它来登记。登记的时候立刻执行,同时要求你返回一个"撤销函数"。
源码中的写法长这样:
// 注册一个事件监听 + 一个定时器
ctx.effect(() => {
const off = ctx.on(‘tool/result’, handler)
const timer = setInterval(flush, 1000)
// 返回撤销函数:卸载时按注册的相反顺序执行
return () => {
clearInterval(timer) // 后注册的先撤销
off() // 再退订监听
}
})
看懂了吗? 副作用和它的撤销逻辑写在一起,而不是分到两个文件里。 卸载的时候,框架自动按 LIFO(后进先出)顺序调用所有撤销函数——先 clearInterval,再 off()。插件作者完全不需要操心"我当初注册了哪些东西"。
如果你写过 React,这个模式你一定觉得眼熟—— 它和 useEffect 几乎一模一样。 回调里做副作用,返回清理函数,组件卸载时框架帮你调。只不过 React 管的是 DOM 渲染,Cordis 管的是 Agent 运行时。
Fiber:插件的生命周期状态机
每个插件被挂载后,Cordis 会给它创建一个 Fiber ——你可以理解成"插件实例的灵魂"。这个灵魂有六个状态:
① PENDING —— 等待依赖就绪
↓
② LOADING —— 正在初始化
↓
③ ACTIVE —— 运行中
↓
④ UNLOADING —— 正在卸载
↓
⑤ DISPOSED —— 已销毁
最妙的是 PENDING 这个状态。插件通过 inject: [‘tools’, ‘shell’] 声明"我依赖 tools 和 shell 两个服务"。如果这两个服务还没准备好,Fiber 就乖乖停在 PENDING,一个字都不执行。等依赖出现,自动激活。
这意味着什么? 插件之间不需要商量加载顺序。 你声明依赖,框架负责排序。你 A 依赖 B,B 依赖 C,框架自己会算出来 C→B→A 的启动链。B 被热替换了?A 自动卸载,等新的 B 就绪,再自动重新加载。
🔬 解剖发现:Agent Loop 本身就是个插件
在 packages/core/agent-loop 里,Agent 的核心循环——决定什么时候给模型发请求、什么时候执行工具、什么时候判断任务完成——它不是写死在框架内核里的特权代码,它就是一个叫 agent-loop 的普通插件。你可以写一个全新的 agent-loop 实现,把整个"Agent 怎么思考"的逻辑换掉,其他插件完全不受影响。这相当于 VS Code 允许你换掉整个编辑器引擎,而不只是换个主题。
12 个可替换核心能力:从发动机到雨刮器
如果把 Agent 比作一辆车,DSH 给每个零件都做了标准接口:
模型适配器 · 换发动机
ctx.llm
文件系统 · 换硬盘
ctx.fs
Shell 执行 · 换变速箱
ctx.shell
沙箱 · 换安全气囊
ctx.sandbox
子 Agent · 换车队管理系统
ctx.subagents
会话持久化 · 换行车记录仪
ctx.sessionPersistence
代码执行 · 换车载电脑
ctx.codeRuntime
(仅展示 7/12 项,完整列表见仓库 docs/architecture.md)
想用 OpenAI 代替 DeepSeek?换 ctx.llm 的适配器。想让 Agent 在 Docker 沙箱里跑命令?换 ctx.shell 的实现。想让会话存到 PostgreSQL 而不是本地文件?换 ctx.sessionPersistence 的 provider。
改一行配置,换一个零件。不用 fork,不用改源码,不用重启进程。
02THE BLACK BOX
会话日志:Agent 的行车记录仪
解剖到 packages/core/session 的时候,我停下来了。因为我意识到,这是整个 DSH 里 最精妙的设计,没有之一。
先说一个问题:你的 Agent 跑了 40 分钟,中间调了 20 次工具,改了 8 个文件,然后它说"任务完成"。你信吗?你怎么知道它中间到底干了什么?模型当时看到了什么上下文?哪一步出了问题?
大多数 Agent 框架的回答是:看日志吧。但日志往往是"尽力而为"的——记了多少算多少,格式不保证,完整性不保证。
DSH 的回答是: Model-visible equals logged——模型能看到的一切,都必须能从日志中完整重建。 这不是文档里的约定,是代码里硬编码的断言。违反就崩溃。
Agent 的一生,写成 Git 提交记录
DSH 把 Agent 的每一次"呼吸"都存成 event,写入一个仅追加(append-only)的事件流。就像 Git 把每次代码变更存成 commit:
# Session 日志(就像 git log)
[seq 0] turn/start → 新一轮对话开始
[seq 1] step/start → 开始一次模型调用
[seq 2] user/message → 用户说了什么
[seq 3] assistant/chunk → 模型吐了一个 token “我”
[seq 4] assistant/chunk → 模型又吐了一个 token “来”
[seq 5] assistant/msg → 模型完整回复 + token 消耗
[seq 6] tool/call → 模型决定调用 bash 工具
[seq 7] tool/result → bash 返回了结果
[seq 8] step/end → 这次模型调用结束
[seq 9] turn/end → 这轮对话结束
关键在于:模型的历史消息不是另外存储的,而是从这个日志 派生 出来的(通过 deriveMessages() 函数)。日志是唯一的事实来源(source of truth)。
🔬 解剖发现:运行时断言比文档约定靠谱一万倍
在 packages/core/agent-loop/src/invariant.ts 里,每次模型请求发出前,代码都会断言:请求里的 messages 和从日志派生的 messages 字节级一致。不一致?直接 throw。这意味着——你不能"偷偷"给模型塞上下文而不记日志。框架会物理阻止你。代价是每次请求多序列化一次完整消息历史,但 DeepSeek 认为"审计性比性能更重要",这个判断我举双手赞成。
"时光机"能力:回放、分叉、恢复
因为模型历史是从日志派生的,你可以做三件传统框架做不到的事:
⏪ 回放
从任意时间点重放日志,看不同插件组合在相同轨迹上的表现。等于给 Agent 做 A/B 测试。
🌿 分叉
从任意事件点分叉出新会话,复用之前的历史,追加新操作。相当于 git checkout -b,不需要复制数据。
🔄 恢复
崩溃后从日志重建整个会话状态,精确到最后一个 token。服务中断?不存在的。
还有更绝的——测试。DSH 把真实录制的会话日志提交到仓库作为测试夹具(fixture),然后从中派生一个 Mock 模型。同一个 .jsonl 日志文件既是输入又是期望输出。测试时启动真实 Agent 子进程,跑完整循环,最后 diff 新生成的日志和原始夹具。
一个文件,两个角色。CI 里不需要 API Key,不需要 LLM-as-judge,回归测试精确到日志 diff。 这个测试方案我愿意称之为"Agent 测试的最佳实践"。
03FOUR MODES
四种模式:从"开箱即用"到"Agent 自己造插件"
DSH 内置了四种 Agent 预设(Preset),每个预设就是一组插件的不同组合方式。理解了它们,你就理解了"一切皆插件"到底意味着什么。
① 标准模式
全能均衡型,功能最完整。文件编辑、Shell、网页搜索、Skills、计划模式、目标追踪、子 Agent 调度、工作流编排——一个按钮全配齐。90% 的日常需求它都够。
② PTC 模式
模型写 TypeScript 程序编排多步工具调用,适合批量任务、多步骤重复操作。
③ 极简模式
只保留 Bash + 文件编辑器,适合基准测试、最小复现。
④ 创造模式
可检查运行时、试验插件、创作新预设,适合实验新插件、组合自定义 Agent。
PTC 模式:让模型写代码来调工具
这个设计很聪明。传统模式下,模型每次想调工具就发一个 tool_call,等结果回来再调下一个。如果一个任务要调 10 次工具,那就是 10 轮模型请求——慢、贵、上下文还膨胀。
PTC(Programmatic Tool Calling)模式让模型直接写一段 TypeScript 程序,把多次工具调用编排成一个脚本,一次提交执行完。相当于把 10 轮对话压缩成 1 轮。 而且——模型写的代码调工具时,同样要经过 tools/pre-execute 权限链。模型写 await tools.bash(“rm -rf /”)?权限插件说不行就不行,代码直接抛异常。 安全模型无法被代码绕过。
创造模式:Agent 自己造 Agent(最炸裂的)
这是整个 DSH 里最"疯"的设计。创造模式(Creator)给 Agent 暴露了 Cordis 运行时的检查和操作能力——Agent 可以在运行时 查看当前加载了哪些插件、试验新的 Cordis 插件、甚至让模型参与创作新的 Agent 预设。
官方文档里有一句话特别值得品味——"一个有缺陷的自我修改可能废掉唯一能用来恢复的那个进程。"这正是 Cordis 可逆副作用的用武之地:Agent 自己写了个有 bug 的插件装进去了?没关系,可逆副作用保证它被卸载时,所有影响被完整撤销,宿主进程不受影响。
这不是"Agent 用工具",这是"Agent 改自己"。自我进化的前提,是有一个能安全回滚的运行时。
04THE COMPARISON
跟 Claude Code、Codex 相比,到底赢在哪?
先把话说在前面:Claude Code 和 Codex 都是优秀的产品。但它们的架构哲学和 DSH 完全不同。
▍架构
Claude Code:固定 Agent 循环
Codex:固定 Agent 循环
DeepSeek Harness:插件式微内核
▍Agent Loop
Claude Code:不可替换
Codex:不可替换
DeepSeek Harness:插件可替换
▍模型绑定
Claude Code:仅 Claude
Codex:仅 OpenAI
DeepSeek Harness:40+ 第三方模型
▍开源程度
Claude Code:有限
Codex:有限
DeepSeek Harness:MIT 完全开源
▍自修改
Claude Code:不支持
Codex:不支持
DeepSeek Harness:创造模式支持
▍会话可观测
Claude Code:基础日志
Codex:基础日志
DeepSeek Harness:运行时断言 + 回放 + 分叉
核心差异:别人给你一辆整车,DeepSeek 给你一盒乐高
Claude Code 和 Codex 的思路是:我给你一个完整的、精心调校的 Agent,你拿去用就行。Agent 循环怎么跑、上下文怎么组织、工具怎么调度——这些是写死在内核里的,你碰不到。
这没什么不好。对于大多数用户来说,"开箱即用"就是最好的体验。但如果你是一个 需要深度定制 Agent 行为的工程团队 ——比如你想换掉 Agent 的决策逻辑、想接入内部审批系统、想让会话存到自己的数据库、想给 Agent 加一个自定义的上下文压缩策略——你就得 fork 它们的代码,维护一个私有分支,每次上游更新都是一场 merge 噩梦。
DSH 不一样。它的内核几乎"什么都不干",只负责管插件的生命周期和依赖关系。所有实际能力——模型、工具、循环、存储、UI——都是插件。你想改什么,写个插件挂上去就行。 不需要 fork,不需要改源码,不需要重启进程。
MIT 协议:不只是开源,是"随便用"
这一点容易被忽略,但极其重要。MIT 协议意味着你可以 fork、改、卖、商用、闭源——不需要问 DeepSeek 要授权,不需要付版税。对于想基于 DSH 做自己产品的团队来说,这是 法律层面上的"可组合性" 。如果协议是 GPL 或 Source-Available,"一切皆插件"的承诺就会大打折扣——你组合出来的东西也得开源,商业空间直接砍半。
DeepSeek 选 MIT 不是随手选的。它和 React、Node.js 选的是同一个协议——而那两个项目恰好是"插件生态最繁荣"的典范。DSH 的文档里也直接引用了它们作为先例。 这是有意为之的生态策略,不是"顺手选了个开源协议"。
05THE PIPELINE
工具执行管道:Koa/Express 中间件模式的复刻
如果你写过 Node.js 的 Koa 或 Express,你一定熟悉中间件模式:请求进来,经过一层层中间件处理,每一层可以放行(调 next())或短路(直接返回结果)。
DSH 把这个模式原封不动搬到了工具执行上。每次模型调用工具,请求要过一条 Waterfall 链 :
模型发出 tool_call
↓
tools/pre-execute(权限检查)
↓
tools/execute(执行)
↓
tools/post-execute(质检)
↓
tools/result(记录结果)
每一层都可以做不同的事:
pre-execute: 门禁。允许、拒绝、或要求人工审批。三个监听器可以依次检查 Hook 规则、权限策略、沙箱策略,任何一个说"不"就短路。
execute: 包裹实际执行。加超时、加重试、打指标。
post-execute: 质检。改写结果、阻止输出、添加额外上下文。
result: 监控。只看不改,记录最终结果。
关键细节:这些钩子不是随便排的。pre-execute 按优先级排序,高优先级先判断;execute 用来包裹执行。而且—— Code Mode 中模型生成的代码调工具时,同样必须经过这条链。 不存在"模型写的代码能绕过权限"这种事。
MCP 和原生工具走同一条路
解剖 packages/mcp/mcp-client 的时候发现一个关键设计:MCP 工具的注册和原生工具完全一样,都走 ctx.tools.register() 。这意味着:
模型看到的 tool schema 格式完全一致。权限链、超时、压缩行为完全一致。 不存在"MCP 工具是特殊的"这种概念。 在 DSH 里,所有工具生而平等。
∞THE VERDICT
这不是工具,是 Agent 时代的操作系统
解剖完这 453K 行代码,我最大的感受是:DeepSeek 做的不是"又一个 Agent 框架",而是 在定义 Agent 运行时的工程范式 。
Cordis 的可逆副作用,解决了插件系统的千古难题——“卸载不干净”。会话日志的运行时断言,让"模型看到了什么"不再是一个黑盒。四种预设模式,展示了"一切皆插件"到底能拼出多少种形态。创造模式甚至让 Agent 自己参与运行时的构建。
当然,它还不完美。开发者预览阶段,API 还在变,核心插件还不稳定,官方甚至明确警告"会有破坏性更新"。沙箱目前只管文件系统,不是通用的网络/进程隔离边界。但这些都不影响一个判断:
DSH 的基础设施赌注是清晰的——Agent 框架应该是组合表面,不是围墙花园。
北大和 DeepSeek 联合发表的那篇论文——《A Programming Paradigm for Spatiotemporal Composability》——不是学术装饰。它回答了一个根本问题:如果"一切皆插件"是真的,那你需要一套严格的数学基础来保证它安全。可逆副作用和反应性协效应就是那套基础。
未来的六个月会证明:是插件生态围绕着这个赌注长出来,还是大多数开发者其实更想要一个精心策展的体验而不是无限的可配置性。
但至少现在,DeepSeek 给了开发者一个选择:你是要一辆别人帮你调好的车,还是 一盒能拼出任何形状的乐高?
这个周末的解剖手术告诉我:这盒乐高,值得拆开看看。
— END —
我是 亿智扬AI ,热衷于把硬核 AI 技术拆成大白话。这篇 453K 行代码的解剖报告,如果你觉得过瘾,欢迎 点赞、在看、转发 三连。
📎 参考资料:github.com/deepseek-ai/deepseek-harness | docs/architecture.md | Cordis 论文《A Programming Paradigm for Spatiotemporal Composability》
⚠️ 声明:本文基于 2026-08-13 开源版本(v0.1.0-rc.6)的源码分析撰写。框架处于开发者预览阶段,API 可能变更,请以官方最新文档为准。
更多推荐



所有评论(0)