## 先说结论

DeepSeek Harness 值得 Agent 开发者认真看一遍。

它不是给 DeepSeek 模型套上一层聊天界面，也不只是把文件读写、终端和搜索工具拼进一个循环。它真正想解决的是一个更底层的问题：当 Agent 开始长时间运行、并发调用工具、接入外部能力，甚至需要在进程崩溃后继续工作时，怎样让整个系统仍然可组合、可追踪、可恢复。

从源码看，DeepSeek Harness 的回答可以概括为四句话：

1. 一切能力都做成插件；
2. 一切模型可见状态都写入会话日志；
3. 工具执行必须经过统一的调度和策略流水线；
4. 运行时能力通过稳定接口组合，而不是彼此硬编码。

这使它更像一个面向 Agent 的“运行底座”，而不只是一个现成应用。它尤其适合想研究 Agent 基础设施、搭建内部开发助手，或需要深度定制运行环境的团队。

本文的源码阅读基于官方仓库提交 [`47f9438`](https://github.com/deepseek-ai/deepseek-harness/tree/47f943859bef60e4160492346772ded9b24f765a)。项目目前仍处于开发者预览阶段，官方明确提示后续会有不兼容变更，因此本文是一篇“值得试用和研究”的推荐，而不是“已经可以无脑替换生产系统”的采购建议。

## Harness 到底是什么

在 Agent 语境里，模型只是负责推理和生成下一步动作的“大脑”。要让它真正完成开发任务，还需要一整套外围系统：

- 把历史消息、系统提示词和工具定义组装成模型请求；
- 解析模型返回的工具调用；
- 读写文件、运行命令、访问网页或调用 MCP 服务；
- 管理权限、沙箱、审批、超时和取消；
- 保存执行历史，并在中断后恢复；
- 支持 Skill、子 Agent、任务计划和不同交互界面。

承载这些工作的运行系统，就是 Harness。

一个最小 Agent 循环看起来并不复杂：把消息发给模型，模型要调用工具就执行工具，再把结果发回模型，直到模型给出最终回答。但真正进入工程环境后，困难很快出现：两个工具能否并行？写操作能否和读操作同时运行？用户中途取消时，已经启动的命令怎么办？工具成功执行但结果尚未来得及落盘时，重启后能否安全重试？插件热更新后，旧能力会不会残留？

DeepSeek Harness 的价值，就在于它没有回避这些“循环之外”的问题。

## 特点一：真正贯彻“一切皆插件”

官方架构文档写得很直接：模型适配器、工具注册表、会话日志，甚至 Agent Loop 本身都是插件；系统里没有一个必须打补丁才能扩展的特权内核。插件通过 Cordis 向共享上下文贡献服务、类型化事件和可逆副作用，卸载插件时，相应注册也会撤销。可参考[官方架构说明](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/architecture.zh.md#cordis)。

这不是一句架构口号。源码中的注册 API 普遍返回 disposer，也就是与本次注册精确对应的注销函数。工具、Skill、模型适配器等能力都遵循相同的生命周期思路。

这种设计带来三个实际好处。

### 能力可以替换

文件系统、Shell、进程、模型提供方、持久化和子 Agent 都通过各自的 service seam 接入。所谓 seam，可以理解为一条约定好的插槽，它包含接口定义、能力提供方和能力消费者。

例如，把本地文件系统与进程提供方替换成远程沙箱实现后，依赖这些接口的 Bash、PTY 和 LSP 能力也可以随之迁移，而不必为每个工具分别维护一套远程版本。

### 配置本身可以组合

运行中的 `dsh` 是一棵有顺序的插件树。官方提供 `web` 和 `headless` 等 profile，开发者还可以叠加 bundle、用户 patch 和命令行 overlay。通过下面的命令，可以查看机器上最终生效的配置树：

```bash
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` 的源码](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/session/src/types.ts#L230-L309)。

这套设计同时解决了几个常见痛点。

### 回放不是事后补做的日志功能

因为模型历史本来就来自事件日志，UI 回放、调试和恢复读取的是同一份事实，而不是另外维护一份“看起来差不多”的审计记录。原始流式 chunk 也被保留，所以界面可以还原流式输出，而不只是展示最终拼接文本。

### 模型请求和日志之间有不变量检查

Agent Loop 在发起请求前会检查：当前请求是否携带正确会话，messages 是否确实由日志推导而来，system、tools、model 等请求头是否与已记录快照一致。对应实现位于 [`invariant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/agent-loop/src/invariant.ts)。

它防止了一类很隐蔽的问题：模型实际看到了某段上下文，但日志中没有；程序重启或会话 fork 后，这段上下文就凭空消失，行为无法复现。

### 崩溃恢复对副作用保持谨慎

会话修复逻辑会扫描未闭合的轮次。如果模型已经发出工具调用，但进程崩溃前没有持久化结果，系统会补上一条确定性的错误结果，再补齐 `step/end` 和 `turn/end`。

更重要的是，它区分“工具尚未开始”和“工具可能已经执行，但结果未知”。后一种情况的恢复消息会明确要求：只对只读或幂等操作直接重试；可能产生副作用的操作，应先检查外部状态或询问用户，不能盲目重放。可参考 [`repair.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/session/src/repair.ts#L12-L131)。

对于会修改代码、发送请求或操作外部系统的 Agent，这种语义比“崩了就再跑一遍”可靠得多。

## 特点三：工具并发不是简单的 Promise.all

模型一次返回多个工具调用时，最粗糙的实现是全部并行，或者全部串行。前者容易打乱副作用顺序，后者又浪费只读任务的并行机会。

DeepSeek Harness 为工具定义了 `parallel` 和 `exclusive` 两种执行模式：

- 连续的并行调用进入有上限的滚动池；
- 独占调用是顺序屏障，必须等待前面的任务排空，并阻止后续调用抢跑；
- 真正重叠的只有工具主体，策略检查、持久结果和附加上下文仍按模型给出的顺序提交；
- 尚未启动的调用会在执行前重新分类，因此运行期间发生的注册表变化也能生效；
- 收到取消信号后不再补充新任务，但会等待已经启动的任务结算；未启动调用会获得合成错误结果，保持日志可回放。

实现集中在 [`tool-calls.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/agent-loop/src/tool-calls.ts#L1-L258)。其中一个值得注意的细节是：只有工具的并发安全分类器明确返回 `true`，运行时才允许并行；未知工具、没有声明、分类异常等情况都会退回独占执行。

这是典型的 fail closed，也就是系统不确定时选择更保守的行为。对于文件修改、命令执行和外部写操作，这个默认值是合理的。

## 特点四：工具执行有统一的安全流水线

工具不是注册一个函数后就直接运行。每次调用都要经过统一流程：

```text
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。模型可以写一小段程序，在程序中组合调用工具：

```typescript
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。实现和限制可参考[工具运行时文档](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/tools/README.zh.md#code-mode)。

## 特点六：Agent Loop 很小，但边界很清楚

默认循环的核心仍然是熟悉的 ReAct 结构：

```text
打开 turn
  → 领取输入并执行 pre-step
  → 写入 step/start 和用户消息
  → 从日志派生模型历史
  → 流式请求模型并记录 chunk
  → 组装 assistant/message
  → 执行工具调用
  → 写入 step/end
  → 如仍有任务则进入下一 step
关闭 turn
```

对应源码在 [`agent.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/agent-loop/src/agent.ts#L246-L400)。

值得学习的不是循环写得多花哨，而是每个阶段都留下了明确边界：持久事实进入 session event，运行中拦截使用 agent event，文件、工具和遥测等领域策略则使用各自的 capability event。

这样新增重试、压缩、审批、上下文注入或观测能力时，不需要不断向主循环里塞条件分支。核心循环保持可理解，复杂度被分配给有职责边界的插件。

## 特点七：工程质量标准很激进

DeepSeek Harness 的测试文档要求 `packages/*/*/src` 的生产文件达到逐文件 100% 覆盖率。除了单元测试，项目还区分真实 API 端到端测试、无密钥快照测试和 Chromium 浏览器快照。

它还有一条很实用的验证原则：不要只相信 Agent 自己报告“已经完成”，而要重新运行命令、读取文件或检查外部状态来验证世界是否真的发生了变化。

对于用户可见的非平凡改动，项目要求提供组装后的会话快照，检查用户输入、模型输出、工具调用、结果和附加上下文的完整序列。测试规范可参考[官方 Testing 文档](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/testing.zh.md)。

高覆盖率不等于没有 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 后运行：

```bash
npx @deepseek-ai/dsh web
```

默认 Web UI 地址是：

```text
http://127.0.0.1:3080
```

如果想从源码阅读和调试：

```bash
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`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/architecture.zh.md)，再按下面的顺序看源码：

1. `packages/core/agent-loop/src/agent.ts`：理解 turn 和 step；
2. `packages/core/session/src/types.ts`：理解系统记录哪些事实；
3. `packages/core/agent-loop/src/tool-calls.ts`：理解工具调度；
4. `packages/core/tools/src/index.ts`：理解注册表、guard 与执行管线；
5. `packages/core/session/src/repair.ts`：理解中断恢复；
6. `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 实现。

项目地址：[deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)
