← 返回AI变现
🌐 其他

5 分钟看懂 DeepSeek Harness

来源:掘金 · 发布于 2026-08-15 20:31:34
5 分钟看懂 DeepSeek Harness 引言 如果你拆开过任何一个 Agent 工具的源码——Claude Code、Codex、OpenClaw——你会发现一个共同模式:有一个核心循环是硬编

5 分钟看懂 DeepSeek Harness

两万五千个小时 2026-08-15 0 阅读15分钟

5 分钟看懂 DeepSeek Harness

「深入理解 DeepSeek Harness」系列第 0 篇——整个系列的地图

📎 仓库:github.com/deepseek-ai…。本文基于撰稿时的源码撰写,代码示例为帮助理解而做了简化,请以实际仓库为准。

注:Mermaid 图表在掘金等博客平台不渲染,发布版会替换为预渲染图片。

引言

如果你拆开过任何一个 Agent 工具的源码——Claude Code、Codex、OpenClaw——你会发现一个共同模式:有一个核心循环是硬编码的,你不能换;有一组工具是内置的,你不能删;有一条 LLM 调用链路是写死的,你不能往里面插东西。想换一个上下文压缩策略?fork 整个框架。想加一个自定义的权限检查?改源码。

DeepSeek Harness 的回答不同:没有任何一层应该是特权核心。

DeepSeek Harness 的 AGENTS.md 开篇一句话定义了整个项目的哲学:

DeepSeek Harness is a plugin-based agent harness on vendored Cordis: everything is a plugin.

Agent Loop 是插件,System Prompt 是插件,LLM 是插件,Tools 是插件,Session 是插件——连配置加载器本身也是插件。这不是一句口号,而是你在源码里能看到、在配置里能改的事实。

更重要的是,DeepSeek Harness 不和 Claude Code、Codex 竞争。它的子 Agent 系统有 subagent-claude-code 和 subagent-codex 两个 Provider——你可以让 DeepSeek Harness 的 Agent 通过 subagent 工具调用 Claude Code 或 Codex 来完成子任务。它们在 DeepSeek Harness 里只是不同的 Provider。

这篇文章是整个系列的地图。读完之后,你会知道 DeepSeek Harness 由哪些部分组成、它们怎么协作、以及每个设计决策背后解决了什么问题。

阅读门槛

前置知识:需要 TypeScript 基础(能读懂 abstract class、generics、declaration merging),了解 ReAct Agent 基本概念(LLM 交替推理与行动的循环模式)。

适合:想要阅读 DeepSeek Harness 源码、做 Agent 框架二次开发的开发者。

不适合:零基础想学习大模型 Agent 入门的读者——建议先了解 ReAct 模式和 TypeScript 插件架构再回来。


一图总览

先放一张整体框架图,后面八个模块都在这张图里:

graph TD
    classDef invisible fill:none,stroke:none,color:none

    U([用户消息]) --> IN[Inbox 输入队列]

    subgraph CORE[八个核心模块]
        direction TB
        IN --> AL[Agent Loop Turn-Step 循环]
        AL --> SP[System Prompt 上下文拼装]
        AL --> TL[Tools pre-execute-post]
        AL --> LLM[LLM 模型适配]
        TL --> CAP[Shell FS 子进程等能力]
        dummy_core[ ]:::invisible
    end

    CMP[Compaction 上下文压缩] -.->|监听 pre-step| AL

    SE[Session 追加式事件日志]
    AL -.->|turn-step-chunk-tool 事件| SE
    IN -.->|inbox-spliced| SE
    CMP -.->|compaction-start-end| SE

    subgraph BASE[Cordis 插件框架 - 地基]
        direction TB
        B1[&#34;依赖注入<br/>事件总线<br/>副作用清理&#34;]
        dummy_base[ ]:::invisible
    end

    dummy_core -.->|构建于| dummy_base

图 1:一条消息从 Inbox 进入,Agent Loop 驱动循环,System Prompt / Tools / LLM 各司其职,Compaction 在模型调用前兜底,Session 沉底记录一切。实线 = 调用,虚线 = 事件 / 日志写入。注意日志的写入方是 Agent Loop(每步、每个流式 chunk、每个工具调用)、Inbox(队列变更)和 Compaction(压缩标记)——LLM 和工具模块本身不写日志,它们产生的事件由循环负责记录。

读图之前,只需要认识一件事:这些模块全都建在 Cordis 上——一个 TypeScript 插件框架(最初为聊天机器人 Koishi 设计)。DeepSeek Harness 把它的源码直接 vendor 进仓库而不是用 npm 安装,因为要在里面打 Agent 特有的补丁。你只需要记住 Cordis 的一个动作:一个类继承 Service 并在构造函数里调用 super(ctx, 'key'),它就自动注册到 ctx.key 上,任何插件都能通过 ctx.key 使用它。注册即挂载,卸载即清理。 下面八个模块全是这么挂上去的。


八个核心模块

① Inbox —— 消息的门口

所有进入 Agent 的消息,第一站都是 Inbox——一个排队的地方。为什么要排队?因为"消息什么时候到"和"Agent 什么时候有空"是两码事。你在 Agent 忙得不可开交的时候发一条消息,它不会丢,也不会打断当前的工作,只是安静地排在队里,等循环有空了再取走。

更有意思的是插话。你在 Agent 干活干到一半,补一句"顺便把测试也跑了"——这条消息会排进一个更短的队列(next-step),当前这一步一完成就被取走。就像餐厅里加单:不用重新排队,也不会打乱厨房的顺序。

排队的可靠性是隐藏的亮点:队列的每一次变更都写进会话日志(agent/inbox/spliced 事件)。配合持久化插件把日志写到磁盘,Agent 崩溃了、进程重启了,队列从日志里重放恢复——你发出的消息一条都不会丢。这一点很多"能用"的 Agent 工具都做不到。

完整的双队列机制、三种注入方式(followup / steer / inject)见 001:Agent Loop 执行引擎。

② Agent Loop —— 执行循环

八个模块里,只有 Agent Loop 是"在动"的——其余模块都在等它来调用。它实现的模式你大概率听过:ReAct,让模型交替"推理"和"行动",直到任务完成。

它用两层循环组织这件事。外层是 Turn:一次完整对话,从用户输入到 Agent 交回控制权。内层是 Step:Turn 内部的一次"调用模型 → 执行工具 → 把结果喂回模型"。你对它说"帮我重构这个函数并跑一下测试",它可能先读文件(Step 1)、改代码(Step 2)、跑测试(Step 3),直到某一步模型不再要求工具,Turn 才结束。

关键不在循环本身,在循环的四个决策点——开始前、请求前、出错时、结束前——全部是事件,不是写死的逻辑。上下文压缩挂在"开始前"(pre-step 事件):压力高了先压缩历史再放行;权限插件也能在这里直接拒绝,整个 Turn 以 blocked 收场,一个模型调用都不花。

另外,循环的每一步都落进日志:turn/start、step/start、每个流式 chunk、step/end、turn/end——有头必有尾,中断也不留半个 Step。这就是为什么后面 Session 那一节能成立。

完整的 Phase 状态机、唤醒与锁存、工具并行调度,见 001:Agent Loop 执行引擎。

③ System Prompt —— 上下文拼装

系统提示词不是一段写死的字符串,是拼出来的。

传统框架里,加一个工具就得去改提示词模板——工具列表、身份、规则全揉在一段字符串里,谁改谁冲突。DeepSeek Harness 把提示词拆成一个个 Section:工具列表是一个 Section、用户身份是一个 Section、环境信息是一个 Section、记忆文件也是一个 Section。每个插件注册自己的 Section、声明优先级,组装时按优先级拼起来。

好处立竿见影:加一个 bash 工具,不需要碰任何人的提示词——tool-bash 插件注册时带上自己的 Section 就行,自动出现在正确位置,互不覆盖。插件之间无法直接改对方的提示词,这个组装点就是它们的"公共黑板"。

组装过程本身也是事件(system-prompt/assemble)——你还能在最后一刻注入额外上下文。详见 002:System Prompt 的动态组装。

④ LLM —— 模型适配

换一个模型供应商,要改多少代码?DeepSeek Harness 的答案是:一行配置。

LLM 模块是一个适配器注册表:llm-deepseek、llm-pi-ai、llm-replay 都是注册进去的适配器,调用方只跟 ctx.llm 打交道,不知道底层是谁。模型调用本身也是事件(llm/stream)——想切路由、注入重试、甚至整体替换流,监听这个事件就行。

配置层面,cordis.patch.yml 里把 llm-deepseek 换成另一个 Provider,整个系统照跑。这是"能力接缝"最直观的体现,也是第 ⑧ 节要展开的模式。详见 003:LLM 适配层。

⑤ Tools —— 工具管道

模型说"我要调用工具",然后呢?

朴素做法是找到工具函数直接执行。DeepSeek Harness 把"执行一个工具"拆成三段瀑布:pre-execute(权限、参数校验)→ execute(超时、重试)→ post-execute(结果转换)。每一段都是事件,任何插件都能插进来;不调 next() 就可以短路——比如权限插件在 pre-execute 拒绝,工具根本不会执行。

段与段之间的"接口"是公开的,所以两个插件监听同一段也不打架:compaction-tool-result-pruner 把超过 8192 字符的工具输出截成头尾,spill-policy 把超过 50KB 的输出换成临时文件引用——各自 next() 下去,互不知道对方存在。

于是"加权限检查""加超时""加大输出截断"这些需求,全部变成"挂一个插件",工具注册代码一行不改。详见 004:工具系统设计。

⑥ Session —— 事件日志

这是全篇最值得停下来想的一节。

Session 不是数据库,是一个追加式事件日志:用户消息、模型回复的每一个流式 chunk、工具调用、工具结果——全部是不可变事件,按顺序追加。项目里有一条硬约束:

模型能看到的,日志里必须有。 任何进入模型请求的内容,都必须能从日志重建——运行时还有断言在检查这条不变量。

它带来一个很爽的推论:给一份日志,就能完整重放一次对话,包括逐 token 的打字效果。持久化(JSONL / SQLite)只是监听 session/event 把事件写盘的插件;不装它,内存里的 session 照常工作,只是重启后丢失。

类比:别的框架存的是"聊天记录的截图",DeepSeek Harness 存的是"录像带"——截图只能看结果,录像带能重放全过程。详见 005:Session 事件日志。

⑦ Compaction —— 长对话压缩

长对话迟早撑爆上下文窗口。DeepSeek Harness 的解法不是"删历史",是"写摘要"。

Compaction 挂在 Agent Loop 的 pre-step 上:每次模型调用前检查上下文压力,压力高了就把一段历史范围替换成一个摘要节点,替换本身也写进日志(compaction/start → compaction/end 一对标记,防止并发压缩)。

三个入口:自动(压力或溢出时触发)、手动(用户执行 /compact)、指定范围(强制压缩某段历史)。默认实现用 LLM 生成摘要;不满意?写一个实现同一接口的新 Provider 挂上去——压缩策略也是插件。详见 006:上下文压缩。

⑧ Capability Seam —— 能力接缝

把 ①-⑦ 串起来,会发现一个贯穿始终的模式:定义、实现、使用三者分离。

以 Shell 为例:ShellExecutor 抽象类定义"能执行命令"这个能力(挂在 ctx.shell 上);bash-sandbox、pwsh-local 是它的实现;tool-bash 是使用者,只调 ctx.shell.execute(),不知道底层是谁。切换实现只需改配置——比如 Windows 上自动禁用 bash、换 pwsh。

这个模式覆盖了整个系统:LLM、文件系统、子进程、沙箱、持久终端、凭证、子 Agent……十几条 Seam。最有意思的是子 Agent 这一条:Provider 列表里有 subagent-claude-code 和 subagent-codex——DeepSeek Harness 不和 Claude Code 竞争,它可以编排它们,让自家 Agent 通过 subagent 工具把子任务交给 Claude Code 完成。在 DeepSeek Harness 里,它们只是不同的插头。详见 007:能力接缝模式。


第二层:能力与协作模块

上面八个模块是一条消息的必经之路——任何一次对话都要经过它们。但一个完整的 Agent 产品远不止这条主线:Subagent、Workflow、Guard、Permission 这些能力不在消息主线上,它们挂在核心周围,决定"这个产品能干什么":

模块干什么文章
Subagent把任务委托给子 Agent,多个 Provider 可共存(含 subagent-claude-code / subagent-codex)009(规划中)
Workflow模型编写编排脚本,驱动多个子 Agent 协作010(规划中)
Guard监视循环的无效模式,给单次工具调用设预算与超时011(规划中)
Permission人类协作层(interaction 包):提问、审批、权限预设、命令011
Sandbox给进程执行套安全边界(sandbox 包)011
Plan计划模式:登录日志的协作状态,而非一个开关012(规划中)
Preset按会话组合 Agent:不同会话可有不同工具与提示词012
Context请求上下文注入(指令、时间、终端状态……)012
Todo / Goal任务清单与持久目标012
Hooks兼容 Claude Code / Codex 的 hook 协议桥013(规划中)

一层决定"一条消息怎么走",一层决定"这个产品能干什么"——这是整套架构的两条腿。下文的系列导航把两层都列上了(第二层标注"规划中")。


这样设计,换来了什么

开头说的那个"if 堆"宿命,现在可以给答案了。

  1. 扩展不碰核心。 权限、超时、压缩、截断——全是挂插件,核心循环一行不改。想换整个 Agent Loop?写一个实现 Agent 接口的新插件就行,不用 fork。
  2. 换件不换车。 Seam 让 Provider 随便换:换模型是一行配置,把文件系统、子进程指向远程沙箱这样的大改,也只是配置。
  3. 一切可重放。 Session 日志让"模型看到的"和"记录下来的"永远一致——恢复、审计、调试、计费全部从日志派生,不需要额外设施。

代价也要说清楚:插件体系把复杂度前置了。第一次理解它比理解一个 if 堆难,但之后每一次改动都比 if 堆简单。是"一次性付清复杂度",还是"每次改动都付利息"——DeepSeek Harness 选了前者。


系列文章导航

八个核心模块,对应八篇详细源码解析;第二层能力模块的文章标注"规划中":

编号核心模块详细文章
001Inbox + Agent Loop执行引擎:Turn/Step 循环与输入队列
002System Prompt提示词拼装:Section 与优先级
003LLM模型适配:换模型只改配置
004Tools工具系统设计:三段 waterfall 管道
005Session事件日志:重放即真相
006Compaction长对话压缩:摘要换历史
007Capability Seam能力接缝:换 Provider 就是换插头
008Cordis插件体系:注册即挂载,卸载即清理
第二层(规划中)
009Subagent子 Agent:委托与隔离(规划中)
010Workflow编排:模型驱动的多 Agent 协作(规划中)
011Guard + Permission + Sandbox循环卫生、人类协作与沙箱(规划中)
012Plan + Preset + Context + Todo会话状态与上下文注入(规划中)
013HooksClaude Code / Codex hook 桥(规划中)

001 是核心必读,理解 Agent Loop 之后其他模块才能串起来;007 是跨模块的设计模式,读完 ①-⑥ 再读更通透。001-008 是第一层(消息主线)的文章,009-013 是第二层(能力与协作模块)——第一层决定"一条消息怎么走",第二层决定"这个产品能干什么"。


常见问题 FAQ

Q: "八个核心模块"是官方划分吗?

A: 不是,是我读源码后归纳的。官方文档(docs/architecture.md)没有这种划分。这个划分是为了帮助理解,不是源码里的命名。

Q: Turn 和 Step 有什么区别?

A: Turn 是一次完整对话轮次(从用户输入到 Agent 返回最终结果),Step 是 Turn 内部的一次"调用模型 → 执行工具 → 返回结果"迭代。一个 Turn 可能包含多个 Step——"帮我重构并测试"可能先读文件、再改代码、再跑测试,最后无工具调用才结束轮次。

Q: DeepSeek Harness 和 Claude Code 是什么关系?

A: 不是替代品。DeepSeek Harness 的 ctx.subagents 有 subagent-claude-code 和 subagent-codex 两个 Provider——你可以让 DeepSeek Harness 的 Agent 通过 subagent 工具调用 Claude Code 或 Codex 完成子任务。它们在 DeepSeek Harness 里只是不同的 Provider。

Q: Session 和持久化是什么关系?

A: Session 在内存里是追加式事件日志。持久化(JSONL/SQLite)是可选插件,监听 session/event 把事件写到磁盘。内存里的 session 不依赖持久化层——不装持久化插件,session 照常工作,只是重启后丢失。

Q: 子 Agent 和主 Agent 共享上下文吗?

A: 不共享。每个 Agent 通过 createScope() 创建独立作用域,有自己的 ctx、工具集、事件监听器。子 Agent 的注册不会泄漏到父 Agent——这是有意的隔离设计,防止子任务污染主上下文。


参考链接