site logo

Marico's space

让你的 Agent Loop 保持心跳:可控成本的计划自动化

AI技术与应用 2026-08-25 14:49:32 9

最近折腾了 AI Agent Loop 的计划自动化,踩了几个坑,这篇把问题说清楚。

一个 Loop 能叫 Loop,得靠你没手动触发它、它自己在跑。三种心跳方式:会话内 /loop(你在键盘前)、云端 Routines(cron 定时任务,笔记本合上)、CI(GitHub Actions)。选哪个取决于工作在哪、谁需要醒着。然后:把发现的结果发到人工审核的收件箱,让空跑几乎不花钱——因为按天跑的节奏,空跑才是常态

这是 Loop Engineering 系列的第四篇,摘自我在 ShipWithAI 上的第六部分原版文章。(我跳着写了:那边的第四、五篇讲状态文件和 worktree,这篇基于这两篇。链接在底部。)

阅读完整原文

三种心跳

心跳方式 触发条件 笔记本状态 最佳场景
会话内 /loop 提示词按间隔重新运行 开着,你守在键盘前 需要盯着的巡视任务
云端 Routines 5 字段 cron,在 Anthropic 基础设施上运行(有每日启动次数上限) 合上 真正的夜间或早晨定时任务
CI(持续集成,GitHub Actions) 工作流中的 on: schedule 或 push 触发 关着(在 runner 上运行) 工作就是代码库、团队在 CI 里协作

定时任务只是简单的一半。cron 跑固定脚本那叫 cron;cron 跑一个决策者才叫 Loop。按谁需要醒着来选:你自己、没人、还是 CI runner。

文章开头引了 Matt Van Horn 的话:Loop 就是 cron 加上一个决策者作为主体。定时任务那部分很简单。每次运行读取状态、决定做什么、更新状态,这样下次运行就能从上次的断点继续,而不是重复跑一个静态命令。

一个容易被忽略的细节:云端 Routines 有每日启动次数上限。如果你的成本模型假设可以随便提高频率,上限就是天花板。

Hook 还是 Schedule(钩子还是定时)?

触发类型 触发时机 例子 是钩子还是定时
生命周期 会话中发生了某事 某个工具运行了;有人尝试停止 钩子(Hook)
时间 时钟跳动,不关其他事 工作日 06:00;每次 push 定时(Schedule)

钩子是本能反应,定时是心跳。本能反应在有事发生时触发。心跳不管有没有事都触发——这正是为什么定时 Loop 需要 triage 收件箱。

很多 Loop 两者都用:定时任务唤醒 Loop,钩子控制 Loop 内部的每一轮。

Triage 收件箱——让这套系统安全运转的部分

scheduled run | |-- findings? --> inbox (state file / issue / PR) --> human decides --> merge | (the loop never merges its own findings) | '-- nothing? --> archive "nothing today" --> exit cheap

无人值守的 Loop 可以找活干,也可以做那些无聊的部分。绝对不允许决定什么能合并上线。收件箱是 Loop 停手、你接手的地方。

文章把自动合并称为"无人值守自动化的头号大忌",同时指出 inbox 模式是综合权衡的结果而非引用的标准——只是作者认为这是文章中最重要的原则。发现自动化,判断权归人。

早晨 Triage Loop

昨天 CI 里有两条失败的测试——真实的 pytest 测试套件,两个独立的问题(slugify 留下尾随连字符、load_port 返回字符串而不是整数),还有三个 open 的 issues,其中两个对应那两个失败。

定时任务,用 GitHub Actions 工作流表达:

name: morning-triage
on: schedule: - cron: "0 6 * * 1-5" # 工作日 06:00,心跳 workflow_dispatch: {}
jobs: triage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run morning triage (discover, write inbox, open lanes) run: python3 triage/triage.py --budget-runs 1 --max-lanes 2

同样的节奏,用 plain crontab 一行搞定:

0 6 * * 1-5 cd /home/you/p6-triage && /usr/bin/python3 triage/triage.py --max-lanes 2 --budget-runs 1 >> $HOME/.morning-triage.log 2>&1

现在说点实在的,先读这段再往下看:没有任何定时任务实际触发过。沙箱里没有 cron daemon,所以 cron 那行——你实际会安装的那个——从来没被 cron 触发过。取而代之的是直接运行了 cron 那行调用的精确 triage 步骤,这跟心跳怎么触发结果一样。Actions 工作流同理:不是云端执行的运行。

所以,手动触发了 triage 步骤——注意它拒绝做什么:

$ python3 triage/triage.py --max-lanes 2 --budget-runs 1
TRIAGE: 3 findings; opened 2 lanes; 1 left in inbox; passed=0 (never auto-merge) $ git worktree list
/home/you/p6-triage 242b6dc [master]
/home/you/wt-finding-14 242b6dc [triage/finding-14]
/home/you/wt-finding-15 242b6dc [triage/finding-15]

排序逻辑:CI 标红的 bug 优先于只有 issue 的,bug 优先于 feature,前两名开 lane。Finding #19(一个 feature 请求)留在收件箱里,没有 lane。

状态文件,用第四篇的 tried/passed/open/blocked 格式:

# Loop State: morning triage (Part 4 schema) ## tried
- read CI: 2 failing test(s); read issues: 3 open
- opened 2 worktree lane(s) for the top 2 finding(s) ## passed
- (none; triage never merges; a human decides what ships) ## open
- [#14 CI-red: tests/test_slugify.py::test_trailing_hyphen] lane opened: /home/you/wt-finding-14 on triage/finding-14 (awaiting human)
- [#15 CI-red: tests/test_config.py::test_port_is_int] lane opened: /home/you/wt-finding-15 on triage/finding-15 (awaiting human)
- [#19 [feature] add a --version flag to the CLI] in inbox, no lane (below top-2) ## blocked

## passed 部分是空的,这不是 bug,是设计。

早晨 Loop 就是整个系列的精华一次跑完:定时任务唤醒它(第六篇),停止条件约束它(第三篇),发现结果写进状态文件(第四篇),前两名开 worktree lane(第五篇)。节奏感是唯一的新东西。

坦白说:这是机制证明。云端 Routines 或云端执行的 Actions 运行在无头环境下无法干净地捕获——跟第三、四、五篇的 trace 记录限制一样。这里的决策者是确定性排序逻辑,不是 LLM agent 的 turn,所以没有 instrumentation,也没有 token 或成本数字可以报告。

空跑才是你的真实成本

干净早晨的情况:

$ python3 -m pytest -q
.. [100%]
2 passed in 0.00s $ python3 triage/triage.py --max-lanes 2 --budget-runs 1
EMPTY RUN: 0 findings -> archived 'nothing today', exit cheap

按天跑的节奏,空跑才是常态,所以空跑就是你的真实成本。每个运行都设上限,让"今天无事"几乎不花钱,否则心跳会在你睡觉时悄悄烧预算。

四个保险开关,约束每次运行而非整个任务:

保险开关 约束什么 防止什么失败
--max-runs 每次调用的迭代次数 无限重跑循环
--max-cost 每次运行的费用上限 夜间账单失控
--max-duration 每次运行的墙上时钟时间 永不退出的卡死运行
--stall-threshold 无进展的迭代次数阈值 空转不收敛

值得一看的先行方案

  • nightcrawler — 自主夜间研究 Loop,带每次运行的 episode 预算
  • agentics — 把"持续 AI"框架为 GitHub Actions 工作流
  • Tmux-Orchestrator — 自定时 agent;2025 年中已停止维护,但有参考价值

这周就试试

按谁需要醒着来选节奏。写发现步骤。然后——在定时任务跑起来之前——先写 inbox,确认你的 Loop 没有自动合并的路径。

然后计时空跑,乘以你的频率——工作日 06:00 一年大约 261 次。每日节奏把成本乘以频率,空跑才是你要乘的那个数。

这是摘要版本。完整文章有逐步构建、worktree 清理、cron vs Routines vs CI 的 FAQ:

👉 Scheduled Agent Automation: Give Your Loop a Heartbeat — Part 6, ShipWithAI

如果想看完整构建,先读跳过的两篇——这篇文章依赖它们:Part 4 — State File Patterntried/passed/open/blocked 格式)和 Part 5 — Git Worktrees for Agents(lanes 的实现)。

系列前篇:Part 1 — Why You Should Stop Prompting · Part 2 — Anatomy of a Loop · Part 3 — Stop Conditions

下篇预告:把这些东西回填到一个真实的、混乱的第一个 Loop 的旅程日志——实践阶段的开始。