ARTICLE · 1078023
LoopX 源码解析(三):回合机制——agent 每次干活,怎么做到不乱跑、不白干、不重跑
第一篇讲过,LoopX 把长任务切成一个个回合(turn),每个回合做一小段有边界的工作。第二篇讲了回合之间的东西——状态怎么存、认领怎么防冲突。本篇进入回合内部:一个回合从"决定要跑"到"记账收尾"的完整过程。
先看一个最顺利的回合长什么样
第一篇见过那四条命令,现在把它们按时间摆开。一个回合分三段:
【决策段】心跳触发loopx quota should-run → 允许跑吗?跑哪个待办?↓ 通过【执行段】loopx todo claim → 认领待办”改组件”agent 干活 → 改代码、跑测试,产出结果↓ 完成【结算段】loopx todo update → 验证结果、写回状态loopx quota spend-slot → 记账,回合结束
三段各一句话:决策段判断"该不该跑",执行段让 agent 干活,结算段验证结果并记账。顺利的回合就是这么平淡。本篇真正要讲的是每一段防的故障——标题里的三个"不"(不乱跑、不白干、不重跑)分别落在哪。
回合要防的四种故障
不该跑的时候跑了。 预算耗尽、审批还挂着、前置条件没满足——agent 却开始干活。长任务跑几天,没有总闸的话这种违规启动迟早发生,每次都在烧钱 跑一半死了,重来全跑。 回合执行到八成进程崩溃,重启后从头再来,已经花掉的模型调用再付一遍钱 干了活没记账,或记了账没干活。 执行和记账是两个动作,中间丢掉任何一步,账本和实际就不符。对不上账的长任务系统没法管成本 拿着旧决策续跑。 崩溃前系统判过"可以跑",恢复后直接拿这个判断继续——但预算可能用完了、待办可能被接走了、审批可能被拒了。旧判断已经过期,照着走必然出事
四条设计理念
理念一:决策只出计划,不动手
should-run 判断"该不该跑"时,只产出一份结构化的决策结果文档:允不允许跑、推荐跑哪个待办、执行边界是什么、不允许的话原因是什么。这份文档自带"不允许直接执行"的标记,真正执行要经过另一条路径。
分离的收益:每个决策可以重放。决策结果是对当时状态的确定性计算,事后重放一遍得到同样结果。出了问题("那晚它为什么跑了三个回合"),答案不是翻日志猜,是重放决策。
这是第 1 篇理念一(治理决策不依赖模型)在回合里的落点:决策段里没有模型。
理念二:agent 每个回合拿到的是新组装的输入,不是聊天记录
执行段开始时,LoopX 从状态文件重新组装这个回合的输入:当前目标、认领的待办、执行边界、上一回合的交接。agent 从这份输入出发干活,不依赖"记得上次聊到哪"。
agent 自带的会话可以续用(省 token),但续用前有身份校验:会话对应的待办必须和本回合认领的待办一致,不一致就拒绝续用、开新的。防的是一种隐蔽错误——待办已经换了,还拿旧待办的上下文接着干。
理念三:agent 干活时被关在只读沙箱里
执行段跑的是 Codex、Claude Code 这些工具,但跑法被约束:
沙箱默认只读:agent 能读代码读文件,写不了 LoopX 状态、花不了配额。它的工作以"结果"的形式交回来,由结算段决定怎么落账 输出必须按格式:结果要按预定义结构返回(做了什么、产出在哪、成没成),一段自由发挥的"我觉得做完了"不是合法输出 规则清单写死:给 agent 的指令里明确三条边界——只做这一个待办、不要改 LoopX 状态、不要花配额
理念四:回合是事务——做了就必须完整落账
这条理念最大,拆成三个机制讲。
机制一:三件事共享一个标识,缺一件回合不算发生。结算段要依次完成三件事:验证(测试过了、产物在)→ 写回(状态更新、证据登记)→ 记账(配额入账)。三件事的记录共享同一个标识(effect id),表明它们属于同一个回合。三件全部完成,这个回合才算"有效进展";缺任何一件,不算。
机制二:按阶段记账,崩溃后从断点继续。执行段每完成一个阶段,就往回合日志里记一笔。崩溃后重启,读日志知道干到哪了,从断点继续——已经付过费的模型调用不重复执行。这是故障二"重来全跑"的解。
机制三:恢复必须重新决策,旧决策不能直接续用。恢复时要继续跑,新决策必须指认上一回合的记录("我是接着那个回合的");指认不上,或环境已经变了(预算用完、待办易主),旧决策作废,重新走一遍决策。这是故障四的解:恢复的正确动作是"重新决策后继续",不是"继续"。
这套设计怎么支撑长任务
一个完整的例子:一个不顺利的回合
继续深色模式任务。第二篇讲到 Claude Code 认领了"改组件"(T3)、做到一半断电。本篇从这个断电点往前后展开。
22:40,决策段。心跳触发。should-run 全量检查:配额今天还剩 12 个槽、没有挂起的审批、T3 无人持有租约、没有在等的外部证据——通过。产出决策结果文档:跑 T3、写范围 src/theme/**、预算一个槽。
22:40–22:52,执行段,中断。认领 T3。LoopX 组装本回合输入:目标 + T3 + 边界 + 第二篇那份交接("从 Button 开始,剩 6 个组件")。agent 在只读沙箱里改完 Button 和 Input 两个组件,产出 diff 和测试。22:52 断电。
23:05,恢复,分四步:
查租约:T3 的租约过期了,待办回到无主状态 查回合日志:执行段已完成两个阶段,Button 和 Input 的改动都在 重新决策:不拿 22:40 那份旧判断直接续——重新全量检查,通过,重新认领 T3 从断点继续:剩下 4 个组件,已改的两个不重改
23:05 之后,结算段。剩余组件改完,agent 按格式交回结果。结算依次执行:验证(测试通过、6 个组件的 diff 都在)→ 写回(T3 标完成、证据登记)→ 记账(配额入账)。三张记录共享同一标识,全部提交——回合算作有效进展。假如验证没过:回合不算进展,配额按规则冲销(第二篇账本里那条 voided 记录就是这种场景)。
回看全程:模型只出现在执行段内部。要不要跑、跑哪个、怎么恢复、算不算完成、扣多少钱——全是确定性代码的决定。
设计亮点
决策结果是可重放的文档,不是消失的过程。多数系统的决策是过程性的,代码跑过去就没了,事后靠日志反推。LoopX 的决策留下一份可重放的结构化文档。可迁移的判断:治理系统的每个决策都应该留下可重放的凭证。
恢复的语义是"重新决策后继续"。常见实现是"接着跑",默认崩溃前的判断仍然有效——环境不变时碰巧对,环境变了就是事故。任何有崩溃恢复的系统都该问:恢复时凭什么认为旧判断还有效。
"做了"和"记了"要绑在同一个事务里。钱账、状态、证据要么都落、要么都没发生——数据库事务的老原则用在 agent 回合上。agent 系统普遍缺这一层:执行和记账各干各的,对不上账是常态。凡是有"做了"和"记了"两个动作的地方,都该问一句:它们在同一个事务里吗。
代价与局限
每个回合有固定开销。决策、认领、组装输入、日志、三段结算,这些管理动作本身花时间和 token。回合切得越碎,管理开销占比越高——回合粒度是使用时最重要的调参:太碎管理成本吃掉收益,太大又回到"一口气做完、崩溃全重来"。
排障要跨三段追。问题可能出在决策段(为什么判它跑)、执行段(agent 干了什么)、结算段(为什么没过验证)。有决策文档和记录可查是缓解,但先得把三段结构学会,排障门槛比"一个循环打天下"的实现高。
沙箱和输出约束依赖运行时能力。只读沙箱、格式化输出是 Codex 这类工具提供的功能;换成不支持的运行时,约束从"结构上不能"退到"提示词劝它别做"。
下一篇
本篇里 should-run 只出现了"通过/拒绝"两种结果,它内部的判断远比这丰富:配额怎么算、审批怎么触发、连续没产出怎么办——下一篇讲治理的三个闸门:配额、门禁、产出底线。