迭代预算:IterationBudget如何防止子Agent跑飞
公众号名称:James的成长日记
作者名称:Jameszyh
发布时间:2026-06-28 16:42
大家好,我是James。
上一篇我们聊了子 Agent 委托——delegate_tool.py 怎样用隔离上下文、受限工具集和深度控制,把一个复杂任务拆成多个并行子任务。那篇文章解决的是“怎么把任务拆开”。
但任务能拆开,只是第一步。真正更难的问题是:拆开以后,怎么防止每个 Agent 一直转圈、一直试错、一直追加上下文,最后把成本、延迟和失败面一起放大?
今天这篇,就来讲 Hermes 在“隔离与协作”板块里的第二个关键设计:迭代预算(IterationBudget)。它只有几十行代码,却定义了一个很重要的系统边界——Agent 可以自主,但不能无限自主;可以探索,但必须在预算内探索。
本文使用版本:Hermes Agent 仓库 2026-06,核心文件:agent/iteration_budget.py、agent/conversation_loop.py、agent/turn_finalizer.py
一、为什么 Agent 需要“迭代预算”:不是怕它不思考,而是怕它一直思考

先看一个典型的 Agent 失控现场。
你让一个 coding agent 修一个测试失败,它的内部循环可能会变成这样:
-
读文件
-
跑测试
-
改一处代码
-
再跑测试
-
发现还是不对
-
再读别的文件
-
再改
-
再跑
-
……
如果没有预算限制,理论上它可以一直循环下去。问题不只是“浪费 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 在语义上把两个概念分开了:
-
事实层:到底发生了几次调用
-
治理层:还有没有资格再来一次
为什么这很重要?因为后面有 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,而不是 ToolBudget 或 CallBudget。
五、预算耗尽后为什么还要给一次 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.py 和 delegate_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 的最后决策权。
独立预算的真正价值:把“拆分能力”变成正收益
父子预算独立后,系统得到三个好处:
-
父 Agent 始终保有统筹能力
- 不会因为子任务太努力,导致自己没预算收尾
-
子 Agent 的失败被局部化
- 一个子 Agent 跑满 50 次,只是这个子任务失败,不会把整轮主任务一起拖死
-
预算语义更清晰
-
父预算 = 主控层最多愿意尝试多少轮统筹
-
子预算 = 单个工作单元最多允许试错多少轮
这很像公司管理里的预算科目拆分:总部预算和项目组预算分开,不是因为钱变多了,而是因为治理颗粒度更合理了。
七、预算、超时、并发为什么要分开设计:三种约束,解决的是三种不同风险

很多系统喜欢只留一个总开关,比如“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 | 防跑飞,同时保住父子协作能力 | 预算约束认知探索,不惩罚廉价执行 |
从这些产品里,可以提炼出三个规律:
-
预算不是“省 token 小技巧”,而是自主系统的治理边界。
-
只要存在任务拆分,就应该尽量把预算绑定到执行单元,而不是一个全局大锅。
-
成熟系统不会只做“超了就停”,还会设计 refund、summary、timeout、audit 这些配套机制。
总结
-
IterationBudget 的本质不是计数器,而是 Agent 自主性的边界定义器:允许探索,但不允许无限探索。
-
Hermes 控制的是“LLM 决策轮”,不是工具调用次数:这样不会惩罚一轮内合理使用多个工具的行为。
-
refund 是这套设计的灵魂之一:失败前置、纯程序化 RPC 这类低认知成本回合,不该吞掉一次完整预算。
-
预算耗尽后的 grace summary 非常关键:不是简单掐断,而是把“跑不完”转换成“有交代的收尾”。
-
父子预算独立,是委托架构成立的前提:否则子任务会反向吞掉父任务的统筹能力。
-
预算、超时、并发必须分开治理:它们分别约束探索次数、墙钟时间和资源扩散面,不能混成一个开关。
下一篇我们进入更硬的一层:权限审批体系——危险操作为什么必须等人拍板,三级授权模型怎么把 Agent 的执行欲望关进笼子里。
关注我,James 的成长日记,持续分享干货,帮你在 AI 时代少走弯路。
内容效果不满意?点此反馈