{"schema_version":1,"id":"article:loop-engineering-optio","kind":"article","slug":"loop-engineering-optio","title":"从 while True 到 Loop Engineering：AI Agent 的古罗马副官","author":"晨旭","url":"https://chenxu.xin/writing/loop-engineering-optio","date":"2026-06-29","updated":"2026-06-29","description":"Claude Code 明明已经有两层循环，为什么还不等于 Loop Engineering？沿着开源项目 Optio 走完一条任务从生成、提交 PR 到 CI 失败后再次唤醒 Agent 的完整生命周期。","position":null,"group":null,"series":"动手落地AI","tags":["agent","harness","product-teardown"],"publication_status":"public","maturity":"evergreen","body_sha256":"04c0f7de59d55ceab85ca4dfe58fcecbe3ef0a6e361427ef79ab2e36bba6c4a8","paragraph_rule":"src/lib/paragraphs.mjs@1","paragraphs":[{"id":"p1","kind":"paragraph","text":"之前的文章拆解了 Claude Code 收到一个任务后，内部到底发生了什么。当时是把它的核心 query_loop 拆成了两层循环：","raw":"之前的文章拆解了 [Claude Code 收到一个任务后，内部到底发生了什么](/writing/claude-code-query-loop)。当时是把它的核心 `query_loop` 拆成了两层循环："},{"id":"p2","kind":"list","text":"- 内层是工具执行循环：模型发出 tool_use，Python 程序执行工具，再把 tool_result 塞回消息列表\n- 外层是回合循环：模型拿到工具结果后继续思考，直到输出 stop_reason == \"end_turn\"","raw":"- 内层是工具执行循环：模型发出 tool_use，Python 程序执行工具，再把 tool_result 塞回消息列表\n- 外层是回合循环：模型拿到工具结果后继续思考，直到输出 `stop_reason == \"end_turn\"`"},{"id":"p3","kind":"paragraph","text":"既然这里都有两层 Loop 了，那它是不是就是 Loop Engineering 呢？这是我最近学习 Loop Engineering 时最把我绕晕的问题。","raw":"既然这里都有两层 Loop 了，那它是不是就是 Loop Engineering 呢？这是我最近学习 Loop Engineering 时最把我绕晕的问题。"},{"id":"p4","kind":"list","text":"- 如果它不是，为什么不呢？难道代码里的 while True 还不算循环吗？\n- 如果它是，Automation、Worktree、Skill、Connector、Sub-agent 和 State 又是在解决什么呢？","raw":"- 如果它不是，为什么不呢？难道代码里的 `while True` 还不算循环吗？\n- 如果它是，Automation、Worktree、Skill、Connector、Sub-agent 和 State 又是在解决什么呢？"},{"id":"p5","kind":"paragraph","text":"带着这些疑问，我找到了一个开源项目 Optio，沿着一个任务从产生、执行、提交 PR，到 CI 失败后再次唤醒 Agent 的完整生命周期走了一遍，终于看清楚：","raw":"带着这些疑问，我找到了一个开源项目 [Optio](https://github.com/jonwiggins/optio)，沿着一个任务从产生、执行、提交 PR，到 CI 失败后再次唤醒 Agent 的完整生命周期走了一遍，终于看清楚："},{"id":"p6","kind":"list","text":"- Claude Code 里的两层循环自然是真正的 Loop，但它们解决的是：一次 Agent 运行内部如何推进\n- Loop Engineering 还要再向外包一层，解决：多次 Agent 运行之间，工作如何持续推进","raw":"- Claude Code 里的两层循环自然是真正的 Loop，但它们解决的是：**一次 Agent 运行内部如何推进**\n- Loop Engineering 还要再向外包一层，解决：**多次 Agent 运行之间，工作如何持续推进**"},{"id":"p7","kind":"paragraph","text":"这不是否定 while True，而是在追问一个更外层的问题：当这次循环退出以后，下一次由谁启动？","raw":"这不是否定 `while True`，而是在追问一个更外层的问题：**当这次循环退出以后，下一次由谁启动？**"},{"id":"p8","kind":"paragraph","text":"这篇文章从 Optio 的源码出发，拆开以下四个问题：","raw":"这篇文章从 Optio 的源码出发，拆开以下四个问题："},{"id":"p9","kind":"list","text":"- Claude Code 明明已经有两层循环，为什么还不等于完整的 Loop Engineering？\n- Automation、Worktree、Skill、Connector、Sub-agent、State 分别解决什么问题？\n- 为什么有了这六个组件，仍然不一定形成真正的 Loop？\n- 一个可以长期运行的 Loop，还需要哪些工程化护栏？","raw":"- Claude Code 明明已经有两层循环，为什么还不等于完整的 Loop Engineering？\n- Automation、Worktree、Skill、Connector、Sub-agent、State 分别解决什么问题？\n- 为什么有了这六个组件，仍然不一定形成真正的 Loop？\n- 一个可以长期运行的 Loop，还需要哪些工程化护栏？"},{"id":"p10","kind":"paragraph","text":"补充一句题外话：Optio，是古罗马军队中协助百夫长维持队伍运转的副手。而这个项目做的也是类似的事：它不亲自写代码，而是负责叫醒 Agent、分配任务、检查结果，再决定下一轮行动。","raw":"补充一句题外话：Optio，是古罗马军队中协助百夫长维持队伍运转的副手。而这个项目做的也是类似的事：它不亲自写代码，而是负责叫醒 Agent、分配任务、检查结果，再决定下一轮行动。"},{"id":"p11","kind":"heading","text":"明明已经有两层循环，为什么还不够？","raw":"## 明明已经有两层循环，为什么还不够？"},{"id":"p12","kind":"paragraph","text":"先回到之前文章中的 query_loop。把那些流式事件、消息类型和容错逻辑暂时去掉，它大致可以压缩成一个很短的循环。","raw":"先回到之前文章中的 `query_loop`。把那些流式事件、消息类型和容错逻辑暂时去掉，它大致可以压缩成一个很短的循环。"},{"id":"p13","kind":"paragraph","text":"这个 Loop 发生在一次 Agent 运行内部：模型规划，程序执行，结果重新放回上下文，模型再判断要不要继续。这当然是循环，而且还是两层嵌套循环。内层让模型和工具来回协作，外层让 Agent 可以连续推进多个回合。它们共同回答的是：","raw":"这个 Loop 发生在**一次 Agent 运行内部**：模型规划，程序执行，结果重新放回上下文，模型再判断要不要继续。这当然是循环，而且还是两层嵌套循环。内层让模型和工具来回协作，外层让 Agent 可以连续推进多个回合。它们共同回答的是："},{"id":"p14","kind":"quote","text":"人给 Claude Code 一个任务后，它内部如何把这个任务做完？","raw":"> 人给 Claude Code 一个任务后，它内部如何把这个任务做完？"},{"id":"p15","kind":"paragraph","text":"但问题恰恰出现在 end_turn 以后。","raw":"但问题恰恰出现在 `end_turn` 以后。"},{"id":"p16","kind":"paragraph","text":"假设 Coding Agent 提交了 PR，然后结束运行。半小时后 CI 才失败。这时 query_loop 已经退出，原来的 Agent 进程可能也不存在了。这时谁去读取 CI 日志？谁来重新唤醒 Agent？谁告诉它只修复这次失败？如果连续失败十次，又由谁来叫停呢？","raw":"假设 Coding Agent 提交了 PR，然后结束运行。半小时后 CI 才失败。这时 `query_loop` 已经退出，原来的 Agent 进程可能也不存在了。这时谁去读取 CI 日志？谁来重新唤醒 Agent？谁告诉它只修复这次失败？如果连续失败十次，又由谁来叫停呢？"},{"id":"p17","kind":"paragraph","text":"这些都不是增加一层工具调用循环能够解决的。","raw":"这些都不是增加一层工具调用循环能够解决的。"},{"id":"p18","kind":"paragraph","text":"之前的 Claude Code 文章拆解的是里面两层，Loop Engineering 重点关注的是最外面一层。","raw":"之前的 Claude Code 文章拆解的是里面两层，Loop Engineering 重点关注的是最外面一层。"},{"id":"p19","kind":"paragraph","text":"还有一个容易混淆的地方：Claude Code 的 Runtime 外面确实有 Supporting Systems，包括权限、Hooks、Memory、Session 和 MCP。但架构上的外层，不一定等于循环上的外层。这些模块在支撑 query_loop 运行，却不一定会在 CI 失败后主动生成下一次 Agent 任务。","raw":"还有一个容易混淆的地方：Claude Code 的 Runtime 外面确实有 Supporting Systems，包括权限、Hooks、Memory、Session 和 MCP。但**架构上的外层，不一定等于循环上的外层**。这些模块在支撑 `query_loop` 运行，却不一定会在 CI 失败后主动生成下一次 Agent 任务。"},{"id":"p20","kind":"paragraph","text":"判断是不是 Loop Engineering，关键不是代码里嵌套了几个 while，而是看：最外层那个负责发现工作、检查结果、记录进度和启动下一轮的人，有没有被系统替代？","raw":"判断是不是 Loop Engineering，关键不是代码里嵌套了几个 `while`，而是看：**最外层那个负责发现工作、检查结果、记录进度和启动下一轮的人，有没有被系统替代？**"},{"id":"p21","kind":"paragraph","text":"把传统的使用方式画出来后，这个区别就更明显了：在这里，Agent 只负责其中一段，真正的循环控制器一直是人。","raw":"把传统的使用方式画出来后，这个区别就更明显了：在这里，Agent 只负责其中一段，真正的循环控制器一直是人。"},{"id":"p22","kind":"paragraph","text":"所以「不要再亲自 Prompt Agent，而要设计 Prompt Agent 的 Loop」，并不是说以后不需要写 Prompt，也不是说 Agent 内部的循环不重要。它真正想替代的，是人反复复制错误、重新运行命令、追问 Agent、记录进度这些机械操作。","raw":"所以「不要再亲自 Prompt Agent，而要设计 Prompt Agent 的 Loop」，并不是说以后不需要写 Prompt，也不是说 Agent 内部的循环不重要。它真正想替代的，是人反复复制错误、重新运行命令、追问 Agent、记录进度这些机械操作。"},{"id":"p23","kind":"paragraph","text":"小结一下：Agent Loop 负责的是一次运行内部如何行动；而 Loop Engineering 负责的则是多次运行之间如何持续推进工作。","raw":"小结一下：Agent Loop 负责的是一次运行内部如何行动；而 Loop Engineering 负责的则是多次运行之间如何持续推进工作。"},{"id":"p24","kind":"heading","text":"Loop Engineering 的六个组件","raw":"## Loop Engineering 的六个组件"},{"id":"p25","kind":"paragraph","text":"带着这个问题我们来到 Optio 这个项目。这里不按文件夹来读源码，而是追踪一条具体链路：","raw":"带着这个问题我们来到 Optio 这个项目。这里不按文件夹来读源码，而是追踪一条具体链路："},{"id":"p26","kind":"quote","text":"每天自动生成一个代码维护任务，Agent 在独立目录中修改代码并提交 PR；CI 失败就重新修，CI 通过后启动另一个 Agent 审查；Review 通过才允许结束。","raw":"> 每天自动生成一个代码维护任务，Agent 在独立目录中修改代码并提交 PR；CI 失败就重新修，CI 通过后启动另一个 Agent 审查；Review 通过才允许结束。"},{"id":"p27","kind":"paragraph","text":"这时，Loop Engineering 的六个组件就从抽象名词变成了六个具体的问题。它们把原来散落在人脑和手工操作里的东西，逐一搬到系统外部。","raw":"这时，Loop Engineering 的六个组件就从抽象名词变成了六个具体的问题。它们把原来散落在人脑和手工操作里的东西，逐一搬到系统外部。"},{"id":"p28","kind":"heading","text":"Automation：并不是「定时调用一次模型」","raw":"### Automation：并不是「定时调用一次模型」"},{"id":"p29","kind":"paragraph","text":"Optio 中的 Automation 是由几样东西配合完成：Task Config 保存「每次做什么」，Trigger 保存「什么时候做」，Trigger Worker 定期检查有没有到期任务。","raw":"Optio 中的 Automation 是由几样东西配合完成：Task Config 保存「每次做什么」，Trigger 保存「什么时候做」，Trigger Worker 定期检查有没有到期任务。"},{"id":"p30","kind":"paragraph","text":"源码里，workflow-trigger-worker.ts 默认每隔一段时间查询到期的 Trigger。当目标类型是 task_config 时，它调用 instantiateTask()。后者会渲染 Prompt 参数、创建一条真实 Task，把状态从 pending 变成 queued，最后放进 BullMQ 队列。","raw":"源码里，`workflow-trigger-worker.ts` 默认每隔一段时间查询到期的 Trigger。当目标类型是 `task_config` 时，它调用 `instantiateTask()`。后者会渲染 Prompt 参数、创建一条真实 Task，把状态从 pending 变成 queued，最后放进 BullMQ 队列。"},{"id":"p31","kind":"paragraph","text":"原来可能会把 Automation 理解成「每天早上八点调用一次模型」。但从这里会发现，一个可靠的 Automation 不是定时器本身，而是「触发条件 + 任务蓝图 + 实例化 + 入队」的完整链路。","raw":"原来可能会把 Automation 理解成「每天早上八点调用一次模型」。但从这里会发现，一个可靠的 Automation 不是定时器本身，而是**「触发条件 + 任务蓝图 + 实例化 + 入队」的完整链路**。"},{"id":"p32","kind":"paragraph","text":"因为闹钟只负责响，它不能直接代替一张工作单。","raw":"因为闹钟只负责响，它不能直接代替一张工作单。"},{"id":"p33","kind":"heading","text":"Worktree：给每个 Agent 一张不会串台的办公桌","raw":"### Worktree：给每个 Agent 一张不会串台的办公桌"},{"id":"p34","kind":"paragraph","text":"任务进入队列后，Optio 不会让所有 Agent 在同一个仓库目录里修改文件。它为每个 Task 创建独立的 Git Worktree。","raw":"任务进入队列后，Optio 不会让所有 Agent 在同一个仓库目录里修改文件。它为每个 Task 创建独立的 Git Worktree。"},{"id":"p35","kind":"paragraph","text":"这一步决定了多个 Agent 能不能真正并行。如果没有 Worktree，就像让三位程序员共用一台电脑、同时编辑同一份文件。Agent 数量越多，冲突反而越快。","raw":"这一步决定了多个 Agent 能不能真正并行。如果没有 Worktree，就像让三位程序员共用一台电脑、同时编辑同一份文件。Agent 数量越多，冲突反而越快。"},{"id":"p36","kind":"paragraph","text":"并行不是多开几个对话框，而是先隔离它们会修改的环境。","raw":"**并行不是多开几个对话框，而是先隔离它们会修改的环境。**"},{"id":"p37","kind":"heading","text":"Skill 与 Connector：一个管「会不会」，一个管「能不能」","raw":"### Skill 与 Connector：一个管「会不会」，一个管「能不能」"},{"id":"p38","kind":"paragraph","text":"在 Agent 启动前，Optio 会向 Worktree 注入两类不同的信息。","raw":"在 Agent 启动前，Optio 会向 Worktree 注入两类不同的信息。"},{"id":"p39","kind":"paragraph","text":"第一类是 Skill：源码中的 buildSkillSetupFiles() 会把技能写成文件，告诉 Agent：这个项目怎么测试、代码规范是什么、哪些目录不能碰、出现某类问题应该按什么步骤处理。","raw":"第一类是 **Skill**：源码中的 `buildSkillSetupFiles()` 会把技能写成文件，告诉 Agent：这个项目怎么测试、代码规范是什么、哪些目录不能碰、出现某类问题应该按什么步骤处理。"},{"id":"p40","kind":"paragraph","text":"第二类是 Connector：Optio 会根据当前仓库、Agent 类型和权限找到可用 Connection，再生成 .mcp.json。Agent 因此可以访问 GitHub、Slack、Linear、数据库等外部系统。","raw":"第二类是 **Connector**：Optio 会根据当前仓库、Agent 类型和权限找到可用 Connection，再生成 `.mcp.json`。Agent 因此可以访问 GitHub、Slack、Linear、数据库等外部系统。"},{"id":"p41","kind":"paragraph","text":"Skill 决定 Agent「应该怎样工作」，Connector 决定 Agent「被允许去哪里工作」。","raw":"Skill 决定 Agent「应该怎样工作」，Connector 决定 Agent「被允许去哪里工作」。"},{"id":"p42","kind":"heading","text":"Sub-agent：不是多叫一个模型，而是把「检查」变成独立任务","raw":"### Sub-agent：不是多叫一个模型，而是把「检查」变成独立任务"},{"id":"p43","kind":"paragraph","text":"这部分是我感受最深的地方。我原来以为 Review Agent 就和普通的 Sub-agent 一样，是由主 Agent 在对话中临时喊来一个小助手。但 Optio 的 Review Agent 不是一次临时调用，而是一条真正写入数据库、可以排队、失败和重试的子任务。","raw":"这部分是我感受最深的地方。我原来以为 Review Agent 就和普通的 Sub-agent 一样，是由主 Agent 在对话中临时喊来一个小助手。但 Optio 的 Review Agent 不是一次临时调用，而是**一条真正写入数据库、可以排队、失败和重试的子任务**。"},{"id":"p44","kind":"paragraph","text":"这相当于把开发和验收完全交给两个人。写代码的 Agent 不能自己说一句「测试通过，完成了」，系统就当真。另一个 Agent 会拿到原始需求、PR 内容、已有评论和测试命令，独立给出审查结果。","raw":"这相当于把开发和验收完全交给两个人。写代码的 Agent 不能自己说一句「测试通过，完成了」，系统就当真。另一个 Agent 会拿到原始需求、PR 内容、已有评论和测试命令，独立给出审查结果。"},{"id":"p45","kind":"heading","text":"真正让 Loop 闭合的，是那位不写代码的「副官」","raw":"## 真正让 Loop 闭合的，是那位不写代码的「副官」"},{"id":"p46","kind":"paragraph","text":"有了队伍，不等于队伍会自己持续向前走。仔细看会发现，这条链路仍然可能只是一条直线：","raw":"有了队伍，不等于队伍会自己持续向前走。仔细看会发现，这条链路仍然可能只是一条直线："},{"id":"p47","kind":"list","text":"- 如果 CI 失败，谁来决定「要不要继续」？\n- 如果 Review 要求修改，谁把反馈交回 Coding Agent？\n- 如果进程中途崩溃，谁知道应该从哪里恢复？","raw":"- 如果 CI 失败，谁来决定「要不要继续」？\n- 如果 Review 要求修改，谁把反馈交回 Coding Agent？\n- 如果进程中途崩溃，谁知道应该从哪里恢复？"},{"id":"p48","kind":"paragraph","text":"上面的组件提供的是循环所需的执行能力，而真正把首尾接起来的，是 State + Feedback + Decision。","raw":"上面的组件提供的是循环所需的**执行能力**，而真正把首尾接起来的，是 **State + Feedback + Decision**。"},{"id":"p49","kind":"heading","text":"1、Reconciler：谁来下达下一道命令","raw":"### 1、Reconciler：谁来下达下一道命令"},{"id":"p50","kind":"paragraph","text":"在 Optio 中，负责把这些能力连接起来的角色叫 Reconciler。也是到这里，我才突然意识到这个项目的名字很有意思：在古罗马军队中，Optio 是协助百夫长维持队伍运转的副手，它会观察队伍、传递命令，并在情况变化时及时补位。","raw":"在 Optio 中，负责把这些能力连接起来的角色叫 Reconciler。也是到这里，我才突然意识到这个项目的名字很有意思：在古罗马军队中，Optio 是协助百夫长维持队伍运转的副手，它会观察队伍、传递命令，并在情况变化时及时补位。"},{"id":"p51","kind":"paragraph","text":"那么是不是可以这么比喻：如果说 Coding Agent 是在前线执行任务的士兵，那么源码中的 Reconciler 就很像站在队伍后方的 Optio——它不亲自修改代码，只负责观察现场、传递命令，再决定下一步行动。","raw":"那么是不是可以这么比喻：如果说 Coding Agent 是在前线执行任务的士兵，那么源码中的 Reconciler 就很像站在队伍后方的 Optio——它不亲自修改代码，只负责观察现场、传递命令，再决定下一步行动。"},{"id":"p52","kind":"paragraph","text":"它反复做三件事。buildWorldSnapshot() 像是重新巡视现场：任务现在处于什么状态？PR 有没有合并？CI 是通过还是失败？Review 有没有要求修改？reconcileRepo() 则根据这些事实，生成下一道命令。源码中的判断很具体：","raw":"它反复做三件事。`buildWorldSnapshot()` 像是重新巡视现场：任务现在处于什么状态？PR 有没有合并？CI 是通过还是失败？Review 有没有要求修改？`reconcileRepo()` 则根据这些事实，生成下一道命令。源码中的判断很具体："},{"id":"p53","kind":"list","text":"1. PR 已合并：把 Task 变成 completed\n2. CI 第一次变为失败：重新唤醒 Coding Agent\n3. CI 通过且开启自动审查：创建 Review Task\n4. Review 要求修改：把评论重新交给 Coding Agent\n5. 自动恢复次数达到上限：进入 needs_attention，等待人处理","raw":"1. PR 已合并：把 Task 变成 completed\n2. CI 第一次变为失败：重新唤醒 Coding Agent\n3. CI 通过且开启自动审查：创建 Review Task\n4. Review 要求修改：把评论重新交给 Coding Agent\n5. 自动恢复次数达到上限：进入 needs_attention，等待人处理"},{"id":"p54","kind":"paragraph","text":"最后，executeAction() 才会真正执行唤醒 Agent、创建 Review Task、自动合并或者结束任务。","raw":"最后，`executeAction()` 才会真正执行唤醒 Agent、创建 Review Task、自动合并或者结束任务。"},{"id":"p55","kind":"paragraph","text":"但这里还有一个问题：Reconciler 每次被唤醒时，原来的 Agent 进程可能早已退出。它怎么知道任务上一次进行到哪里了呢？","raw":"但这里还有一个问题：Reconciler 每次被唤醒时，原来的 Agent 进程可能早已退出。它怎么知道任务上一次进行到哪里了呢？"},{"id":"p56","kind":"paragraph","text":"答案不是 Reconciler 自己拥有记忆，而是系统把现场写在了外部 State 中。","raw":"答案不是 Reconciler 自己拥有记忆，而是系统把现场写在了外部 State 中。"},{"id":"p57","kind":"heading","text":"2、State：这位副官靠什么记住现场","raw":"### 2、State：这位副官靠什么记住现场"},{"id":"p58","kind":"paragraph","text":"Optio 把任务状态写入 Postgres，并通过状态机限制它如何变化。","raw":"Optio 把任务状态写入 Postgres，并通过状态机限制它如何变化。"},{"id":"p59","kind":"paragraph","text":"这里不只保存了一个简单的完成 / 未完成：","raw":"这里不只保存了一个简单的完成 / 未完成："},{"id":"p60","kind":"list","text":"- tasks.state 记录任务现在进行到哪里\n- task_events 记录任务为什么从一个状态进入另一个状态\n- PR、CI 和 Review 状态记录外部世界发生了什么\n- Git 分支和 Worktree 保留 Agent 已经完成的代码修改","raw":"- `tasks.state` 记录任务现在进行到哪里\n- `task_events` 记录任务为什么从一个状态进入另一个状态\n- PR、CI 和 Review 状态记录外部世界发生了什么\n- Git 分支和 Worktree 保留 Agent 已经完成的代码修改"},{"id":"p61","kind":"paragraph","text":"每一次 transitionTask() 也不只是修改一个字段。它还会校验状态迁移是否合法、记录事件，并唤醒下一轮 Reconcile。","raw":"每一次 `transitionTask()` 也不只是修改一个字段。它还会校验状态迁移是否合法、记录事件，并唤醒下一轮 Reconcile。"},{"id":"p62","kind":"heading","text":"3、CAS、Resync 与预算：如何防止「副官」自己失控","raw":"### 3、CAS、Resync 与预算：如何防止「副官」自己失控"},{"id":"p63","kind":"paragraph","text":"如果同时有两个 Worker 下达命令，或者某次唤醒消息丢了，这该怎么办呢？","raw":"如果同时有两个 Worker 下达命令，或者某次唤醒消息丢了，这该怎么办呢？"},{"id":"p64","kind":"paragraph","text":"顺着这个问题继续读源码，这才明白那些一开始让我觉得很繁琐的代码，几乎都在处理这个问题：如果 Loop 在无人盯着时出错怎么办？Optio 加了至少三道护栏。","raw":"顺着这个问题继续读源码，这才明白那些一开始让我觉得很繁琐的代码，几乎都在处理这个问题：如果 Loop 在无人盯着时出错怎么办？Optio 加了至少三道护栏。"},{"id":"p65","kind":"paragraph","text":"第一道是 CAS：更新任务状态时，SQL 会附带「当前状态仍然等于我刚才看到的状态」这个条件。两个 Worker 同时处理同一任务时，只有一个能成功，另一个会重新读取最新事实。","raw":"**第一道是 CAS**：更新任务状态时，SQL 会附带「当前状态仍然等于我刚才看到的状态」这个条件。两个 Worker 同时处理同一任务时，只有一个能成功，另一个会重新读取最新事实。"},{"id":"p66","kind":"paragraph","text":"第二道是 Resync：即使某次消息丢了，系统仍会周期性扫描未完成任务，再次放入 Reconcile 队列。这个很像夜班交接时重新点一遍未结工单，避免某个任务永远躺在角落里。","raw":"**第二道是 Resync**：即使某次消息丢了，系统仍会周期性扫描未完成任务，再次放入 Reconcile 队列。这个很像夜班交接时重新点一遍未结工单，避免某个任务永远躺在角落里。"},{"id":"p67","kind":"paragraph","text":"第三道是预算和停止条件：自动恢复不是无限的，源码会比较 recentAutoResumeCount 与 maxAutoResumes。超过上限，就不再继续烧 Token，而是把任务交回人类。","raw":"**第三道是预算和停止条件**：自动恢复不是无限的，源码会比较 `recentAutoResumeCount` 与 `maxAutoResumes`。超过上限，就不再继续烧 Token，而是把任务交回人类。"},{"id":"p68","kind":"paragraph","text":"能重复运行的脚本，不等于可靠的 Loop。可靠的 Loop 必须知道何时继续、何时重试、何时恢复，以及何时承认自己做不到。","raw":"能重复运行的脚本，不等于可靠的 Loop。**可靠的 Loop 必须知道何时继续、何时重试、何时恢复，以及何时承认自己做不到。**"},{"id":"p69","kind":"heading","text":"最后","raw":"## 最后"},{"id":"p70","kind":"paragraph","text":"再回到开篇的那个问题：Claude Code 明明已经有两层循环，为什么还需要 Loop Engineering？","raw":"再回到开篇的那个问题：Claude Code 明明已经有两层循环，为什么还需要 Loop Engineering？"},{"id":"p71","kind":"paragraph","text":"现在可以这样回答：Claude Code 的 query_loop 解决了一个 Agent 被叫醒之后，如何调用模型、执行工具，再一轮轮把任务做下去；而 Optio 继续追问的是，当这次运行结束以后，谁检查结果，谁记住现场，谁决定要不要再次叫醒它。","raw":"现在可以这样回答：Claude Code 的 `query_loop` 解决了一个 Agent 被叫醒之后，如何调用模型、执行工具，再一轮轮把任务做下去；而 Optio 继续追问的是，当这次运行结束以后，谁检查结果，谁记住现场，谁决定要不要再次叫醒它。"},{"id":"p72","kind":"paragraph","text":"前者让 Agent 能够行动，后者让行动可以跨越一次会话，持续地向一个目标收敛。","raw":"前者让 Agent 能够行动，后者让行动可以跨越一次会话，持续地向一个目标收敛。"},{"id":"p73","kind":"paragraph","text":"Agent 负责向前走，而人负责决定方向，以及哪里才是终点。","raw":"Agent 负责向前走，而人负责决定方向，以及哪里才是终点。"}],"authored_claims":[{"id":"agent-loop-vs-loop-engineering","text":"Agent Loop 负责的是一次运行内部如何行动，Loop Engineering 负责的是多次运行之间如何持续推进工作。判断是不是 Loop Engineering，关键不是代码里嵌套了几个 while，而是看那个负责发现工作、检查结果、记录进度、启动下一轮的人，有没有被系统替代。","stance":"judgment","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-06-29"},{"id":"architectural-outer-is-not-loop-outer","text":"架构上的外层不等于循环上的外层。Claude Code 的 Supporting Systems（权限、Hooks、Memory、Session、MCP）确实包在 Runtime 外面，但它们只是在支撑 query_loop 运行，不会在 CI 失败后主动生成下一次 Agent 任务。","stance":"judgment","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-06-29"},{"id":"automation-is-not-a-timer","text":"可靠的 Automation 不是定时器本身，而是「触发条件 + 任务蓝图 + 实例化 + 入队」的完整链路。闹钟只负责响，它不能直接代替一张工作单。","stance":"judgment","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-06-29"},{"id":"parallel-requires-isolation-first","text":"并行不是多开几个对话框，而是先隔离它们会修改的环境。没有 Worktree，就像让三位程序员共用一台电脑同时编辑同一份文件，Agent 数量越多冲突反而越快。","stance":"judgment","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-06-29"},{"id":"reliable-loop-needs-guardrails","text":"能重复运行的脚本不等于可靠的 Loop。可靠的 Loop 必须知道何时继续、何时重试、何时恢复，以及何时承认自己做不到——对应 CAS 乐观锁、周期性 Resync 扫描和自动恢复次数上限三道护栏。","stance":"judgment","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-06-29"}],"claim_ids":["agent-loop-vs-loop-engineering","architectural-outer-is-not-loop-outer","automation-is-not-a-timer","parallel-requires-isolation-first","reliable-loop-needs-guardrails"],"revision":"2feb68d5c15d8b96de7e286996a40e24c308fd2f75ace0ed0580b3f7217308e3"}