Harness 不是又一个 Agent 框架,而是一套让模型 真正能干完事 的执行系统。
过去两年,大模型工程的关注点基本是这条线:模型 → Prompt → RAG → Agent。但凡真正跑过复杂任务就会发现一件事——模型能力只是 Agent 能力的一部分。一个模型再聪明,如果没有工具调用、没有环境管理、没有状态管理、没有执行循环、没有错误恢复、没有日志追踪,它照样很难稳定地把一件复杂事情干完。

DeepSeek 前几天开源了一个叫 Harness 的项目,代号 dsh。第一眼看容易划走——又一个 Agent 框架。但把它的架构文档、生命周期文档、子系统文档翻了一遍之后,我改变了想法。这个项目值得聊的地方,不是它接了几个模型、封了几个工具,而是它把“模型如何完成任务”这件事,做到了一个少见的工程精细程度。
官方文档里有句话我印象很深:
这也是从 Prompt Engineering 到 Context Engineering 再到 Harness Engineering 的一个典型体现。这篇文章想顺着源码和文档,把几个具体机制拆开给你看。
📌 本文看点
一个被低估的公式:Agent = Model + Harness
从源码看架构:任务是怎么一层层走下来的
最核心的部分:执行 Loop 到底怎么转
一个被低估的公式:Agent = Model + Harness
先建立一个认知框架。

模型负责的东西很纯粹:理解、推理、生成、决策。Harness 负责其余所有事:给模型看什么上下文、什么时候轮到它执行、它能调用什么工具、工具在哪跑、怎么保存状态、出错了怎么办、怎么判断任务完成。
最简单的 Agent 应用长这样:
User → Prompt → LLM → Answer
加一层工具调用之后:
User → LLM → Tool → Observation → LLM → Tool → ... → Final Answer
到这一步大部分团队都能做出来。真正复杂的 Agent,其实是这样一条链路:
Task → Context → Model → Decision → Tool/Environment → Observation
→ State Update → Verification → Retry/Continue → Final Result
真正复杂的是后面这套控制系统,而不是前面的 Prompt。这句话可以当成贯穿全文的线:Context 决定模型知道什么,Tool 决定模型能做什么,Harness 决定模型能不能把事情真正做完。
从源码看架构:任务是怎么一层层走下来的
一个任务从进入 dsh,到模型执行,到最终产出结果,中间经过了哪些层?
按官方架构文档的说法,答案不是“Context / Model / Tool / Environment / State / Evaluation” 这几个并列模块拼出来的——dsh 的真实组织方式更彻底:没有一个模块是特权的,模型适配器、工具注册表、会话日志、连 Agent 循环本身,全都是插件,通过配置组装起来。
这套组装机制有两层:
Bundle(组合包):Cordis 配置项加它挂载的代码,是一种分发格式,它插入的内容始终可以被上层 patch 覆盖
Profile(配置):存放在 Harness home 里的具名组装,列出自己叠放了哪些 bundle,还保存用户自己的 cordis.patch.yml
层与层的应用顺序很讲究:先按 profile 列出的顺序应用每个 bundle,再应用 profile 自己的 patch,再应用 home 级别的 patch,最后才是命令行传入的 --patch。dsh-base 是每个 profile 的第一层,包含模型适配器、工具、持久化、沙箱与审批策略;dsh-web-app 在此基础上加浏览器应用;dsh-headless 加一个完全不带服务器的一次性运行器。
想直观感受这套组装结果,跑一条命令就够了:
dsh --profile web --dump-config
这条命令会把当前 profile 实际加载的整棵插件树摊开——从模型适配器到沙箱策略,每一层配置来自哪个 bundle、被哪一层 patch 覆盖过,一目了然。这也是为什么说 dsh 更像一个操作系统内核,而不是一个应用框架——扩展点不是“预留的几个回调”,而是系统默认的组织方式。
最核心的部分:执行 Loop 到底怎么转
Harness 的核心不是某一个类,而是一条 Loop。这条 Loop 不是通用意义上“while not finished 一直跑”的抽象循环,dsh 把它拆得很具体:

几个值得展开的细节:
事件被明确分成两类。 turn/*、step/*、user/message、assistant/*、tool/* 是持久会话事件,老老实实写进日志构成可回放的历史;agent/* 是实时扩展点,用来做队列管理、状态通知、prompt 拦截,不进日志。不是所有事件都值得永久记录,但所有影响模型看到什么的决策,最终都要落到会被记录的那条日志上。
事件有两种执行模式。 agent/pre-step、agent/request、llm/stream、三个 tools/* 事件都是瀑布式——每个监听器必须显式调用 next() 才能把处理权继续往下传,跟中间件几乎一模一样。而 agent/turn-stopping 是串行事件,没有 next(),谁都可以直接一票终止这个回合。一个用来层层加工,一个用来一锤定音。
agent/pre-step 有个反直觉但很讲道理的限制。 这个钩子不能直接改写消息内容——模型可见的内容必须走已被记录的日志通道。你能在这一步拒绝这批消息,或者替换成另一批已经写进日志的消息,但不能凭空塞一段游离在日志之外的文本让模型看到。这条限制保证了“模型看到的东西必须能从日志里重建”这条硬规则不会被这一个钩子破坏掉。
一次尝试哪怕彻底失败,也会被记下来。 如果消息在 agent/pre-step 被拒绝,或者第一次认领时就是空的,系统依然会关闭一个“没有花费任何 step”的完整 turn——日志里留下“这次尝试发生过”的记录,而不是悄悄丢弃。
Context 是怎么构建出来的
agent/pre-step 这个钩子,本质上就是在回答“模型这一步该看到什么”这个问题。System Prompt、历史消息、工具 schema,全部在这一步组装完成,然后才真正打包成一次模型请求。
这里可以引出一个贯穿全文的观点:Harness 本质上是在控制模型看到什么。这就是 Context Engineering 的工程落地——不是靠精心措辞的提示词模板,而是靠一个可以拦截、可以拒绝、可以替换的钩子,系统性地管住“模型的输入”这件事。
工具调用:不只是 Function Calling
真正的 Tool System 要解决的问题,远比“模型说要调用一个函数,然后执行它”复杂:注册表、参数解析、权限控制、执行、结果回传、错误处理、超时、日志,每一环都要落地。dsh 把工具执行拆成三段流水线——tools/pre-execute → tools/execute → tools/post-execute,每一段都是瀑布式事件,插件可以在任意一段介入。
更关键的是 Tool 和 Environment 之间的关系。dsh 引入了一个叫 Capability Seam(能力接缝) 的设计单位,每个能力由三个角色拼出来:
Service Definition:只声明接口,比如 ctx.llm、ctx.fs
Service Provider:真正的实现,比如 llm-deepseek 实现 ctx.llm,fs-local 实现 ctx.fs
Consumer:使用这个能力的一方,往往就是模型能调用的一个工具,比如 tool-bash 消费的是 shell 这个能力
三个角色缺一不可。这套设计的直接好处是:想把文件系统从本地换成远程沙箱,只需要换掉 Provider,Definition 和 Consumer 一行代码都不用动,Bash、持久终端、代码检查这些能力会整体跟着搬家。这也是 Agent 与普通 Chatbot 最大的工程区别之一——Chatbot 的“工具”往往是辅助性的,而在 dsh 里,Environment 是核心基础设施,不是可有可无的附加项。
State:一条日志,取代了一整套状态机
如果 Agent 要连续执行几十步,它到底记住了什么?很多框架会分别维护 Task State、Execution State、Tool State、Context State 好几套东西,但 dsh 的做法是把它们统一收敛成一条只能追加、不能修改的 Session Log。
有一条硬规则贯穿始终:任何模型能看到的东西,必须能从这条日志里完整重建出来。这不是文档里写写的约定,是运行时会强制校验的规则——前面提到 agent/pre-step 不能直接改写消息内容,本质上就是这条规则在具体钩子上的体现。
会话要不要 fork、断点要不要恢复、UI 要不要重放某一次流式输出,全部从这一条日志派生,不需要另外维护一套状态系统。就连 assistant/message 这种记录一次成功模型调用的事件,连“内容为空但耗尽了 token 限额”这种边缘情况都会精确记下 usage 和对应的 chunk 序列。
Memory 解决的是“记住什么”,State 解决的是“现在进行到哪里”——这句话在通用 Agent 框架里往往对应两套独立机制,但在 dsh 里,两者被统一成了同一条日志的不同投影,这是比“分开维护两套系统”更干净的做法。
也正因为日志格式本身还在快速迭代,dsh-session 包里的 SESSION_FORMAT_VERSION 现在被钉在 0,官方原话是目前没有向后兼容承诺。这是把地基做对放在稳定性前面的一个坦诚代价。
错误处理:真正的 Agent 不可能一次成功
Agent 执行中的错误来源很多:模型报错、工具报错、环境报错、解析失败、超时、无效动作、结果错误。这部分很能体现工程水平,因为它决定了系统撑不撑得住长任务。
dsh 有一个具体的补偿机制值得细讲:内置的 dsh-compaction-basic 插件专门处理上下文压力。它挂在两个时机上——一是 agent/pre-step 阶段根据上下文压力提前介入,二是 agent/request-error 阶段专门盯着“上下文溢出”这一类错误。触发之后,它会先尝试裁剪历史工具调用结果,不够的话再进行摘要压缩。
更细一点的是它和重试之间的配合:一次失败的 step 和一次失败的 turn 之间,只有当压缩或摘要动作确实让上下文往前推进了,系统才会开一个新的重试回合;如果没有实质性进展,原始的请求错误依然是最终结论,不会无意义地空转重试。什么情况下值得重试,什么情况下不值得,是被明确写进机制里的,不是一句“我们支持 retry”就能带过的。
Evaluation:dsh 没有直接回答,但留了地基
传统 LLM 应用的评估很简单:Input → Model → Output → Score。但复杂 Agent 的执行链路是 Task → Planning → Tool → Execution → Observation → Retry → Result,真正应该评估的是整个执行轨迹,而不是最终那段文本——Task Success、Tool Success、中间状态、成本、延迟,都是轨迹级别的信号。
这是一个行业层面越来越清晰的判断:Agent Evaluation 的对象,正在从“答案”变成“轨迹”。
不过要坦白说清楚一件事:目前抓取到的 dsh 核心包列表和文档里,并没有一个独立的 evaluation 子系统。Session Log 那条只追加的事件流,客观上为轨迹级评估提供了现成的数据基础——你想统计工具调用成功率、想复盘某一次失败的完整过程,理论上都能从日志里拿到,但这是“日志设计带来的可能性”,不等于“dsh 内置了评估功能”。这一点上,dsh 目前给的是地基,不是答案。
配置体系:为什么不把这些东西写死在代码里
回到前面讲的 Bundle/Profile/Patch 机制,本质上是在回答这个工程问题。因为 Harness 的核心目标之一,就是把执行逻辑和实验参数解耦——同一套 Harness,换一层 patch 就能装上不同的模型、不同的工具集、不同的执行环境,不需要碰底层代码一行。
从代码设计看几个关键工程思想
把前面几节的具体机制往上收一收,能提炼成几条原则:
插件优先,无特权核心。 不是先设计好一个固定内核再开几个扩展口子,而是从一开始就没有内核,所有子系统按同一套 Cordis 规则组装。
模型是可替换的一个组件,不是系统的中心。 ctx.llm 只是众多 Capability Seam 里的一个,和 ctx.fs、ctx.shell 地位相同。
能力和执行环境彻底解耦。 这是 Capability Seam 设计带来的直接结果——换底座,能力整体搬家。
用一条日志取代一整套状态管理。 Session Log 既是历史记录,也是恢复的依据,也是审计的证据,一件事解决了原本要好几套机制才能覆盖的需求。
评估在闭环之内留了口子,但没有交卷。 这是目前唯一一个“设计上支持、功能上未完成”的部分,值得持续关注它接下来怎么补。
和主流框架比,差在哪一句话
不做简单的功能对比表,从架构思想上比会更清楚:
维度ChatbotAgent Framework(LangGraph / CrewAI 等)DeepSeek Harness核心对话编排器/图引擎作为内核没有内核,一切皆插件Model 的地位系统核心核心组件众多 Capability Seam 里的一个Tool辅助功能重要模块基础设施,和文件系统、Shell 平级State一段对话历史各家自己的 checkpoint 机制统一收敛到一条只追加的 Session LogEvaluation输出评估Agent 级评估设计上支持轨迹级评估,功能未内置目标回答问题完成任务稳定完成任务,且全程可审计
Agent Framework 关注“怎么让模型调用工具”,Harness 更关注“怎么让整个任务可靠地跑完”。这是两个不同层次的野心。
真实世界的另一面
设计上的巧思讲了不少,得讲点真话。
官方自己承认接下来会有破坏性变更。 项目文档里明确写着,现阶段没有外部使用者,团队更愿意选对的基础设施而不是保兼容,该改名改名、该重构重构。这话说得坦诚,但也意味着现在往生产环境接,是要担风险的。
学习成本是双重的。 想真正玩明白 dsh,得先搞懂它底下那套叫 Cordis 的插件框架怎么运作,理解 Service/Event/Scope 这几个基本概念,然后才能理解 dsh 在这套框架上又搭了什么。这不是一层学习曲线,是两层叠在一起的学习曲线。
系统复杂度是实打实提高了的。 更多的 Loop、更多的 State、更细的事件分类,意味着 debug 成本会上升;长任务如果跑得久,Token 消耗、Context 大小、状态复杂度都会跟着涨。这些不是 dsh 独有的毛病,任何走向“插件化操作系统”这条路的项目,都要经历这样一段权衡。
如果自己做企业级 Agent,该从这里学什么
不建议照着这个项目抄,更值得抄的是这套分层方法:

五层里前四层,dsh 都给出了具体到能看代码的实现方式;最后一层评估,目前是留白,需要使用者自己在这条日志之上把轨迹级评估搭起来。这也是这套系统目前最诚实的地方——它没有假装什么都做完了。
写在最后
过去大家拼的是谁能接上最强的大模型。
后来开始拼 RAG、拼 Agent、拼 Tool Calling。
但当所有团队都能调用同一个模型之后,真正拉开差距的,就变成了模型之外的那一层。Context 决定模型知道什么,Tool 决定模型能做什么,而 Harness 决定模型能不能把事情真正做完。
所以 DeepSeek Harness 值得研究的地方,并不是“DeepSeek 又开源了一个项目”,而是它让我们重新看到了一件事:
LLM 时代的核心工程问题,正在从“如何调用模型”,转向“如何围绕模型构建一个可靠、可审计的执行系统”。