Codex 调度 Loop(调度 / Heartbeat / Resume)
术-运行时 · 术层 skill 全文
本页是 <code>rules/skills/codex_schedule_loop.md</code> 的逐字投影(仅隐私清洗,零改写)。
时点提示:本页是仓内文件 rules/skills/codex_schedule_loop.md 的逐字投影(仅做隐私清洗:仓库根绝对路径→相对路径、家目录→~/;除此零改写)。若源文件后续有修订,以仓内真源为准。
Skill:Codex 调度 Loop(调度 / Heartbeat / Resume)
使用时机
需要选择 Codex App automation、cron/CI、codex exec resume,或检查已有 codex cloud 任务时参考本 skill。典型触发词:schedule loop、定时、heartbeat、automation、自动检查、每天/每周运行、轮询、检查状态、resume、重新开始。
本 skill 只回答运行在哪里、什么时候醒来、怎么查看和恢复。它不定义修改 / 验证 / 保留或丢弃,不定义 requirements verdict,不创建 task card,不决定业务 closure。Codex 自调用、调 Claude Code、以及 Codex runtime execution contract 统一放在 codex_self_evoke_call_claude_code_loop;上层 Controller 任务契约放在 workflow_controller_loop。
目标
为一个 Codex schedule loop 任务选出最小可行调度方式,并留下可检查、可恢复的状态。完成后必须能回答五个问题:什么时候运行、在哪个 workspace 运行、怎么查看结果、怎么继续/重跑、什么条件下停止或上报。
执行模式
| 模式 | 用途 | 状态与检查 |
|---|---|---|
| Codex App thread 自动化 | 同一对话需要按分钟/天/周醒来继续;适合检查长命令、PR 状态、review loop、持续调研 | 当前 thread + Codex App Automations 面板 / Triage |
| Codex App standalone/project 自动化 | 每次 run 独立;适合日报、issue triage、CI 摘要、跨一个或多个 project 的周期性扫描 | Automations 面板 / Triage;Git repo 可选 local 或独立 worktree;必须保留 automation run record |
codex exec + cron/launchd/GitHub Actions | 需要脚本化、文件日志、CI、仓库自管状态机;适合 context-infra 这类已有 cron 管线 | stdout/stderr 日志、--json events、--output-last-message、自定义 state file |
codex exec resume / codex resume | 已有 session 要续接;或失败后带上下文补跑 | 保存 session id/thread name 和 rollout;禁止 --ephemeral |
codex cloud | 已委派到 Codex Cloud 的任务需要列表、状态、diff、apply | 只用于检查已有 cloud task record;不能替代需要保存本地聊天/rollout 的 wakeup fallback |
当前仓库已有两类参考实现:
adhoc_jobs/library_distillation_round2_codex_20260501/LOOP_ENGINE.codex.md:thread heartbeat 唤醒当前 Codex thread,业务状态由磁盘文件恢复;loop_state为awaiting_user时 heartbeat 只报告等待,不继续生成 evidence。adhoc_jobs/library_distillation_workflow_audit_20260430_codex/LOOP_ENGINE.codex.md:CLI fallback 通过codex execrun directory 保留 prompt、events、stderr、last message、exit code。
短周期持续 Controller Loop
当用户要求 sustained Controller Loop、持续测试、监控与控制机制,或 20 分钟级唤醒时,优先使用同一 thread heartbeat,除非用户明确要独立 project/worktree automation。默认 cadence 是每个 Loop Unit 之间约 20 分钟;用户明确指定更长或更短间隔时,以用户指定为准。不要把持续 Loop 设成每天或每周,除非用户明确要求低频运行。
本 skill 只设置调度面。业务状态必须留在任务目录,例如 REQUIREMENTS.md、CONTROL.md、.loop_trace/state.json、loop_state.codex.json、next_prompt.codex.md、PROGRESS.md、HANDOFF.md、evidence/debug/ 和 evidence/reviews/。
20 分钟 heartbeat prompt 必须要求每次醒来:
- 先读取 Controller 状态和
next_prompt.codex.md。 - 如果状态是
awaiting_user、terminal、blocked without allowed action,或存在STOP.codex,先检查 Controller 文件中是否有更新的用户需求解除等待。没有允许的隔离行动时只报告等待/停止原因;如果新需求允许 adhoc/no-mutation/proposal-only 工作,必须把等待降级为 formal-write boundary,并继续按 Controller 更新后的 active 状态推进。 - 只推进少量原子任务,通常 1 到 3 个。
- 记录本轮修改、问题、测试和下一步。
- 写入
round_<N>_file_production与round_<N>_effect_review。 - 用户要求 HTML 时更新
reports/FINAL_REPORT.html,但不能用报告存在替代业务完成判断。
决策规则
- 要保留同一对话上下文:用 thread automation。
- 每次运行应当从新 prompt 开始,或同一 automation 要覆盖多个项目:用 standalone/project automation。
- 需要进入现有 shell/CI/cron 管线,或需要完整日志与状态文件:用
codex exec脚本化。 - 需要继续旧 session:用
codex exec resume <session_id>或交互式codex resume。 - 只需要一次延时命令且不需要 Codex 判断:参考
workflow_delayed_execution,不要升级成 automation。 - 生产脚本里需要 provider fallback、quota 识别或模型 alias:优先走
cli_agent;直接 Codex CLI 保留给边界清晰的 Codex 专项任务。 - 所有会唤起 Codex agent/user-task 的方法都必须保存聊天记录和 rollout;不允许选择
--ephemeral或任何会跳过 session persistence 的路径。 - App 默认列表可见性按任务重要性选择:用户明确要求在 Codex 中显示,或任务是独立重要主线(例如单独 Loop 进程)时,用 Codex App thread/automation 或可确认保存的交互式
codex [PROMPT];从属小任务可以用codex exec,但必须保留 session/rollout 和 run logs。
必需运行契约
创建或实现定时任务前,先把这些字段写进 prompt、automation 描述、wrapper 脚本注释或状态文件:
purpose: 任务要持续完成什么,不要只写「检查一下」。cadence: 运行节奏和时区;sustained Controller Loop 默认每个 Loop Unit 之间约 20 分钟;涉及用户可见时间时用 Europe/Stockholm。workspace: 目标 project / cwd;Git repo 说明 local 还是 worktree。stop_or_report: 什么情况继续沉默,什么情况写 findings / 通知用户 / 停止。observability: 查看 run、日志、结果文件、diff 的入口。resume_plan: 失败后如何续接或重跑;若需要 resume,禁止--ephemeral。chat_record: 本次唤醒会保存哪类聊天记录;至少包括 Codex session id/thread id、rollout_path、source kind 和 run dir。没有聊天/rollout 持久化的启动面不可用。safety: sandbox、approval、网络、文件写入范围。timeout_policy: 单次无人值守 Codex wakeup 的硬超时、终止方式、退出码、重试策略和暂停阈值。stale_lock_policy: 发现旧锁、孤儿进程或长时间无事件增长时如何记录、释放、重试或暂停。
这些字段只描述调度和观测。业务状态、证据判断、issue class 和下一步允许/禁止动作必须写入任务目录的 Controller/runtime 状态文件,不能只写在 automation 描述里。
超时、重试与健康检查
任何无人值守的 codex exec + cron/launchd/CI wakeup 都必须有防卡机制。不能只依赖人手工查看 launchctl print 或 run 目录。
最低要求:
- 每次 run 必须有 wall-clock hard timeout。短 cadence Controller Loop 的默认值建议 45 分钟;成功历史明显更短时可以降低,复杂调研可以升高,但必须写入 run config。
- 超时后 wrapper 必须递归终止
codex exec进程树,等待一个短 grace period,然后必要时KILL。 - 超时 run 必须写
exit_code.txt = 124、last_message.md、timeout_report.md,并在.loop_trace/events.jsonl追加status: timed_out或等价事件。 - 超时后必须释放 lock,让下一次 schedule tick 能重试同一个
next_prompt.codex.md。不要让一个无输出进程占住所有后续 heartbeat。 - 连续超时必须有上限。默认建议 2 次连续 timeout 后写
SCHEDULER_PAUSED_TIMEOUT.codex或等价 pause file,让后续 wakeup 只报告 paused,不再无限重试。 - pause file 必须写明暂停时间、最近 timeout run、恢复前应查看的证据文件,以及删除/恢复条件。
- stale lock 必须可清理:若 lock 超过阈值且 owner pid 不存在,wrapper 应记录
stale_lock_cleared并释放;若 owner pid 仍存在但超时,应先写 timeout evidence 再终止进程树。 - 健康状态不能只看
last exit code。必须同时看当前state、是否有 active pid、当前 run dir 是否持续增长、是否有exit_code.txt/last_message.md、rollout 是否有 assistant/tool events。
状态解释:
| 状态 | 含义 | 允许反应 |
|---|---|---|
exit_code=0 且非空 last_message.md | 本轮完成 | 进入下一轮或等待下一 tick |
state=running 且 run dir 增长 | 本轮仍活跃 | 等待 |
state=running 且超过 timeout、events/rollout 不增长 | 卡死 | 终止进程树,写 timeout evidence,释放 lock |
| 连续 timeout 达阈值 | 系统性故障风险 | 写 pause file,停止自动重试,等待人工或上层 Controller 决策 |
| lock 存在但 owner pid 不存在 | 陈旧 lock | 记录并释放 lock,然后按正常策略继续 |
Codex App 自动化
在 Codex App 里能使用 automation 工具时,优先让 Codex 创建或更新 automation,而不是手写内部配置。描述任务、schedule、是否绑定当前 thread、是否使用 project/worktree、模型/推理强度即可。
如果当前工具面暴露 automation_update,并且用户要求 sustained Loop / schedule wakeup / heartbeat,必须直接创建或更新 thread heartbeat。只写 SCHEDULER_DECISION.md、只描述应当如何调度、或把创建动作推给未来 Agent,都不算完成调度。只有工具不可用、权限不足、用户禁止、或调度会触发明确安全风险时,才允许降级为 verified fallback / manual resume / blocked,并把原因写入 Controller 状态。
线程型 automation 的 prompt 要持久:说明每次醒来要做什么、如何判断没有新发现、何时停止或请求用户输入。项目型 automation 默认把 findings 放进 Triage;如果选择 worktree,定期归档不再需要的 runs,避免频繁 schedule 产生过多 worktree。
创建 automation 前必须确认该面会保留 App thread/run 记录;如果当前工具或权限只能触发一个无聊天记录的后台命令,不得把它登记为 Codex wakeup 方法,只能改走保留 session 的 codex exec wrapper 或等待可用的 App automation。
安全默认值:Git repo 中优先使用 worktree;只有明确希望直接修改当前 checkout 时才用 local。后台 automation 是无人值守执行,默认按当前 sandbox 设置运行;需要写文件、联网或操作本机 app 时,先确认权限与风险边界。
Controller Loop 中的 Codex 调度写法
当 workflow_controller_loop 选择 scheduled_codex_runtime 时,本 skill 只设置调度面。Controller 文件仍是业务真相源。
同一 thread heartbeat 的 prompt 必须包含:
- 先读取
REQUIREMENTS.md、CONTROL.md、.loop_trace/state.json、next_prompt.codex.md或等价恢复入口。 - 如果状态是
awaiting_user、terminal、blocked without allowed action,或存在STOP.codex,只在没有 Controller-approved isolated action 时报告等待/停止原因;formal approval 不应阻止用户已经要求的 adhoc/no-mutation testing。 - 每次实际继续前写 run start receipt,结束前写 terminal/completed receipt。
- 每轮结束后要求 Controller 做文件产生检查和效果审查,再决定下一次 wakeup。
project/standalone automation 的 prompt 必须包含:
- workspace/cwd 和是否使用 local/worktree。
- 输出写到哪个任务目录或 Triage。
- 无发现、发现问题、需要用户输入、失败重跑时分别怎么处理。
- 如何把 run 结果写回 Controller 状态,而不是只留在 automation run 页面。
codex exec + cron/CI 的 wrapper 必须保留 prompt、events JSONL、stderr、last message、exit code、开始/结束时间,并把 resume pointer 写回 Controller/runtime 状态文件。wrapper 禁止传 --ephemeral,也禁止在 run 结束后清理 rollout 或 session 索引。wrapper 还必须有 timeout / retry / stale-lock handling:一个 stuck run 不能阻塞所有后续 wakeup。
本地 scheduler fallback 的健康状态不能只看 job 已注册。launchd / cron / CI wrapper 创建后,至少要完成一次 foreground run 或 launchctl kickstart 等价验证,看到 run directory、exit_code=0、非空 events.jsonl、turn.completed 和非空 last_message,才能写成 verified。若只看到 launchctl print 里有 label 或 interval,状态最多是 created_pending_first_success。脚本语法检查和 plist lint 只是静态检查,不能替代首次成功运行。
macOS launchd runner 用 zsh/bash 时避免使用 shell 保留或特殊变量名作为局部变量,例如 zsh 的 status。失败日志必须保留到 run directory 并写入 Controller/runtime state,避免 silent retry。
本地后台启动要使用真正脱离父 shell 的调度面。临时 agent shell 里的 cmd & 或 nohup ... & 可能会被宿主工具回收,不能作为 fallback 验证依据。macOS 一次性或周期性后台启动优先使用已有 cron/launchd wrapper 或 LaunchAgent plist,并显式控制 KeepAlive / StartInterval / RunAtLoad;不要把 launchctl submit 当作一次性模板,除非 wrapper 有锁、去重和每次唯一 RUN_DIR,因为快速退出的 submitted job 可能被 keepalive/minimum-runtime 语义重复拉起。每次 cron/launchd run 必须分配唯一 run directory,避免重复启动覆盖 events.jsonl、stderr 和 last message。
本地 launchd fallback 的推荐自愈策略是:StartInterval 负责重跑;wrapper 负责超时退出和释放锁。不要在同一个 wrapper 里无限递归自调用。若需要更快重试,可由 wrapper 写 retry_recommended evidence,由外层 scheduler 或人工 launchctl kickstart 触发;连续失败达到阈值后必须 pause。
本地 scheduler fallback 不能长期静默运行。它只能作为 Codex App automation 不可用、权限不足、或用户明确要求本地调度时的降级方案。创建 launchd/cron fallback 时必须同时写明:
- 为什么不能使用 Codex App automation。
- 对应的 LaunchAgent label、plist 路径、runner 路径、run directory、pause file、stop file、卸载命令。
- 何时自动停止:terminal state、
STOP.codex、连续 timeout、无新 Controller action、用户撤销任务、或达到预设最大运行窗口。 - 如何人工停止:至少包含
launchctl bootout gui/$(id -u) <plist>或等价命令,以及确认launchctl list不再包含 label 的检查。 - 如何回收:哪些
.schedule_runs/、.schedule_lock/、.loop_trace/是 runtime artifact,哪些 summary/report 应保留进 Git;不能让 run directory 无界增长。 - 如何避免 Git 噪声:runtime artifact 默认进入
.gitignore或任务目录的 GENERATED/runtime 区;需要入库的报告、决策、evidence 必须显式列出,不得依赖后台git add -A。
如果 fallback 已经被创建但没有这些停止/回收字段,状态只能写成 fallback_created_without_lifecycle_contract,不能写成 verified。后续 agent 的第一步应补齐 lifecycle contract 或停用该 fallback。
CLI 与 Cron 模式
直接用 Codex CLI 时,先以当前机器的 codex exec --help 为准核对 flags,再保持 agent invocation 的 durable output 规则。当前 CLI 的 approval 行为来自配置/profile;不要把旧示例中的 --ask-for-approval 写进新 wrapper,除非本机 codex exec --help 明确支持该 flag。当前 CLI 暴露 --ephemeral,但 scheduler/runtime wrapper 一律不得使用它。
codex exec \
--sandbox workspace-write \
--cd "$WORKSPACE" \
--json \
--output-last-message "$RUN_DIR/last_message.md" \
- < "$RUN_DIR/prompt.md" \
> "$RUN_DIR/events.jsonl" \
2> "$RUN_DIR/stderr.log"生产 wrapper 不应裸跑上面的命令;应把它放进 timeout/watchdog 块。超时退出码统一用 124,并写 timeout report。伪代码:
codex exec ... > "$RUN_DIR/events.jsonl" 2> "$RUN_DIR/stderr.log" &
pid=$!
deadline=$(( $(date +%s) + WAKEUP_TIMEOUT_SECONDS ))
while kill -0 "$pid" 2>/dev/null; do
if [ "$(date +%s)" -ge "$deadline" ]; then
terminate_process_tree "$pid"
echo 124 > "$RUN_DIR/exit_code.txt"
write_timeout_report
release_lock
exit 124
fi
sleep 5
done
wait "$pid"
echo "$?" > "$RUN_DIR/exit_code.txt"续接非交互 session:
codex exec resume "$SESSION_ID" \
--json \
--output-last-message "$RUN_DIR/resume_last_message.md" \
"Continue from the last recorded state. Re-check the current workspace before editing." \
> "$RUN_DIR/resume_events.jsonl" \
2> "$RUN_DIR/resume_stderr.log"云任务检查:
codex cloud list --json --limit 20
codex cloud status "$TASK_ID"
codex cloud diff "$TASK_ID"
codex cloud apply "$TASK_ID"Cloud 命令只检查或应用已有 cloud task。不要把 cloud status check 写成新的 Codex wakeup fallback,除非同时有可追溯的 cloud task record,并且任务自己的 Controller ledger 记录了该 task id。
可观测性
- Codex App:查看 Automations 面板和 Triage;thread automation 还要看当前 thread。
- CLI:至少保留 prompt、events JSONL、stderr、last message、exit code、开始/结束时间。
- Codex 本地线程索引:用
[codex-path]的threads表和rollout_path交叉验证是否入库。持久化的codex execrun 通常是source=exec, thread_source=user;交互式终端codex [PROMPT]通常是source=cli, thread_source=user;Codex App 当前 thread 通常是source=vscode, thread_source=user;Codex App 子 agent 通常是thread_source=subagent且source包含 parent thread。官方 App Serverthread/list在未传sourceKinds时默认只列交互来源cli和vscode,不会默认列出exec;因此 App 聊天列表可见性要求不能靠codex exec满足,除非调用方显式包含sourceKinds=["exec"]或使用 App/CLI 交互来源启动。 - 聊天记录保存验收:每个唤醒/子调用 run 都要能从 run ledger 回到
[codex-path]和 rollout 文件;如果找不到 session id 或 rollout path,本次启动面按未验证处理。 - context-infra:Codex rollout 会被
extract_codex_conversations.py提取到contexts/daily_records/codex/;长期任务的设计和发现仍应落到对应 workspace 文件。 - 自定义 cron:时间线登记在
config/CRONTAB.md;脚本或 wrapper 的实际路径通过tools/INDEX.md查。
来源
- OpenAI Codex 自动化:https://developers.openai.com/codex/app/automations
- OpenAI Codex 非交互模式:https://developers.openai.com/codex/noninteractive
- OpenAI Codex CLI 参考:https://developers.openai.com/codex/cli/reference
- 本仓库 Codex 自调用 / 调 Claude Code / Loop 入口:
codex_self_evoke_call_claude_code_loop - 本仓库 cron 时间线:
config/CRONTAB.md