8月13日晚,DeepSeek 开源了 DeepSeek Harness。
其实,从他们此前公开表示要进军 Agent 领域、并开始招聘 Agent 团队时,我就一直猜测,DeepSeek 迟早会推出一款对标 Claude Code 或 Codex 的产品,甚至连名字我都替它想好了,就叫 DeepCode。
没想到,率先亮相的竟然是这个 Harness。
其实我想要的是一个DeepCode,然后提供一个包月会员多好,我都懒得自己去折腾harness,之前的opeclaw,Hermes Agent,claude code,workbuddy等,每一个出来都去折腾了一番,好像也就那样了,没啥特别鲜明的地方
不过,作为一个技术人折腾才是常态,下面我们就来看看 DeepSeek Harness 到底是个什么。
关于 Agent Harness 业内早有说法:Agent = Model + Harness。
如今 DeepSeek 把 Harness 开源,再配上自家的模型,本质上已经构成了一个完整的 Agent 体系。
DeepSeek Harness 官方开源地址:https://github.com/deepseek-ai/deepseek-harness

如果你问 DeepSeek Harness 最鲜明的设计是什么?
如上图,在github开源项目首页上 标黑的几个大字 Everything is a plugin,万物皆插件。
我们都知道harness核心是,上下文管理,工具治理,Agent Loop等等这些内容,现在这些全变成插件了?
没错,现在模型接入是插件,文件系统是插件,沙箱和会话日志也是插件,连负责驱动 agent 运转的主循环也由插件提供。
DeepSeek Harness 并没有在一些harness具体任务处理上给出了一些新的范式,而是给了一种新的工程范式,通过万物皆插件的设计,可以把harness工程涉及的模块都设计插件。
我们研究DeepSeek Harness,主要是看的插件是如何运行的。
这篇文章我们先看 DeepSeek Harness 由哪些部分组成,再看插件是如何组装和运行的,随后跟着一条用户消息走完整个执行流程,最后在用一个任务来测试下效果如何。
DeepSeek Harness 架构总揽

上图是 dsh 从用户入口到 agent 核心的整体运行结构。系统分成 Node Host 和 Browser 两个运行区域,还有一个命令行入口。
Node Host 负责装载 agent 核心、能力 Provider、执行策略和本地状态,Browser 负责界面与交互,通过 Web Host 把操作送进 Node Host。
我们沿着途中的连线来看,Web 用户先进入 Browser 中的 UI,再经过 HTTP、API 和转发事件连接 Web Host,如果是命令行模式不启动 Browser,任务由 Headless runner 直接送入同一套 agent 核心。
两种入口共享模型、工具、会话与主循环,区别主要在界面和启动方式。替换模型或文件系统 Provider 时,入口都不需要任何变动。
dsh 用 Profile 和 Bundle 管理这些组合,Bundle 可以理解成一组已经搭配好的插件,Profile 决定这次启动选用哪几组。用户还可以在后面叠加自己的配置。最终结果交给 Cordis Loader,由它把配置变成正在运行的插件树。

dsh-base里装了什么
dsh-base 是 Web 和命令行共同使用的基础组合。里面有几十个插件,按用途看,我们把它可以归到下面几类。
| 功能 | 负责的事情 |
|---|---|
| 模型接入 | 连接不同模型,选择默认模型,处理重试和 token 统计 |
| 本地工具 | 提供文件、Shell、搜索和后台任务等能力 |
| agent 编排 | 驱动主循环,组织子 agent 和工作流,并提供计划模式 |
| 上下文管理 | 组装项目指令,加载技能,在内容过长时压缩上下文 |
| 权限与策略 | 处理审批、沙箱和执行超时 |
| 会话与配置 | 保存会话、附件、设置和凭据,并提供查询与观测能力 |
表中各部分通过服务依赖连接,不是按照配置文件里的先后顺序来启动。以文件工具为例,文件系统服务尚未登记时,tool-fs 保持等待,服务出现后,Cordis 才调用它的 apply。模型接入、会话和主循环也按各自声明的依赖启动。
Cordis 怎样托住万物皆插件
dsh 建在 Cordis 插件框架上,我们可以把Cordis看成一套管理依赖和生命周期的插件系统。
每一个插件都会申明自己需要哪些服务,也会说明自己提供什么。比如文件工具需要一个文件系统服务,如果这个服务还没准备好,文件工具就等待。服务出现后,Cordis 自动启动文件工具。服务被替换或卸载时,Cordis 会先清理依赖它的插件,等新的实现就绪,再重新激活它们。

Cordis 为每个插件创建一个 Fiber,用它管理这个插件的依赖状态和生命周期。依赖没有满足时,Fiber 保持 pending,不会调用插件的 apply。插件启动后,它注册的服务和监听器都会带有对应的清理函数,Fiber 负责保存这些函数。插件卸载或依赖被替换时,Cordis 先执行这些清理函数,撤销旧的注册,再等待依赖恢复并重新调用 apply。

这里文件系统只是一个例子,模型、工具注册表和 agent 主循环也采用相同思路。主循环虽然处在执行路径中央,在 Cordis 看来仍是一个普通插件。它也会等待所需服务,也可以在卸载时清理自己。
这就是连主循环都是插件的实际含义,系统没有一块永远不能替换的巨大核心,核心能力由一组有明确关系的插件共同组成。
插件之间主要有两种连接方式,长期使用的能力通过服务来连接,运行到某个时间点才需要介入的行为通过事件连接。

服务回答的是我现在依赖哪项能力。事件处理的是当这件事发生时,谁想参与。
服务连接长期存在的能力调用。tool-fs 读写文件时调用 ctx.fs,不需要知道当前 Provider 是使用本地磁盘还是 E2B。事件则是用于主流程中的特定检查点,模型请求发出前,压缩插件可以检查上下文长度,工具执行前,策略插件可以返回 allow、ask 或 deny。主循环负责后续步骤,监听器处理收到的一些事件。
一个事件可能同时被多个插件监听。Cordis 需要决定监听器是一起运行还是逐个运行,也要决定是否等待异步任务。某个监听器给出结果后,Cordis 还要判断要不要继续调用后面的监听器,Cordis 因此提供五种事件分发模式。
| 模式 | 工作方式 | 常见用途 |
|---|---|---|
emit |
发出通知,不等待异步结果 | 状态变化通知 |
bail |
同步逐个询问,拿到有效结果后停止 | 客户端同步决策 |
waterfall |
按顺序传递,也允许中途截住 | 包裹模型请求或工具执行 |
parallel |
同时执行并等待全部完成 | 互不依赖的异步任务 |
serial |
异步逐个询问,拿到有效结果后停止 | 按顺序作出决定 |
从ctx.fs看 Capability Seam 的三个角色
dsh 把一项可替换的能力分成三个角色。
- Service Definition 规定大家可以调用什么
- Provider 完成实际工作
- Consumer 把这项能力交给上层使用。
三个角色合在一起,dsh把它称为 Capability Seam。
我们用文件系统来举例。FileSystem 规定读取、写入和列出目录等操作。本地文件系统、带沙箱限制的文件系统和 E2B 远程文件系统都可以提供这些操作。
tool-fs 再把它们包装成模型能使用的文件工具。

图中间的 FsTarget 是 Provider 交给 Consumer 的文件引用。tool-fs 调用 resolve() 得到它,随后只会把它传给 readText()、writeText() 等接口。怎样用这个引用找到实际文件,始终由当前 Provider 负责。本地实现可以关联到 realpath,E2B 实现可以关联到远程绝对路径,这些差别都留在 Provider 内部。
这样一来,更换文件系统只会改变 ctx.fs 后面的实现。tool-fs 仍按 FileSystem 的接口工作,模型看到的也仍是 read、edit 和 write。文件工具不必处理运行环境,更不必为每种环境各写一套调用路径。
从文件系统到其他可替换能力
上面文件系统是比较容易看懂的一个例子,其他的插件工作原理也是一样的,模型调用也有自己的 Service Definition,不同的模型厂商有不同的 Provider 来处理各家的请求格式不同。
一项可替换的能力怎样接入 dsh 就清楚了。
- Service Definition 先规定调用方法和共用的数据类型,
- Provider 负责本地环境、远程环境或第三方协议带来的差异。
- Consumer 只调用这项服务,再把结果交给模型或其他上层功能。
配置决定实际挂载哪个 Provider,更换实现时,Consumer 和主循环都不用跟着修改。
一条消息怎样让 agent 跑起来
前面两节讲的是插件怎样组成系统。等用户发来一句任务,这些插件才开始一起工作。下面就跟着一条“修复这个 bug,并运行测试”的消息走一遍。
消息先进入 Inbox,也就是 agent 的待处理队列。主循环取出消息,写下 turn/start,这项任务的一个 turn 就开始了。从接下任务到确认没有工作需要继续,都属于这个 turn。
我们先理清2个概念:
- step(步)= 一次模型请求加它调用的工具
- turn(回合)由零个或多个 step 组成。Agent 开始处理一条消息时,turn 开启,当前回合中的消息全部处理完,并且不需要继续请求模型时,turn 结束。
模型通常不能一次就把事情做完。它会先看问题,决定读取哪些文件,拿到文件内容后,它还要判断怎样修改,修改完再运行测试。每次请求模型,并执行这次回答中包含的工具调用,算一个 step,一个 turn 因此可以包含多个 step。

每个 step 开始前,agent/pre-step 会把即将交给模型的输入交给插件检查。上下文压缩插件可以在这里缩短过长的历史,其他插件也可以补充、改写或拒绝这次输入。检查通过后,主循环请求模型并执行工具。工具结果还需要模型处理时,主循环再开一个 step,模型已经给出最终回答时,这个 turn 就接近结束。
主循环结束 turn 之前会触发 agent/turn-stopping。插件可以在这个时刻补入一条消息,让模型再处理一步。没有插件要求继续,主循环才写下 turn/end。
执行中又收到消息怎么办
Inbox 把新消息分成两种。用户补充一句“先别改配置”,它和正在执行的任务有关,会在下一个 step 交给模型,当前 turn 继续。用户另发一个新问题时,这条消息留到当前 turn 结束,再开启新的 turn。
这两种去向在代码里分别叫 next-step 和 next-turn。前者续接当前任务,后者排队等待下一项任务。主循环处理完 Inbox 中的消息后回到空闲状态。
看到这里你会有一个疑问,这个消息是怎么来判断,它是应该进入 next-step 还是 next-turn
dsh在这里没有使用语义判断,而是发送消息的一方会同时指定它的处理时机,这里有三种方式。
Agent 用 followup()、steer() 和 inject() 接收这三类消息。三个方法最后都调用 send() 把消息写进 Inbox,差别在于消息排到 next-turn 还是 next-step。
followup() 接收一条普通的新消息。它把消息放进 next-turn 。agent 空闲时,这条消息会启动一个新 turn,当前 turn 还在运行时,它留在队列里,等这一轮结束以后再单独开启下一轮。
steer() 处理执行途中的补充要求。它把消息放进 next-step,当前 turn 正在运行时,会在下一个 step 开始前取走这条消息,让模型在同一个 turn 里接着处理,agent 空闲时,steer() 也能启动一个 turn。
inject() 供插件和后台任务补充模型需要看到的上下文。它同样写入 next-step,agent 正在运行时,这份上下文等到后面的 step 再被取走,agent 空闲时,它会一直留在 Inbox,直到后续的 followup() 或 steer() 消息到来。
从 turn/start 到 turn/end,用户消息、模型回答和工具结果都会写入 Session 日志。
模型看到的内容都要留在 Session 日志里
dsh 有一条很重要的原则。
凡是进入模型请求的内容,都必须能从 Session 日志重建。
用户说过什么,模型回答过什么,工具返回了什么,使用了哪套系统提示和工具定义,这些都会留下记录。下一次请求所需的模型历史,由这些记录整理出来。

图里的几层处理可以用一句话理解。日志保存发生过的事,投影把这些事件整理成当前对话,缓存避免每次都从头计算,请求发出前还会检查整理结果是否与日志一致。
这份日志后面还有几个用处。会话中断了,系统可以获取历史日志,继续完成任务。想从某个位置开一条新分支,需要等待当前 turn 完整结束,再把到这个位置为止的记录复制过去。界面里的回放同样来自这些记录,系统不用另外保存一份对话。
日志采用追加方式,旧记录通常不会被直接改写。上下文压缩会生成新的替代内容,让后续模型看到更短的历史,同时保留过程所需的记录。
空 assistant 不会写入历史
有时模型达到输出上限,系统仍要记录 token 用量,却没有有效的回答内容。这类空消息会留在日志里,但不会进入下一次模型请求的对话历史。日志负责保存事实,模型历史只选择后续请求需要看到的部分。
工具调用中的策略判断与执行
agent 的价值很大一部分来自工具,当然风险也一般来自于这里。读文件和搜索网页通常比较温和,修改代码、运行命令和访问远程系统则需要更谨慎的控制。
dsh 的工具注册表先运行通用策略,再把获准的调用交给工具及其依赖的能力实现。结果随后进入统一的后处理与记录流程。

模型发起工具调用后,tools/pre-execute 监听器可以放行、询问用户或拒绝。需要审批却没有可用的审批服务,或者审批没有通过,注册表都会生成拒绝结果。
随后运行的单调守卫只能拒绝或弃权,因此已经形成的守卫拒绝不会被后面的宽松策略改回放行。
这张图画的是工具注册表的通用策略扩展点。具体限制也可以由工具或能力 Provider 执行。当前 base 组合中的文件与 Shell 沙箱就在各自的能力层生效,模型请求更宽的执行模式时,相关工具会在执行前调用 ctx.approval,未获批准便不执行。
通过检查以后,调用才进入工具本身。

tools/execute 可以统一挂入超时、重试和指标统计。工具返回以后,后处理插件能够检查或整理结果。最终内容固定后,注册表先发出只读的 tools/result 实时通知,agent-loop 随后把 tool/result 事件写入 Session 日志,再交给模型继续判断。
这样的分层让各类工具共享一套安全和观测流程。Bash、网页搜索和子 agent 仍然处理各自的业务,权限、超时和结果记录则不用在每个工具里重复实现。
实际测试
我最近正在开发一个应用程序,刚好有一个bug,我们就拿他来测试,主要的问题是一个页面初始化的时候,比较卡顿,我看了下主要原因是每一个接口的耗时都到了2到4秒,差不多有10个接口的样子,理论上最好的解法是,找到耗时长的原因,并且把能够合并的接口进行合并
我们给到一个统一的提示词,测试Codex,在DeepSeek Harness中使用v4-flash和v4-pro这两个模型
本次测试的源码基于8月13日晚的代码,测试时间8月14日上午
我们给到他们一个统一的提示词
当前项目中 创作台列表卡片界面,点击一个卡片进入创作空间的时候,接口响应比较慢,而且请求的接口比较多,接口耗时普遍在3秒左右,先排查一下为什么会这么慢,找到原因并修复
我们先看看CodeX的表现


测试耗时9分钟, 测试之前的周额度有70%,测试编程了68%,也就是说周额度消耗2%,解决方案和我的预期差不多,增加了一个接口,一次返回之前多个接口返回的信息,基本达到了预期,不过这个接口还需要1.3秒左右,我还是觉得有点差。
再来看看 deepseek-v4-flash 在Deepseek harness中的表现


测试耗时20分钟,总体来说效果还行,它没有合并接口,但是把所有接口的耗时都处理到了800ms一下
再来看看 deepseek-v4-pro

这个v4-pro的效果测试下来,居然没有v4-falsh效果好,这个是我没有想到的,这个测试用了2轮,第一轮结束时,我访问了一下页面,下面是截图返回的效果

感觉这个接口,根本没有什么变化,只不过它在第一轮结束后,跟我说,可以把多个接口合并,用一个详情接口返回需要的数据,我觉得这个和我预期差不多,就让它继续做了。下图是做完之后的结果,什么变化也没有,搞了20分钟,没有任何结果产出

下面是本次测试的消耗,v4-falsh用了0.82元,pro用了1.33元
我是没想到pro的效果,居然比flash的效果差,后面有一些消息说 8与13号发布的pro版本发错了,不知道是不是真的。
| 模型 / Harness | 测试耗时 | 优化方案 | 优化后效果 | 本次消耗 | 最终评价 |
|---|---|---|---|---|---|
| Codex | 约 9 分钟 | 将原来多个接口合并,新增一个详情接口,一次返回页面所需数据 | 接口数量明显减少,但新接口仍需约 1.3s | 周额度约 2% | 方案最符合预期
,整体完成度最高,但单接口性能还有优化空间 |
| DeepSeek V4 Flash + DeepSeek Harness | 约 20 分钟 | 没有合并接口,而是分别优化现有接口 | 原本普遍约 3s 的接口,基本都降低到 800ms 以下 | 0.82 元 | 实际效果不错
,虽然方案和预期不同,但性能改善比较明显 |
| DeepSeek V4 Pro + DeepSeek Harness | 两轮,约 20 分钟 | 第一轮提出合并多个接口、增加详情接口;第二轮继续实施 | 第一轮接口性能基本没有明显变化;二轮执行约 20 分钟后仍没有有效结果产出 | 1.33 元 | 表现低于预期
,不仅没有超过 Flash,反而耗时更长、成本更高,最终结果也最差 |
写在最后
DeepSeek Harness 最有意思的地方肯定是它的万物皆插件,它对Agent 的运行时做了可组合的架构,也许发展到后面,Agent能够根据任务类型,自主的选择合适的插件来运行。
同时它也让 Agent 跑起来这件事更加复杂。功能全都拆成插件以后,首先需要选对插件和配置,接着还要处理它们之间的依赖。真出了问题,排查日志也是比较烦杂,好在 它现在已经提供了运行轨迹,比较方便排查Agent的执行记录,想把整套东西稳定地跑起来,也没有看上去那么轻松。
dsh 目前仍处于 developer preview,项目明确提醒后续可能出现不兼容变化。它更适合作为一份正在演进的工程样本。对于正在设计 agent 的团队,它提供了一些参考,但不要急于把它这个基础架构引入项目中。
本文由作者@叶小钗,授权发布于平台,未经许可禁止转载。
