Clipping 微信公众号

迭代预算:IterationBudget如何防止子Agent跑飞

by Jameszyh 原文 ↗
Created: 2026-06-28

公众号名称:James的成长日记

作者名称:Jameszyh

发布时间:2026-06-28 16:42

大家好,我是James。

上一篇我们聊了子 Agent 委托——delegate_tool.py 怎样用隔离上下文、受限工具集和深度控制,把一个复杂任务拆成多个并行子任务。那篇文章解决的是“怎么把任务拆开”。

但任务能拆开,只是第一步。真正更难的问题是:拆开以后,怎么防止每个 Agent 一直转圈、一直试错、一直追加上下文,最后把成本、延迟和失败面一起放大?

今天这篇,就来讲 Hermes 在“隔离与协作”板块里的第二个关键设计:迭代预算(IterationBudget)。它只有几十行代码,却定义了一个很重要的系统边界——Agent 可以自主,但不能无限自主;可以探索,但必须在预算内探索。

本文使用版本:Hermes Agent 仓库 2026-06,核心文件:agent/iteration_budget.pyagent/conversation_loop.pyagent/turn_finalizer.py


一、为什么 Agent 需要“迭代预算”:不是怕它不思考,而是怕它一直思考

先看一个典型的 Agent 失控现场。

你让一个 coding agent 修一个测试失败,它的内部循环可能会变成这样:

  1. 读文件

  2. 跑测试

  3. 改一处代码

  4. 再跑测试

  5. 发现还是不对

  6. 再读别的文件

  7. 再改

  8. 再跑

  9. ……

如果没有预算限制,理论上它可以一直循环下去。问题不只是“浪费 token”,而是会同时带来四个系统级副作用:

  • 成本失控:每一轮都要重新发起一次模型调用

  • 延迟失控:用户在等待一个没有上限的过程

  • 上下文膨胀:每轮工具结果都在追加,越跑越大

  • 错误放大:模型一旦进入错误策略,会高频重复犯同一种错

所以,成熟 Agent 系统一定要回答一个问题:

什么时候该继续探索,什么时候该承认“这轮预算用完了”?

Hermes 的答案不是“限制总 token”,也不是“限制总耗时”,而是先定义一个更贴近 Agent 行为本质的单位:迭代(iteration)

在 Hermes 里,一次“迭代”大致等于:一次完整的 LLM 决策轮。也就是模型读完当前上下文,做一次决策,可能调用工具,也可能直接回答。这个单位比“token”更贴近产品体验,因为用户感知到的不是消耗了多少 token,而是:这个 Agent 又试了一次。


二、IterationBudget 的核心实现:几十行代码,定义整轮自主性的边界

agent/iteration_budget.py 只有一个类:

class IterationBudget:
    def __init__(self, max_total: int):
        self.max_total = max_total
        self._used = 0
        self._lock = threading.Lock()

    def consume(self) -> bool:
        with self._lock:
            if self._used >= self.max_total:
                return False
            self._used += 1
            return True

    def refund(self) -> None:
        with self._lock:
            if self._used > 0:
                self._used -= 1

    @property
    def used(self) -> int:
        with self._lock:
            return self._used

    @property
    def remaining(self) -> int:
        with self._lock:
            return max(0, self.max_total - self._used)

如果只看表面,它像一个线程安全计数器;但它真正重要的,是后面那几个设计含义。

1)预算对象是“每个 Agent 一个”

注释里写得很明确:

  • 父 Agent 默认预算来自 max_iterations(默认 90)

  • 子 Agent 默认预算来自 delegation.max_iterations(默认 50)

  • 每个 Agent 都持有自己的 IterationBudget

这意味着预算不是“全局请求配额”,而是执行单元级配额。Hermes 把预算和 Agent 实例绑定,而不是和整个任务绑定。

2)它是线程安全的

为什么要上锁?因为 Hermes 的子 Agent 可以并发运行。只要预算对象会跨线程读写,就不能只靠普通整数。

这里的锁非常朴素,但很关键:预算不是一个统计字段,而是运行时治理开关。 一旦并发条件下计数错乱,系统就可能出现两种极端:

  • 本该停的时候不停,导致跑飞

  • 本该还能跑的时候误停,导致任务过早终止

3)它支持 refund,而不是只支持 consume

这是这套设计最有意思的地方。Hermes 不是把每次“模型出手”都一刀切算进预算,而是允许某些低成本、程序化的回合把预算退回来。

也就是说,它控制的不是“绝对调用次数”,而是更接近:

真正消耗自主决策机会的次数

这比粗暴限流高明很多。


三、预算在主循环里怎么生效:为什么 Hermes 控的是“决策轮”,不是“工具次数”

conversation_loop.py 的核心循环:

while (api_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0) or agent._budget_grace_call:
    api_call_count += 1

    if agent._budget_grace_call:
        agent._budget_grace_call = False
    elif not agent.iteration_budget.consume():
        _turn_exit_reason = "budget_exhausted"
        break

这段逻辑非常值得细看。Hermes 同时维护了两套数字:

  • api_call_count

  • iteration_budget

很多人第一眼会觉得重复,其实不是。

api_call_count 是“这轮实际调了多少次模型”

它偏诊断、偏日志、偏最终汇报。比如 turn finalizer 里会记录:

  • 这次 turn 一共调了多少次 API

  • 最大允许值是多少

  • 最终退出原因是什么

iteration_budget 是“还允不允许继续自主探索”

它偏治理、偏控制、偏产品边界。循环能不能继续,不只看 api_call_count,还看 remaining > 0

这意味着 Hermes 在语义上把两个概念分开了:

  1. 事实层:到底发生了几次调用

  2. 治理层:还有没有资格再来一次

为什么这很重要?因为后面有 refund、grace call、错误回滚这些机制时,如果只有一个计数器,语义会越来越乱;拆成两层之后,每个字段职责就清晰很多。

更关键的一点:预算按“LLM 决策轮”扣,不按“单个工具调用”扣

这是 Hermes 的核心哲学。

一个 Agent 在某一轮里可能发起多个工具调用:

  • 先 read 文件

  • 再 grep 搜关键字

  • 再跑测试

  • 再汇总

这些都属于同一轮模型决策的执行后果,而不是四次独立的自主探索。如果按工具次数收费,模型会天然倾向于少用工具、多靠猜;但真正强的 Agent 恰恰应该在一轮里把工具用足。

所以 Hermes 控制的是“你还能再进行多少轮独立思考和策略更新”,而不是“你点了几个工具按钮”。


四、为什么要有 refund:不是所有回合,都该消耗一次完整预算

Hermes 在两个场景里会把预算退回去。

场景一:运行时上下文问题导致这一轮根本没真正开始

比如 Ollama 运行时上下文太小,系统在真正进入有效对话前就发现这轮请求不可能成功:

if _runtime_context_error:
    final_response = _runtime_context_error
    failed = True
    _turn_exit_reason = "ollama_runtime_context_too_small"
    api_call_count -= 1
    agent._api_call_count = api_call_count
    try:
        agent.iteration_budget.refund()
    except Exception:
        pass
    break

这里的意思很明确:既然这次“迭代”没有形成一次有效探索,就不该占掉用户的一次预算。

场景二:只有 execute_code 这种程序化 RPC 调用

Hermes 在循环后半段专门写了这样一段:

_tc_names = {tc.function.name for tc in assistant_message.tool_calls}
if _tc_names == {"execute_code"}:
    agent.iteration_budget.refund()

源码注释很直接:这类调用是 cheap RPC-style calls that shouldn’t eat the budget

这个判断特别有味道。它承认了一件现实:

  • 有些回合是“我需要重新思考策略”

  • 有些回合只是“我已经知道要干嘛,让程序替我做一下”

如果把两者都按同样价格计费,系统会把“思考”与“执行”混为一谈。Hermes 通过 refund 把它们拆开了。

这背后的设计哲学可以总结成一句话:

预算应该约束稀缺的认知决策,不应该惩罚廉价的程序执行。

这也是为什么它叫 IterationBudget,而不是 ToolBudgetCallBudget


五、预算耗尽后为什么还要给一次 grace call:停机不是终点,收尾也是产品体验的一部分

很多 Agent 的预算机制都停留在“到点就掐断”。这在工程上简单,但在产品上很差:用户只会看到一个半截子过程,不知道它做到了哪、卡在了哪、下一步该怎么办。

Hermes 在 turn_finalizer.py 里做了一个很成熟的补救:预算耗尽后,再给模型一次不带工具的总结机会。

if final_response is None and (
    api_call_count >= agent.max_iterations
    or agent.iteration_budget.remaining <= 0
):
    agent._emit_status(
        f"⚠️ Iteration budget exhausted ({api_call_count}/{agent.max_iterations}) "
        "— asking model to summarise"
    )
    final_response = agent._handle_max_iterations(messages, api_call_count)

注意这个动作不是“继续放权”,而是收权后的总结

  • 工具被剥掉

  • 不允许继续执行外部动作

  • 只允许基于已有上下文做总结

这就把“预算耗尽”从一个生硬的系统报错,变成了一个相对完整的用户结果:

  • 我已经做到了哪些

  • 哪些没完成

  • 卡在哪

  • 你可以接下来怎么处理

这一步非常像一个成熟工程团队里的超时机制:不是时间到就把终端断电,而是先要求 worker 交一份状态报告。

也正因为有这一步,Hermes 的预算上限才更像“治理边界”,而不是“粗暴熔断器”。


六、父子预算为什么必须独立:Hermes 刻意避免“父任务配额被子任务吞光”

这是第 23 篇最核心的设计点。

iteration_budget.pydelegate_tool.py 的注释里,Hermes 反复强调一件事:

  • 父 Agent 有自己的 max_iterations

  • 每个子 Agent 也有自己的预算

  • 父子预算是独立的

  • 因此“父 + 子”的总迭代次数,完全可能超过父 Agent 自己的上限

源码注释原话几乎就是:

each subagent gets its own budget capped at max_iterations … total iterations across parent + subagents can exceed the parent’s max_iterations

很多系统设计者看到这里会本能紧张:这不是意味着总成本可能放大吗?

对,Hermes 是有意这么设计的。因为它要解决的是另一个更糟的问题:

如果父子共用同一份预算,会发生什么?

假设父 Agent 总预算 90,拆了 3 个子 Agent:

  • 子 A 调研资料,用掉 30

  • 子 B 跑测试,用掉 25

  • 子 C 做代码修改,用掉 20

这时父 Agent 自己还没来得及真正汇总、比较、做最终决策,预算已经快没了。

结果会非常糟:

  • 拆得越细,父控制层越容易被饿死

  • 父 Agent 会因为“预算被子任务吃光”而没有能力做最后统筹

  • 用户会看到一堆局部结果,却拿不到最终结论

这违背了委托架构的初衷。子 Agent 的价值是替父 Agent分摊认知负载,不是抢走父 Agent 的最后决策权。

独立预算的真正价值:把“拆分能力”变成正收益

父子预算独立后,系统得到三个好处:

  1. 父 Agent 始终保有统筹能力

  • 不会因为子任务太努力,导致自己没预算收尾
  1. 子 Agent 的失败被局部化

  • 一个子 Agent 跑满 50 次,只是这个子任务失败,不会把整轮主任务一起拖死
  1. 预算语义更清晰

  • 父预算 = 主控层最多愿意尝试多少轮统筹

  • 子预算 = 单个工作单元最多允许试错多少轮

这很像公司管理里的预算科目拆分:总部预算和项目组预算分开,不是因为钱变多了,而是因为治理颗粒度更合理了


七、预算、超时、并发为什么要分开设计:三种约束,解决的是三种不同风险

很多系统喜欢只留一个总开关,比如“30 分钟超时”或者“最多 100 次调用”。看起来简单,其实会把不同类型的问题搅在一起。

Hermes 明显不是这么想的。它把子 Agent 治理拆成三类约束:

1)Iteration Budget:限制“策略探索次数”

防的是:

  • 一直重复试错

  • 进入错误循环

  • 模型不停改主意

2)Child Timeout:限制“墙钟时间”

配置里默认:

delegation:
  child_timeout_seconds: 600

防的是:

  • 网络很慢

  • 工具调用卡住

  • 长任务一直不返回

3)Max Concurrent Children:限制“同时开多少个 worker”

配置里默认:

delegation:
  max_concurrent_children: 3
const governance = {
  iterationBudget: '限制还能试几轮',
  childTimeoutSeconds: 600,
  maxConcurrentChildren: 3,
};

防的是:

  • 一次性起太多子 Agent

  • API 成本线性爆炸

  • 本地资源被并发打满

这三者各管一件事:

  • 预算管 “能试几轮”

  • 超时管“能等多久”

  • 并发管“能开几个”

只有把它们拆开,系统才能真正做到可调优。否则你会发现自己在用“超时”补“预算”,或者用“并发上限”掩盖“单任务跑飞”,最后全都难解释。


八、业界成熟 Agent 怎么做预算治理

如果把视角放大,会发现成熟产品几乎都在做预算治理,只是名字和颗粒度不同。

1)Claude Code / Codex CLI:更像“单 Agent 轮次上限 + 人在环接管”

这类 CLI agent 面向的是连续的人机协作,所以更强调:

  • 当前这轮别无限试

  • 不确定时把控制权还给人

  • 让用户决定要不要继续追加尝试

为什么这么选?因为 CLI 场景下,用户就在旁边。预算更像是“别自作主张太久”,而不是后台批处理配额。

2)LangGraph / AutoGen:更像“节点步数限制 + 图级停止条件”

框架型系统通常会在 graph / workflow 层设置:

  • 最多多少步

  • 某个角色最多说多少轮

  • 达到哪个状态就停止

为什么这么选?因为它们关注的是可编排和可证明终止。预算不是体验参数,而是图执行语义的一部分。

3)Cursor / Devin 这一类工程型 agent:预算会和任务单元绑定

工程型产品最怕的是:

  • 一个修 bug 任务无限跑测试

  • 一个索引任务不停重扫仓库

  • 一个安装依赖任务把工作区卡死

所以它们的预算通常会和 task / job / sandbox 绑定,再叠加 wall-clock timeout、日志和审批边界。

为什么这么选?因为代码任务有副作用,必须先把“最坏情况”锁死。

4)Hermes 的位置:把预算下沉到 Agent 实例层,再通过独立预算支撑委托架构

Hermes 的特别之处在于:

  • 它不是只给整个 workflow 一个总步数

  • 也不是只给用户一个“最多执行多久”的模糊承诺

  • 它是把预算做成了每个 agent 实例自带的运行时对象

这样做的结果是:

  • 单 Agent 场景下,它是防跑飞边界

  • 多 Agent 场景下,它变成委托架构的局部治理单元

这比“全局配额”更细,也比“纯工作流步数”更贴近真实运行时。

产品横评:预算到底约束什么

产品 / 体系预算约束对象主要目标典型哲学
Claude Code / Codex CLI单轮连续尝试防止长时间自作主张超预算后尽快还权给用户
LangGraph / AutoGen图节点 / 工作流步数保证流程可终止预算是编排语义的一部分
Cursor / Devin 类task / sandbox / job防副作用放大预算必须和隔离执行单元绑定
企业 Agent 平台任务实例 + 审批流控成本、控事故、可审计超预算要能追责、能重试
Hermes每个 Agent 的 IterationBudget防跑飞,同时保住父子协作能力预算约束认知探索,不惩罚廉价执行

从这些产品里,可以提炼出三个规律:

  1. 预算不是“省 token 小技巧”,而是自主系统的治理边界。

  2. 只要存在任务拆分,就应该尽量把预算绑定到执行单元,而不是一个全局大锅。

  3. 成熟系统不会只做“超了就停”,还会设计 refund、summary、timeout、audit 这些配套机制。


总结

  • IterationBudget 的本质不是计数器,而是 Agent 自主性的边界定义器:允许探索,但不允许无限探索。

  • Hermes 控制的是“LLM 决策轮”,不是工具调用次数:这样不会惩罚一轮内合理使用多个工具的行为。

  • refund 是这套设计的灵魂之一:失败前置、纯程序化 RPC 这类低认知成本回合,不该吞掉一次完整预算。

  • 预算耗尽后的 grace summary 非常关键:不是简单掐断,而是把“跑不完”转换成“有交代的收尾”。

  • 父子预算独立,是委托架构成立的前提:否则子任务会反向吞掉父任务的统筹能力。

  • 预算、超时、并发必须分开治理:它们分别约束探索次数、墙钟时间和资源扩散面,不能混成一个开关。

下一篇我们进入更硬的一层:权限审批体系——危险操作为什么必须等人拍板,三级授权模型怎么把 Agent 的执行欲望关进笼子里。


关注我,James 的成长日记,持续分享干货,帮你在 AI 时代少走弯路。


内容效果不满意?点此反馈

输入关键词开始搜索