先说结论
DeepSeek Harness 值得 Agent 开发者认真看一遍。
它不是给 DeepSeek 模型套上一层聊天界面,也不只是把文件读写、终端和搜索工具拼进一个循环。它真正想解决的是一个更底层的问题:当 Agent 开始长时间运行、并发调用工具、接入外部能力,甚至需要在进程崩溃后继续工作时,怎样让整个系统仍然可组合、可追踪、可恢复。
从源码看,DeepSeek Harness 的回答可以概括为四句话:
- 一切能力都做成插件;
- 一切模型可见状态都写入会话日志;
- 工具执行必须经过统一的调度和策略流水线;
- 运行时能力通过稳定接口组合,而不是彼此硬编码。
这使它更像一个面向 Agent 的“运行底座”,而不只是一个现成应用。它尤其适合想研究 Agent 基础设施、搭建内部开发助手,或需要深度定制运行环境的团队。
本文的源码阅读基于官方仓库提交 47f9438。项目目前仍处于开发者预览阶段,官方明确提示后续会有不兼容变更,因此本文是一篇“值得试用和研究”的推荐,而不是“已经可以无脑替换生产系统”的采购建议。
Harness 到底是什么
在 Agent 语境里,模型只是负责推理和生成下一步动作的“大脑”。要让它真正完成开发任务,还需要一整套外围系统:
- 把历史消息、系统提示词和工具定义组装成模型请求;
- 解析模型返回的工具调用;
- 读写文件、运行命令、访问网页或调用 MCP 服务;
- 管理权限、沙箱、审批、超时和取消;
- 保存执行历史,并在中断后恢复;
- 支持 Skill、子 Agent、任务计划和不同交互界面。
承载这些工作的运行系统,就是 Harness。
一个最小 Agent 循环看起来并不复杂:把消息发给模型,模型要调用工具就执行工具,再把结果发回模型,直到模型给出最终回答。但真正进入工程环境后,困难很快出现:两个工具能否并行?写操作能否和读操作同时运行?用户中途取消时,已经启动的命令怎么办?工具成功执行但结果尚未来得及落盘时,重启后能否安全重试?插件热更新后,旧能力会不会残留?
DeepSeek Harness 的价值,就在于它没有回避这些“循环之外”的问题。
特点一:真正贯彻“一切皆插件”
官方架构文档写得很直接:模型适配器、工具注册表、会话日志,甚至 Agent Loop 本身都是插件;系统里没有一个必须打补丁才能扩展的特权内核。插件通过 Cordis 向共享上下文贡献服务、类型化事件和可逆副作用,卸载插件时,相应注册也会撤销。可参考官方架构说明。
这不是一句架构口号。源码中的注册 API 普遍返回 disposer,也就是与本次注册精确对应的注销函数。工具、Skill、模型适配器等能力都遵循相同的生命周期思路。
这种设计带来三个实际好处。
能力可以替换
文件系统、Shell、进程、模型提供方、持久化和子 Agent 都通过各自的 service seam 接入。所谓 seam,可以理解为一条约定好的插槽,它包含接口定义、能力提供方和能力消费者。
例如,把本地文件系统与进程提供方替换成远程沙箱实现后,依赖这些接口的 Bash、PTY 和 LSP 能力也可以随之迁移,而不必为每个工具分别维护一套远程版本。
配置本身可以组合
运行中的 dsh 是一棵有顺序的插件树。官方提供 web 和 headless 等 profile,开发者还可以叠加 bundle、用户 patch 和命令行 overlay。通过下面的命令,可以查看机器上最终生效的配置树:
dsh --profile web --dump-config
这对排查“某项能力到底由谁提供、配置为什么变成这样”很有帮助。
热替换更容易保持干净
可逆副作用意味着插件卸载时不只是删掉一个对象,还要撤销它注册的服务、事件监听器和工具。对于需要热更新配置、切换 Agent 预设或长期运行的宿主来说,这比散落在全局对象里的注册可靠得多。
特点二:把会话日志当作事实来源
DeepSeek Harness 最让我印象深刻的一条原则是:模型可见即已记录。
在它的设计里,模型下一次请求看到的历史,不是某个临时数组碰巧保存下来的结果,而是由仅追加的会话事件日志通过 deriveMessages() 投影出来。官方架构文档说明,fork、恢复、会话记录、遥测和持久化也都从这条事件流派生。
源码中的 SessionEventMap 把关键事实拆得很细,包括:
turn/start与turn/end;step/start与step/end;user/message;- 原始流式片段
assistant/chunk; - 组装后的
assistant/message; tool/call与tool/result;- 请求头和请求上下文。
这些事件必须是无损 JSON,序号保持连续。可以直接查看 SessionEventMap 的源码。
这套设计同时解决了几个常见痛点。
回放不是事后补做的日志功能
因为模型历史本来就来自事件日志,UI 回放、调试和恢复读取的是同一份事实,而不是另外维护一份“看起来差不多”的审计记录。原始流式 chunk 也被保留,所以界面可以还原流式输出,而不只是展示最终拼接文本。
模型请求和日志之间有不变量检查
Agent Loop 在发起请求前会检查:当前请求是否携带正确会话,messages 是否确实由日志推导而来,system、tools、model 等请求头是否与已记录快照一致。对应实现位于 invariant.ts。
它防止了一类很隐蔽的问题:模型实际看到了某段上下文,但日志中没有;程序重启或会话 fork 后,这段上下文就凭空消失,行为无法复现。
崩溃恢复对副作用保持谨慎
会话修复逻辑会扫描未闭合的轮次。如果模型已经发出工具调用,但进程崩溃前没有持久化结果,系统会补上一条确定性的错误结果,再补齐 step/end 和 turn/end。
更重要的是,它区分“工具尚未开始”和“工具可能已经执行,但结果未知”。后一种情况的恢复消息会明确要求:只对只读或幂等操作直接重试;可能产生副作用的操作,应先检查外部状态或询问用户,不能盲目重放。可参考 repair.ts。
对于会修改代码、发送请求或操作外部系统的 Agent,这种语义比“崩了就再跑一遍”可靠得多。
特点三:工具并发不是简单的 Promise.all
模型一次返回多个工具调用时,最粗糙的实现是全部并行,或者全部串行。前者容易打乱副作用顺序,后者又浪费只读任务的并行机会。
DeepSeek Harness 为工具定义了 parallel 和 exclusive 两种执行模式:
- 连续的并行调用进入有上限的滚动池;
- 独占调用是顺序屏障,必须等待前面的任务排空,并阻止后续调用抢跑;
- 真正重叠的只有工具主体,策略检查、持久结果和附加上下文仍按模型给出的顺序提交;
- 尚未启动的调用会在执行前重新分类,因此运行期间发生的注册表变化也能生效;
- 收到取消信号后不再补充新任务,但会等待已经启动的任务结算;未启动调用会获得合成错误结果,保持日志可回放。
实现集中在 tool-calls.ts。其中一个值得注意的细节是:只有工具的并发安全分类器明确返回 true,运行时才允许并行;未知工具、没有声明、分类异常等情况都会退回独占执行。
这是典型的 fail closed,也就是系统不确定时选择更保守的行为。对于文件修改、命令执行和外部写操作,这个默认值是合理的。
特点四:工具执行有统一的安全流水线
工具不是注册一个函数后就直接运行。每次调用都要经过统一流程:
tools/pre-execute
→ 单调守卫 guards
→ tools/execute
→ tools/post-execute
→ 工具自己的 finalizeContent
→ tools/result
这几层各有清晰职责:
pre-execute可以在执行前允许或拒绝调用;- guard 只能拒绝,不能强行放行,因此后注册的插件无法覆盖前面的安全拒绝;
execute是超时、重试和指标采集等包装策略的扩展点;post-execute可以检查或替换结果,并附加后续上下文;- 工具最终决定如何把规范结果转换成模型可见内容和 UI 展示数据。
工具还可以按 Agent 作用域隐藏、限制或替换。也就是说,同一个宿主里的不同 Agent 可以拥有不同工具集合,而不是共享一张无法隔离的全局表。
这套管线很适合企业内部场景:只读工具可以默认开放,写操作进入审批,敏感命令由沙箱策略拦截,审计插件再统一记录结果。各个策略不必侵入具体工具实现。
特点五:原生工具调用之外,还有 Code Mode
DeepSeek Harness 支持三种工具呈现模式:native、code 和 both。
在原生模式下,模型直接看到每个工具的 Function Calling schema。在 Code Mode 下,模型主要看到一个保留的 run_code 工具,以及根据当前可用工具自动生成的 TypeScript 或 Python SDK。模型可以写一小段程序,在程序中组合调用工具:
const matches = await tools.search({ query: "tool scheduler" })
const useful = matches.items.filter((item) => item.score > 0.8)
return useful.map((item) => ({
title: item.title,
url: item.url,
}))
程序里的每个 tools.* 调用并没有绕过安全系统,它仍然会重新进入完整的工具流水线,并复用并发安全分类和独占屏障。只有程序最终打印或返回的内容进入模型上下文,中间结果保留在执行局部。
这对“大量检索后筛选”“读取多份数据再聚合”一类任务很有吸引力。传统做法需要把每次工具结果都塞回上下文,让模型逐轮决定下一步;Code Mode 则允许模型用普通程序完成过滤、循环、异常处理和并行组合,减少无意义的中间信息。
不过官方也没有把它宣传成万能省 token 方案。文档明确说明,自动生成的 SDK 本身有固定上下文成本,不保证任何情况下都更省;中间值目前没有逐项字节上限,每次 run_code 也是全新状态,不是持久 REPL。实现和限制可参考工具运行时文档。
特点六:Agent Loop 很小,但边界很清楚
默认循环的核心仍然是熟悉的 ReAct 结构:
打开 turn
→ 领取输入并执行 pre-step
→ 写入 step/start 和用户消息
→ 从日志派生模型历史
→ 流式请求模型并记录 chunk
→ 组装 assistant/message
→ 执行工具调用
→ 写入 step/end
→ 如仍有任务则进入下一 step
关闭 turn
对应源码在 agent.ts。
值得学习的不是循环写得多花哨,而是每个阶段都留下了明确边界:持久事实进入 session event,运行中拦截使用 agent event,文件、工具和遥测等领域策略则使用各自的 capability event。
这样新增重试、压缩、审批、上下文注入或观测能力时,不需要不断向主循环里塞条件分支。核心循环保持可理解,复杂度被分配给有职责边界的插件。
特点七:工程质量标准很激进
DeepSeek Harness 的测试文档要求 packages/*/*/src 的生产文件达到逐文件 100% 覆盖率。除了单元测试,项目还区分真实 API 端到端测试、无密钥快照测试和 Chromium 浏览器快照。
它还有一条很实用的验证原则:不要只相信 Agent 自己报告“已经完成”,而要重新运行命令、读取文件或检查外部状态来验证世界是否真的发生了变化。
对于用户可见的非平凡改动,项目要求提供组装后的会话快照,检查用户输入、模型输出、工具调用、结果和附加上下文的完整序列。测试规范可参考官方 Testing 文档。
高覆盖率不等于没有 Bug,但这种测试对象很正确:不仅测某个函数返回值,还测插件经过真实 Loader 组装后是否生效、会话记录是否符合预期、浏览器界面是否真的渲染出来。
现阶段已经能组合哪些能力
从仓库的包结构和官方架构目录看,DeepSeek Harness 已经覆盖了一个通用 Agent 底座的大部分拼图:
- 多模型适配与流式输出;
- 文件系统、Shell、持久终端和 LSP;
- Web 搜索与抓取;
- Skill 注册与按需加载;
- MCP 工具桥接;
- 子 Agent、工作流、Todo、Plan 和 Goal;
- 上下文压缩、会话 fork 与恢复;
- 用户审批、沙箱、凭据和设置;
- Web 与 headless 两种产品入口。
这里最重要的不是功能数量,而是这些能力大多经由 provider、consumer 和事件扩展点接入。开发者可以只替换其中一层,而不必 fork 整个 Agent 产品。
例如 Skill 注册表并不知道内容来自本地文件、HTTP 还是嵌入式插件;MCP Client 把外部工具规范化为 mcp__<serverName>__<rawName> 后,再注册进同一套工具运行时,因此同样受到作用域、调度和策略流水线管理。
五分钟跑起来
如果只是体验 Web 版本,官方给出的方式很简单。安装 Node.js 后运行:
npx @deepseek-ai/dsh web
默认 Web UI 地址是:
http://127.0.0.1:3080
如果想从源码阅读和调试:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
建议先读 docs/architecture.zh.md,再按下面的顺序看源码:
packages/core/agent-loop/src/agent.ts:理解 turn 和 step;packages/core/session/src/types.ts:理解系统记录哪些事实;packages/core/agent-loop/src/tool-calls.ts:理解工具调度;packages/core/tools/src/index.ts:理解注册表、guard 与执行管线;packages/core/session/src/repair.ts:理解中断恢复;packages/bundle/base:理解这些插件怎样组装成可运行产品。
哪些开发者最值得尝试
我会优先向三类开发者推荐 DeepSeek Harness。
正在构建 Coding Agent 的团队
如果你的 Agent 已经不满足于一次问答,而是需要长时间运行、操作代码仓库、执行命令和调度多个工具,那么会话事实源、取消语义、工具并发和安全门禁迟早都要面对。这个仓库提供了很好的实现参考。
需要私有化和深度定制的团队
插件树与能力 seam 允许替换模型、存储、文件系统、进程和 UI。对需要接入内网工具、远程开发机、自有审批系统或审计设施的团队,这比只能在固定产品表面增加几个工具更有扩展空间。
想研究 Agent 工程化的开发者
它把很多容易被 Demo 忽略的问题写进了源码和测试:如何保证请求可重建、怎样处理中断后的未知副作用、并发结果怎样按模型顺序提交、插件卸载怎样撤销注册。这些部分比又一个 Prompt 模板更值得学习。
现在不该忽略的限制
推荐归推荐,当前版本并不适合在没有评估的情况下直接承担关键生产任务。
首先,它仍处于开发者预览阶段,官方明确预告会有破坏兼容性的变更。现在更适合做技术验证、插件开发和内部试点,升级时应固定版本并阅读变更记录。
其次,部分能力还处在清晰但有限的 MVP 状态:
- MCP Client 当前只桥接工具,不消费 MCP Resources 和 Prompts;
- Goal 负责同会话目标状态,但没有独立评估器,完成或阻塞仍由调用方决定;
- 用户审批目前主要是一次性决策,还没有持久的“始终允许”策略;
- Code Mode 的中间值没有逐项字节上限,也不会跨运行保留状态;
- 更复杂的跨进程子 Agent 协作和可靠消息投递仍有继续完善空间。
这些限制并不是从外部猜测得出的,而是官方各包 README 主动列出的已知边界。对开发者来说,这反而是加分项:知道系统不保证什么,才能做正确的生产设计。
为什么我仍然推荐它
Agent 产品很容易从一个漂亮 Demo 开始:模型能读文件、改代码、跑命令,看上去已经完成了大半。真正困难的部分往往在随后出现——能力越来越多,策略互相覆盖,工具并发产生竞态,会话无法复现,崩溃后不敢恢复,最后任何改动都要碰主循环。
DeepSeek Harness 的源码把这些问题放在了架构中心。它的插件化不是只为“方便加工具”,事件日志也不是只为“展示聊天记录”;二者共同构成了一套可组合、可审计和可恢复的运行模型。再加上保守的工具调度、单调安全守卫、Code Mode 以及相当严格的测试规范,它已经表现出一个长期工程项目应有的骨架。
所以我的建议是:如果你只想快速做一个调用两三个工具的对话 Demo,没有必要立刻引入如此完整的体系;但如果你准备认真构建 Agent 基础设施,DeepSeek Harness 非常值得现在就 clone 下来,先跑起来,再沿着会话、工具和插件三条主线读一遍源码。
它当前最有价值的身份,不是“已经定型的标准答案”,而是一个公开、完整,而且愿意把工程边界写清楚的 Agent Harness 实现。