Agent Loop 只是起点:DeepSeek Harness 在模型之外做了什么?
一次失败测试,刚好暴露了 Agent Loop 图里没有画出来的部分。
TLDR:Agent Loop 解释了模型为什么能看完结果再试一次,却没有解释谁替它准备上下文、谁把它输出的 JSON 变成一条命令、任务中断后还剩下什么,以及一次危险写入为什么没有碰到文件系统。这些都是 Harness 要处理的问题。DeepSeek Harness 尤其适合拿来研究,因为它把其中许多职责做成了插件。这样设计给了系统更大的调整空间,也让依赖、调试和安全成本变得更清楚。
版本说明:本文依据 DeepSeek Harness commit
47f94385核验,时间为 2026 年 8 月 16 日。该版本对应dsh@0.1.0-rc.5。项目仍处于开发者预览阶段,后续版本可能改变本文介绍的设计。
1. Agent Loop 画到哪里就停了
先把一项不大不小的任务交给 Coding Agent:
这个测试在重构后失败了。找出原因,修好,再运行相关测试。
接下来的步骤已经很熟悉了。Agent 先读报错,搜索相关代码,打开文件,作出修改,再运行测试。如果测试仍然失败,新的输出会交还给模型,帮助它调整判断。
这个过程通常被画成 Agent Loop:
模型 -> 选择动作 -> 调用工具 -> 读取结果 -> 再次请求模型
这张图抓住了 Coding Agent 与普通聊天模型的一项重要区别:模型不必根据最初的上下文一次猜完。它可以先行动,看见结果,再修改自己的判断。
现在把任务暂停在四个再普通不过的时刻。
第一次请求模型之前,已经有一个系统选好了项目说明、对话历史和可用工具。模型要求读取文件时,也需要一个系统把结构化请求交给文件系统。代码修改完成、测试尚未运行时,还要有人记住任务停在了哪里。假如模型选错路径,准备写入工作区之外的文件,这次操作也必须在碰到文件系统之前被拦下来。
模型自己做不了这些事。它输出的是表达意图的 token,并不直接拥有文件系统、Shell、网络和使用这些资源的权限。
Agent Loop 的解释力就停在这里。图上那条“调用工具”的箭头,实际上压缩了许多决定:模型看得见哪些工具、工具是否存在、参数能否解析、操作是否允许、它会在哪里执行,以及结果怎样进入下一次模型请求。
用过 Codex 或 Claude Code 的人,其实已经从产品里见过这些决定。项目说明会影响 Agent 的回答;命令输出会进入下一步;有些操作会弹出审批;任务进行中,用户还可以补充要求。不同产品的做法各有差异,但模型周围始终有一套运行系统在工作,承担的工作远多于重复请求模型。
Agent Loop 描述了一项任务的节奏。Harness 则负责让这项任务能够实际执行、可以恢复,并且受到约束。
接下来要回答四个问题:
- 模型这一轮能看到什么?
- 工具请求怎样变成实际操作?
- 一次模型请求结束后,什么还能保留下来?
- 危险动作怎样在执行前被拦住?
2. Agent Loop 没画出来的四件事
本文所说的 Harness,指模型周围的运行系统。它准备模型需要的输入,把模型意图接到执行环境,记录已经发生的事情,并在操作进入外部系统之前施加限制。
这里的先后顺序很重要。Harness 先组装上下文,模型才能选择下一步。模型给出工具请求之后,Harness 又要接手,决定这次请求能否执行、怎样执行。模型负责选择动作,运行系统决定它根据什么信息来选,以及这项选择能获得多大权限。
由此可以看出 Harness 的四项基本职责。
准备上下文。 系统从项目说明、之前的消息、任务状态和当前可用工具中,组装下一次模型请求。上下文窗口有限,历史记录通常不会原样重放。哪些说明被选中、旧事件怎样表示、哪些工具仍然可见,都会影响模型的下一步判断。
执行工具。 模型看到的是工具名称、参数格式和用途说明。运行系统还要解析调用、找到对应实现、处理取消与超时,并把成功、失败或拒绝整理成模型能够理解的结果。
保存任务状态。 用户消息、模型输出、工具请求、工具结果、审批和中断,都可能影响后面的工作。系统保存这些事实,下一步才能接着做;任务恢复或从旧进度分出新任务时,也能从同一份记录开始。
限制危险操作。 模型提出一个动作,不等于它获得了执行权限。运行系统要判断哪些操作需要用户批准,并通过执行边界限制进程能够访问的资源。提示词里写一句“请小心”做不到这两件事。
下面这张图表示这些职责之间的关系。它是一张逻辑图,不代表真实进程、部署方式或网络连接。

图 1:Harness 围绕一次模型与工具交互形成的逻辑边界。箭头表示意图、结果和任务记录的流向。
图里有两条回路。容易看到的一条,从模型走向工具,再带着结果回到模型。另一条经过任务状态:消息和每一步的事件先被记录,再用于组装下一轮上下文。缺少第二条回路,Agent 仍然能调用工具,却很难可靠地应对长任务、中断和恢复。
外层边界也值得留意。Harness 不等于整个产品。用户界面、模型服务、文件系统、Shell 和外部服务都可以位于边界之外。Harness 负责协调对它们的访问,让这些访问遵守同一份任务状态和权限规则。如果把 Coding Agent 产品里的所有部分都叫作 Harness,这个词也就失去了作用。
有了这条边界,我们不必先钻进源码,也能理解 DeepSeek Harness 的架构。沿着前面的四个问题看下去就够了。
3. DeepSeek Harness 怎样组织这四件事
DeepSeek Harness 用一句话概括自己的架构:“everything is a plugin”。这里的插件远不止工具。模型接入、工具注册、任务记录,甚至 Agent Loop 本身,都可以通过插件加入运行系统。
下一篇会专门介绍管理这些插件的 Cordis。眼下先看这种设计怎样影响那项失败测试任务。
下一次请求模型时,它会收到什么
模型开始调试之前,需要拿到用户要求、项目约束、有用的历史和一组可调用工具。它不知道这些内容放在哪里。DeepSeek Harness 会在每一步开始前把它们组装起来。
插件可以加入系统提示词片段、补充上下文和工具定义。当前 Agent 的作用范围会进一步限制哪些内容可见。进入下一步之前,运行系统仍然可以改写或拒绝已经组装好的输入。
所以,上下文是每一轮计算出来的结果,并非一段始终不变的大提示词。项目说明发生变化、某个工具被卸载,或者旧任务重新恢复,下一次请求的内容都可能跟着变化。模型没有换,做判断时掌握的条件已经不同。
这个区别能帮助我们判断问题出在哪里。Agent 不知道项目要求使用哪条测试命令,可能是相关说明没有进入上下文,未必是模型不会调试。一个已经卸载的工具仍然出现在工具列表中,则说明系统展示给模型的能力与当前可用能力脱节了。
DeepSeek Harness 还会记录当时送给模型的完整系统提示词和工具定义。任务中断后,运行系统可以查到模型实际看见了什么,无需根据精简过的聊天记录猜测。恢复任务和排查问题,也就有了具体起点。
一次工具调用怎样进入现实世界
假设模型决定读取 src/parser.ts。它此时输出的仍然只是一次结构化工具调用,其中包含工具名称和参数。模型的工作在这里暂停,接下来由 Harness 接手。
DeepSeek Harness 先记录这次调用,再寻找对应工具、解析参数,并在工具运行前检查权限。执行结束后,系统把结果整理成一份统一记录。任务状态、用户界面和下一次模型请求看到的,都是这份结果的不同用途。
这条路径看起来比直接调用函数多了不少步骤,因为三件事必须保持一致:模型要求做什么,界面显示做了什么,执行环境实际做了什么。如果中间环节可以悄悄把 src/parser.ts 换成另一个路径,任务记录就无法描述事实。审批和后续排查也会建立在错误信息上。
失败结果同样要沿着这条路径返回。文件不存在、权限申请被拒绝、测试进程返回错误,这些都是下一步需要的观察。把它们隐藏或粉饰掉,模型便会把没有发生的操作当成已经完成。
Agent Loop 用一条箭头连接模型与工具。Harness 要保证箭头经过的每一步说的是同一件事。
任务中断后还剩下什么
假设 Agent 已经修改了解析器,却在运行测试之前停了下来。仅靠最后一条模型回复无法恢复任务。系统还需要知道用户最初要求什么、哪些工具已经运行、文件修改是否成功,以及任务停在了哪个位置。
DeepSeek Harness 把这些事实写进一份只追加的任务记录,官方称为 Session。用户消息、模型输出、工具调用和工具结果按照发生顺序进入 Session。下一次请求模型时需要的历史,也从这份记录生成。
聊天界面只是 Session 的一种展示,并不是它的全部。界面可以把一次操作收成一张小小的“运行测试”卡片,运行系统仍然要保留准确的调用内容、完成状态和执行结果。
回到失败测试,Session 可以留下一个很明确的状态:源码已经修改,测试尚未运行。下一步便能从中断处继续,而不必依赖一句含糊的总结。至于任务怎样分轮推进、怎样恢复和怎样从旧进度分支,会放在第 3 篇详谈。这里先记住一点:任务能够延续,靠的是 Harness 保存的记录,不是模型自己的记忆。
谁阻止危险动作落到系统里
工具调用表达了模型的选择,却没有赋予模型权限。读取普通源码与写入工作区之外的文件、删除数据、启动拥有更大访问范围的进程,风险完全不同。
DeepSeek Harness 把用户审批和执行边界分成两道控制。审批回答“用户是否允许这项操作”,沙箱等执行边界则决定“进程启动后实际能碰到什么”。某项操作无需弹出审批,并不代表它可以越过既定边界。
多项安全检查还必须保持单向收紧。一项检查已经拒绝某次调用,后面的步骤不能再把拒绝改成允许。否则,只要在工具流程里多接入一个插件,前面作出的安全决定就可能被削弱。
在失败测试任务里,修改工作区中的目标文件可以继续。写入其他位置则可能要求审批,或者直接失败。拒绝结果会和普通工具结果一样写入 Session,Agent 必须据此调整方案。
这些控制无法阻止模型选择错误动作。它们能阻止错误选择自动变成系统权限。具体的审批规则和沙箱机制,留到第 5 篇再展开。
现在,四条路径已经连起来了。上下文准备好这一轮的输入,Agent Loop 请求模型作出选择,工具流程把选择接到执行环境,Session 记下结果,安全限制决定哪些动作可以越过边界。DeepSeek Harness 让它们共同完成一项任务,又没有把所有职责写死在同一个实现中。
4. 插件化能消除复杂度吗
模型接入、工具、任务记录和 Agent Loop 都可以替换,确实给了系统很大的调整空间。团队能够更换模型提供方,为不同任务开放不同工具,也可以把文件和 Shell 操作移到隔离环境,而不用重写整条任务流程。
接口可以替换,系统原有的约束却必须继续成立。
以执行环境为例。文件工具已经指向远程沙箱,Shell 却仍在本机运行,Agent 面对的就是两套文件系统。它可能读取远程项目,又拿本地代码去测试。仅仅让两个插件拥有相似接口,解决不了这个问题。相关工具还必须看见同一批文件,在同一个执行环境里工作。
卸载带来的是另一类风险。一个插件可能注册了工具、监听了事件、启动了后台任务,还依赖其他服务。删除工具名称很容易。移除所有监听、停止所有任务、释放相关资源,才是完整的生命周期管理。
依赖被替换时,类似的问题会在系统运行中出现。使用这项依赖的插件应该重新启动、暂时停用,还是继续拿着一个已经过期的对象?这些选择都可能合理,但运行系统需要给出明确规则。否则,实际行为会取决于插件加载顺序,以及哪些旧引用碰巧没有消失。
插件化也扩大了调试范围。一次测试失败,原因可能在模型判断、上下文组装、插件顺序、工具实现、权限决定或持久化后端。清楚的边界有助于定位问题,更多边界也意味着需要检查更多位置。
兼容性不再只是版本管理问题,而会直接进入架构。DeepSeek Harness 目前处于开发者预览阶段,官方明确提醒后续会有破坏兼容性的变化。当 Agent Loop 和任务记录都可以替换时,事件顺序、取消方式和记录格式不能只靠默认约定。具体实现可以更换,其他插件依赖的规则必须保持清楚。
安全方面的取舍更尖锐。增加一个只读搜索工具,获得的权限有限;能够修改工具策略或任务记录的插件,则靠近运行系统的信任边界。扩展位置要有足够能力解决问题,也要防止一个组件悄悄放宽另一个组件的权限。
因此,“everything is a plugin”强调的是职责怎样分布,与插件数量无关。DeepSeek Harness 把原本容易固定在 Agent Loop 周围的决定,移到了可以替换的部分中。这样带来了更多控制,也要求系统承担生命周期管理、依赖规则、兼容性、调试范围和安全审核上的成本。
Codex 与 Claude Code 同样需要准备上下文、执行工具、保留进度和限制操作。比较它们时,重点不在于谁“拥有 Harness”,而在于哪些边界被固定在产品内部,哪些边界允许用户配置或扩展。要作出更强的判断,还需要限定具体版本和工程问题。
5. 为什么下一篇要从插件生命周期讲起
现在,那项失败测试已经走过整套运行系统。Harness 决定模型看见什么,把工具调用送进文件系统,记住尚未完成的测试,并把危险路径挡在执行边界之外。Agent Loop 很重要,但它只是贯穿这些系统的一段可见节奏。
DeepSeek Harness 又把其中许多部分做成了插件,于是留下了一组更难的问题。插件卸载时,它注册的工具、监听器和后台任务怎样一起消失?依赖被替换时,谁负责重新连接或启动使用方?多个插件都要检查同一次工具调用时,系统怎样维持明确顺序,并阻止后面的组件推翻前面的安全拒绝?
这些都属于插件生命周期,Agent Loop 回答不了。
下一篇讨论:DeepSeek Harness 为什么要用插件系统? 我们会从一个很实际的观察进入 Cordis:加载插件不难,难的是让它在离开时收拾干净,并在依赖变化时继续正确工作。