对比三种触发模式(/loop 云端定时、CI GitHub Actions、云端 Routines),给出按场景选择路由的建议,强调空运行的低成本设计。
TL;DR — 循环之所以成其为循环,在于它按着你没有主动触发的节奏运行。三种心跳:/loop 会话内(你在键盘前)、云端 Routines(cron,笔记本合上)、以及 CI(GitHub Actions)。根据工作在哪里运行、谁需要保持清醒来选择。然后:把发现结果路由到人类会读的收件箱,并让空跑几乎零成本——因为在每日节奏下,空跑才是常态。
这是我的 Loop Engineering 系列文章的第四篇,转载自 ShipWithAI 原系列的第六部分。(我跳过了前几篇:那里的第四和第五篇分别讲状态文件和 worktree,这篇建立在两者之上。链接在末尾。)
阅读完整文章 →
调度是简单的那一半。跑固定脚本 的 cron 任务只是 cron;跑一个决策者的 cron 任务是循环。按谁需要保持清醒来选择心跳:你、不需要任何人、还是 CI runner。
文章开头引用了 Matt Van Horn 的话:循环是 cron 加一个运行体的决策者。调度只是微不足道的那一半。每次运行读取状态,决定做什么,然后更新状态,这样下一次运行从这个停止的地方继续,而不是重复执行一条静态命令。
一个容易遗漏的规划细节:云端 Routines 携带每日可启动运行次数的上限。如果你的成本模型假设可以调高频率,那个上限就是天花板。
钩子是反射;调度是心跳。反射在有事发生时触发。心跳无论有事无事都会触发,这正是为什么定时循环需要 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
一个无人值守的循环可以被允许找到工作、被允许做那些无聊的部分。但永远不允许它决定什么可以合并。收件箱是循环停止、你开始的地方。
文章称自动合并是"无人值守自动化的头号大忌",同时指出收件箱模式是它自己的综合结论而非引用的标准——只是认为它是这篇文章最重要的纪律。发现过程是自动化的;决策权保留在人类。
昨天 CI 里两个测试失败——真实的 pytest 套件,两个独立的真实失败(slugify 留下尾随连字符、load_port 返回字符串而非整数),外加三个 open issues,其中两个对应该失败。
调度,作为一个 GitHub Actions workflow:
name: morning-triage
on:
schedule:
- cron: "0 6 * * 1-5" # 06:00 on weekdays, the heartbeat
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
或者同样的节奏作为一条普通 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 workflow 也不是云端执行的运行。
所以,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-red bugs 优先于仅 issue 的,bugs 优先于 features,前两名获得 lane。Finding #19(一个功能请求)留在收件箱里,没有 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。这是设计。
早晨循环是整个系列的浓缩:调度唤醒它(第六篇),停止条件约束它(第三篇),发现结果落入状态文件(第四篇),前两名获得 worktree lanes(第五篇)。节奏只是新增的部分。
坦白说:这是一个机制验证。既没有可捕获的实时云端 Routine,也没有云端执行的 Actions 运行——第三、四、五篇的 trace 里记录了同样的限制。这里的决策者是确定性排序逻辑,不是 LLM agent 轮次,所以没有任何检测手段,也没有 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
在每日节奏下,空跑才是常态,所以空跑才是你真正的成本。为每次运行设置上限,让"nothing today"几乎零成本,否则心跳会在你睡觉时悄悄耗尽预算。
四个 kill switches,约束每次运行而非仅仅是整个任务:
nightcrawler — 自主夜间研究循环,情景化执行,每轮有 episode 预算
agentics — 将"Continuous AI"框定为 GitHub Actions workflows
Tmux-Orchestrator — 自调度 agent;2025 年中已进入休眠,但有启发意义
按谁需要保持清醒来选择节奏。写发现步骤。然后——在调度任何东西之前——写收件箱,并确认你的循环没有通往合并的路径。
然后计时空跑的耗时,乘以你的频率——工作日早上 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 Pattern(tried/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
下一篇:把所有这些改造到一个真实的、混乱的首个循环上的旅程日志——实践阶段的开始。