← 文章

DeepSeek Harness

Agent 一直往下做,什么时候该停下来?

从一句“先别写文件”出发,追踪 DeepSeek Harness 的输入队列、轮次循环、工具并发、审批守卫与合作式取消,理解 Agent 何时继续、何时停下。

假设你让 Agent 排查一个偶发故障:先读日志和配置,找出原因,再决定要不要修改。它开始工作后,你忽然想起一个限制,补了一句:“先别写文件,只给我分析。”

这句话到达时,模型可能正在输出,也可能已经请求了几个工具。系统应该立刻把新消息塞进当前请求吗?已经开始的读取要不要中断?如果一个写操作正在等待批准,点击允许之后,还能不能再被其他策略拦住?

这些问题很难靠一句“调用模型、执行工具、重复”回答。DeepSeek Harness 把默认驱动放在 agent-loop,但驱动周围还有输入队列、工具调度和执行策略。这个系列最后一篇,沿着上述假设任务把它们接起来。源码固定在 dsh-v0.1.2-rc.1,场景不是官方故障案例,也不代表本文运行过真实修复任务。驱动说明

一句话进入系统,不等于已经进入模型

先看你补充的那句限制。Agent 有两个待处理队列:next-turn 留给后续轮次,next-step 留给下一步。followup() 把输入放进下一轮并唤醒驱动;steer() 把输入放进下一步并唤醒;inject() 也进入下一步,却不主动唤醒一个空闲 Agent。输入入口

因此,补充上下文和开启任务不是同一件事。一个插件可以先放入背景材料,等真正的工作到来再一起使用;正在进行的模型请求也不会因为队列多了一项,就神奇地改写已经发送出去的输入。消息通常在下一次步骤边界被领取。

在轮次起点,Inbox 领取全部下一步输入,再领取一条下一轮消息;同一轮后续步骤只领取下一步输入。这样排队的独立任务不会全部挤进当前步骤,而用于修正当前工作的内容能在下一步被处理。Inbox 领取算法

这个版本还把队列的插入、移除和领取写成 agent/inbox/spliced 事件。构造 Inbox 时,它重放所属事件来恢复待处理状态。相同消息身份不能同时重复待在两份队列里。队列可重建,并不表示排队内容已经成为模型历史:真正进入步骤的消息还要经过下一道入口,才会记录为 user/message。

一轮工作,可能向模型问很多次

这里最好把两个词分清。turn 是一轮工作的外框,step 才是一次模型请求及其后续工具处理。驱动先记录 turn/start,领取输入,组装提示,再通过 agent/pre-step 决定是否进入。

这个入口可以拒绝,也可以重写待进入的消息。拒绝时,本轮标记为 blocked;第一次进入被改成空消息时,也可以结束一轮而不花一次模型调用。只有通过入口之后,才记录 step/start 和实际进入的消息。入队、领取、被模型看见,沿途有不同的状态。轮次状态机

模型回答如果没有工具调用,步骤可以结束;如果请求了工具,驱动先执行、记录结果,通常还需要下一次模型请求来理解它们。源码用尚未形成结束原因的状态表达这种“后续还欠一次处理”。工具结果也可以带 concludesTurn,让这一批工具后的步骤被视为完成;它不是一个立即取消其他工具的开关。

即使当前步骤可以结束,下一步队列里的新内容仍可能让工作继续。agent/turn-stopping 还提供一个收尾扩展点,让策略在最终退出前参与决定。它是串行事件,不是所有事件都共用同一种可调用 next() 的中间件协议。理解这些条件后,就不该把 Agent 的结束判断简化成“模型没有说话了”。步骤与收尾

几个工具一起跑,怎样避免乱序

回到故障排查。读取日志和读取配置可能相互独立,修改文件却可能依赖前面的结果。Harness 的调度器根据工具的执行分类,把允许并行的调用放进有上限的滚动池,把独占调用作为屏障。

假如顺序是 A、B、C 三个并行调用,池容量为二,A 很慢而 B 很快,B 结束后可以启动 C,不必等整个小批次齐步完成。遇到独占的 X,则先排空前面的池,让 X 单独运行,再允许后面的调用开始。工具调度实现

并行调用重叠执行,独占调用形成屏障,结果仍按模型顺序提交
开始、完成和记录是不同的顺序;并发优化没有把历史顺序交给网络延迟决定。

关键是,重叠的是 dispatch 和工具主体,前置策略与最终结果处理仍按模型调用顺序推进。调度器为每个位置保存结果槽,committed 只能跨过连续准备好的位置。B 先完成,可以释放并发位置,却不能让 B 的结果越过尚未完成的 A 写入最终历史。附加上下文也跟着有序结果进入下一步队列。

这是有意保留的等待:慢的 A 会延迟后面结果的最终记录,但不会必然阻止并行池继续利用空位。默认并行上限为十,并非十个线程,也不是模型服务的吞吐保证。调度器还会在后续调用启动前重新查询分类,避免把最早看到的注册状态当成永远有效。调度配置、滚动池与提交游标

工具能否安全并行也不能只看名字。本版本按单个调用分类;如果安全性需要比较兄弟调用是否操作同一资源,就应保守地保持独占。调度器没有替所有业务构建文件锁或数据库事务冲突检测。

模型想调用,不代表系统允许执行

轮到一个写操作时,模型输出的只是调用意图。工具注册表还要经过执行流水线。先运行 tools/pre-execute,得到允许、拒绝或询问的决策;需要询问时,再交给 approval 服务。

如果没有 approval 服务,没有可路由的 Agent,或者没有可用的审批通道,请求不会因为“问不了”而默认放行。批准一次仅让这次请求继续往下走。接下来还有注册的单调守卫:它们可以拒绝或不表态,却不能强行覆盖其他守卫的拒绝。审批解析与守卫

工具调用经过前置决策、可选批准、单调守卫及执行,取消停止后续派发并等待在途工作
批准不是跳过后续限制的通行证;取消也不是把已经发生的动作撤回。

这个顺序很有价值。可扩展的前置中间件负责交互、解释和部署策略,而不可被后续“允许”推翻的限制放在守卫里。假设你只批准修改某个目录下的文件,其他作用域中的限制仍能拒绝不属于授权范围的动作。这里是架构举例,并不是说这个注册表自身已经实现了所有路径权限规则。

真正执行之后,还有 tools/post-execute 结果处理、工具拥有的内容收尾,以及最终结果观察通知。前置拒绝会跳过工具主体,但仍能走结果处理形成一个模型可读的失败结果;下一步模型因此可以调整方案,而不是只能面对一个突然消失的工具调用。执行流水线

不过,修改返回文本不等于撤销副作用。假如操作已经写入远端,后置策略把结果改成错误,也不能让那次写入自动消失。权限控制必须放在真正产生副作用之前,业务系统仍需承担自己的数据权限和事务规则。

点下停止后,为什么还需要等一会儿

agent.cancel() 会中止当前活动的 AbortController,默认还清空待处理输入;调用者明确指定 keepInbox 时才保留队列。取消后到来的新唤醒消息会改投下一轮,避免把新任务塞进已经中止的活动。取消与重新唤醒

对于工具池,取消首先意味着不再补充新调用,并等待已经启动的调用结束和提交。剩余没有派发的模型调用会补上一对合成的 tool/call 与错误结果,错误码为 ABORTED_BEFORE_DISPATCH。这些记录解释“为什么没有执行”,而不是证明工具主体真的跑过。取消后的调度收束

工具注册表也会保留原始调用者信号,即使 around-wrapper 更换了执行信号,进入工具主体前仍把两者合并。主体一旦开始,注册表等待它的 Promise 收束,不会只赢下一场 Promise 竞速就把正在运行的工作遗弃。副作用是否能够停止,最终还取决于具体工具怎样响应这个信号。工具主体与信号合并

所以,停止可能有等待时间。一个完全忽略取消、永不兑现 Promise 的工具,也可能拖住这条合作式退出路径;循环本身不提供“按一下就杀掉所有外部执行”的保证。部署需要怎样的超时、进程终止或容器约束,应在相应插件与执行后端中实现,不能把 UI 按钮当成已经具备这些能力的证据。

停下来的原因,也要留下来

如果取消发生在流式回答中间,驱动会保留已经交付内容中可形成记录的部分,追加带 interrupted: true 的 assistant 消息,并引用对应 chunk。用户看见过的前缀因此不会在后续模型历史中毫无来由地消失。

步骤和轮次还有各自的结束记录。取消写入 aborted 和具体原因,普通失败写入 error;模型输出达到 max-tokens 的事实也不会被后续普通完成轻易降级为一轮完全正常的结果。模型请求失败能否重试由 agent/request-error 扩展点参与,而不是把工具错误、插件异常与模型服务故障全塞进同一套无条件重试。结束状态与请求恢复

本版本没有内置的整轮预算。工具调用或新输入可以继续推动工作;需要限制失控轮次时,应由策略通过已有生命周期入口取消。并行上限限制同时执行多少工具,token 上限限制一次请求能输出多少,它们都不能代替“这整个任务最多花多久、调用多少次”的预算。

让循环可靠,比让循环看起来聪明更具体

四篇读下来,可以把职责接成一条完整链:插件装配让能力可替换,workflow 把多 Agent 分工写成控制流,Session 留下可追溯的事实,而 Agent 循环把输入、请求、工具与结束状态接起来。可靠性来自这些边界互相对得上,不只来自模型下一句说得是否漂亮。

本篇执行了固定提交的 tool-calls.ts 和 inbox.ts,完成 18 项源码级检查,覆盖滚动补位、独占屏障、有序结果、取消与队列重建。工具注册表阶段、Session 和消息创建使用替身,没有运行完整 Agent、真实模型或审批通道;权限与信号合并部分由源码核验支持,不能当作生产隔离测试。

DeepSeek Harness 的四篇研究在这里收束。接下来转向 Hermes Agent,从它的架构组织和实际运行路径继续追问:当同一个 Agent 需要跨越不同入口,哪些状态和能力应该共享,哪些又必须各自隔离?

继续阅读