Heartbeat Automation Prompt¶
This is the public copy-paste template for a Codex App heartbeat automation that advances one LoopX goal without hiding compute policy inside the timer.
The timer only wakes the executor. LoopX decides whether that wakeup should spend delivery compute.
Two Prompt Layers¶
Keep the Codex App visible goal text and the heartbeat automation task body as two separate layers:
- Visible goal text: short and human-scannable, for example
按 ACTIVE_GOAL_STATE.md,基于 LoopX 体系,推进项目. - Heartbeat automation task body: generated by
loopx heartbeat-prompt, and nearly identical across projects except forgoal_id, optional active-state override, and a small number of project-specific boundary rules. - Compact heartbeat body: generated by
loopx heartbeat-prompt --compact, preferred for live Codex App automations when context pressure matters. It keeps the guard, gate, blocker-push, recommendation, steering-audit, writeback, refresh, and spend rules in the installed prompt, while pointing rare edge branches back to the expanded lifecycle contract. - Brief installed body: generated by
loopx heartbeat-prompt --brief, preferred when an existing automation already has too much prompt weight. It is a thin dispatcher: keep only the goal identity, preflight, quota guard, hard skip/monitor/spend rules, and a compact-contract interface in the installed prompt. Pull extra detail on demand with the compact contract, top-3 status queue, or handoff packet instead of pasting everything into every heartbeat. - Thin dispatcher body: generated by
loopx heartbeat-prompt --thin, preferred as the local machine default when the target Codex agent is trusted to inspect LoopX registry/global quota truth, active state, status/run history, repo state, and project signals at wakeup time. It does not paste command branches into the automation prompt. Normal turns use CLIinteraction_contract; useloopx-projectfor lifecycle/registry andloopx-self-repairfor runtime/projection drift. The CLI payload remains the runtime source of truth. This makes the Codex thread a replaceable worker and leaves durable task truth in LoopX.
Do not paste the full lifecycle protocol into the visible goal text, and do not use a short goal text such as "advance TODO" as the recurring automation body. The short text names the goal; the generated task body enforces quota, gates, steering audit, writeback, refresh, and spend accounting.
Ark Managed Agent is not an automation profile. Its integration uses one transport-neutral goal prompt and lets the goal runtime own inner iteration; see the host integration protocol instead of adapting this recurring automation contract.
For Codex App, the generated quota command carries the compact explicit runtime
profile --runtime-profile codex_app_heartbeat (generated commands use the
equivalent compact alias --codex-app). The prompt does not restate the
three scheduler ownership fields as prose. Other hosts generate their real typed
execution context instead of inheriting App cadence by omission.
Do not hand-edit per-project lifecycle branches into one automation prompt.
Project-specific behavior belongs in the LoopX registry, active-state
sections, adapter output, or narrow boundary rules. If a lifecycle rule is
generally useful, add it to loopx heartbeat-prompt and its smoke
contract so every project inherits it.
Executable versus watch-only agent follow-up should be registered through the
todo CLI (--task-class and optional --action-kind), then projected by
quota should-run; it should not be encoded as benchmark- or project-specific
prompt text.
When the generated prompt and the installed skill disagree, the worker should
trust the current CLI interaction_contract first, then use the skill as the
operation manual and the prompt only as a bootstrap.
Within that contract, the worker should execute
agent_channel.primary_action. An optional agent_channel.resolution_trace
is for debugging route selection and drift; it should not be treated as another
action to run or as permission to rewrite active-state Next Action.
You can generate the task body from the CLI. Prefer the registry-backed form for connected goals, so the installed automation does not hard-code a state-file path that can drift:
If you are testing a detached state file or a goal that has not been connected to a registry yet, pass an explicit override:
For recurring App heartbeats, the default body is the thin local dispatcher. Use the compact body after the full lifecycle has been reviewed when the installed prompt should carry more lifecycle detail inline:
The expanded prompt remains available as the explicit audit source:
The thin prompt is the installed default. It keeps per-tick context small and expects trusted agents to pull the current LoopX state before acting. The compact prompt is the heavier inline lifecycle body.
When multiple agents share the same project control plane, first register the public-safe agent ids on the goal, then give each automation an explicit identity and natural-language scope:
loopx configure-goal \
--goal-id <GOAL_ID> \
--registered-agent codex-main-control \
--registered-agent codex-side-bypass \
--agent-model peer_v1 \
--execute
loopx heartbeat-prompt \
--goal-id <GOAL_ID> \
--compact \
--agent-id codex-main-control \
--agent-scope "benchmark readiness, benchmark execution, and benchmark writeback"
For another peer, use a different id and a disjoint scope:
loopx heartbeat-prompt \
--goal-id <GOAL_ID> \
--compact \
--agent-id codex-side-bypass \
--agent-scope "control-plane coordination and todo claim ergonomics" \
--agent-scope "do not take benchmark execution todos unless reassigned"
The generated body tells the agent to claim only in-scope todos with
loopx todo claim --claimed-by <agent-id>. --agent-scope requires
--agent-id, and the CLI accepts that agent id only when it is registered for
the goal. Scope stays in the automation prompt or handoff; todo metadata records
only the soft claimed_by owner. Registered identities are peers. Functional
profile roles are advisory, while workspace isolation and continuation behavior
come from the selected task, goal policy, and typed continuation policy.
Generated scoped heartbeat commands pass the same --agent-id to both
quota should-run and quota spend-slot, so workspace guards and quota
accounting evaluate the same identity.
Host capabilities are declarations, not permission grants. When the selected Codex App, CLI, or external launcher already has a capability required by its todos, declare it while generating the heartbeat:
loopx heartbeat-prompt \
--goal-id <GOAL_ID> \
--thin \
--agent-id <AGENT_ID> \
--available-capability network \
--available-capability external_evidence_poll
The generated quota guard and spend command preserve the same declarations. Do not declare credentials, production access, or another capability merely to bypass a gate; the launcher must actually provide it.
When a thin prompt is generated without explicit declarations, it still tells
the runtime worker to project non-basic capabilities that are actually present
with --available-capability. This prevents an observed network or polling
capability from becoming a false user gate without guessing capabilities the
host does not have. Explicit generator arguments remain preferred for hosts
whose capabilities are known when the automation is installed.
- for small AGENTS-eligible validated changes, self-merge and complete the todo
with
--self-merged --evidence "<commit and validation summary>"; - for an independent continuation, create
--next-agent-todoand optionally select a registered peer with--next-claimed-by; - when independent review is required, use
--next-action-kind reviewwith an ordinaryindependent_handoff; add--next-excluded-agent <author>only when the author must not reclaim the unclaimed successor; - when a validated pull request merely needs human review, keep the reminder
non-blocking with
--next-user-todo "<review action>"and--next-user-task-class user_action, then create the next runnable agent todo in the same completion. Add a separatecontinuous_monitorfor the PR lifecycle when merge/readback must be observed. Do not turn review latency into a gate; - use
user_gateonly for an exact authority boundary such as approval to merge an aggregate branch intomain, release, launch a benchmark, or perform a protected action; - when work is blocked without a valid successor, keep the todo with the current peer and write a concrete blocker rather than inventing a hierarchy route.
Once a goal has coordination.registered_agents, prompt generation without
--agent-id fails closed. That is the lightweight migration signal for stale
Codex App automations: the next refresh attempt surfaces a concrete
identity/scope upgrade command instead of returning a legacy unscoped prompt.
quota should-run follows the same rule for executor safety: an unscoped call
returns automation_prompt_upgrade.required=true,
blocks_should_run=true, and should_run=false instead of allowing delivery.
For a hierarchy-era registry, quota should-run and upgrade-plan return one
stable migration id, one heartbeat command per registered peer, and a completion
command. The host update may be retried with that idempotency key; the completion
command atomically records the migration once, and later quota checks do not
project it again. A registry without coordination.registered_agents must first
register the peer identity before a scoped prompt can be generated.
If even the compact body is too heavy for an installed automation, generate the brief body:
Use the brief body only when the target agent has LoopX CLI access. It is intentionally small and treats the compact/full lifecycle contract like a skill-style interface that the agent fetches when quota says real work can run or when an edge branch is ambiguous.
For the thinnest local default, generate the dispatcher body:
Use the thin body only when the controller is expected to do a fresh registry/quota/state/status/repo inspection on each wakeup. It should stay project-agnostic; if a behavior needs to be remembered across workers, write it to active state, run history, the registry, or a generated prompt contract rather than hand-editing the automation body.
loopx heartbeat-prompt --format json emits an interface_budget
object for the selected mode. It reports the rendered prompt's char_count,
line_count, normalized budget_char_count, max_chars, and
within_budget. upgrade-plan --format json carries the same budget summary
inside each generated prompt, so local default-promotion checks can flag prompt
bloat without parsing prose or relying on a chat thread.
upgrade-plan --format json also carries a compact prompt_policy_audit for
installed prompts when their body is available through the local Codex App
automation record or an explicit manifest. The audit does not echo the prompt
body. It only reports warning kinds such as a generic should_run=false
hard-stop appearing before safe-bypass handling, embedded project policy blocks,
or pinned --active-state arguments. Any warning should be treated as upgrade
work: regenerate the installed heartbeat from the current CLI contract and keep
project-specific policy in registry/state/status/review-packet payloads.
For gray rollout, generate the brief body through loopx-canary and pass
--cli-bin loopx-canary so only the selected goal controller uses the
live checkout:
Template¶
Replace the placeholders before installing the automation:
<ACTIVE_GOAL_STATE_PATH>: optional override for a detached state file. For connected goals, omit it and let the CLI resolve the registry goalstate_file.<GOAL_ID>: the stable LoopX goal id.<MATERIAL_QUEUE_RULE>: optional project-specific rule such as "do not consume the learning material queue unless the user explicitly asks."
Advance the goal described in <ACTIVE_GOAL_STATE_PATH>.
Generic LoopX lifecycle. Keep project-specific branching out of the
automation prompt. Put local policy in registry, active-state sections, adapter
output, quota should-run.goal_boundary, or boundary rules; if a lifecycle rule
is needed, update loopx heartbeat-prompt so all projects inherit it.
Before spending delivery compute, first make the LoopX CLI reachable in
this automation shell, then run the quota guard:
export PATH="$HOME/.local/bin:$PATH"
install_script="$HOME/loopx/scripts/install-local.sh"
if ! command -v loopx >/dev/null 2>&1; then
if [ -x "$install_script" ]; then
"$install_script"
export PATH="$HOME/.local/bin:$PATH"
else
echo "loopx is not on PATH; clone the LoopX repo and run scripts/install-local.sh" >&2
exit 1
fi
fi
loopx doctor >/dev/null
loopx --format json --registry "$HOME/.codex/loopx/registry.global.json" quota should-run --goal-id <GOAL_ID> --runtime-profile codex_app_heartbeat --turn-instance-id "${LOOPX_TURN:?}"
If that preflight still fails, do not do implementation work, adapter work,
file edits, research, project exploration, or quota spend in this turn. Return
a quiet heartbeat DONT_NOTIFY response with the exact preflight failure reason.
If the result says should_run=false:
- If the payload says state=operator_gate, treat the gate as a user/controller
interaction, not as a silent skip. Read gate_prompt, operator_question,
recommended_action, next_handoff_condition, missing_gates,
user_todo_summary, and agent_todo_summary from the payload. If the same
unresolved gate has not already been asked in the recent visible thread,
return heartbeat NOTIFY with
one concise Chinese question that lists the gate and the expected reply
format. Treat `interaction_contract.user_channel.notify` as the final
notification signal. When it is `NOTIFY`, name concrete projected
`actions`, todos, or questions even when `action_required=false`,
`user_todo_summary.open_count=0`, and `non_blocking=true`; non-blocking means
the agent may continue independent work, not that the user action is silent.
Never say only "owner gate". If required user-facing items are not projected,
say "具体 user todo 未投影,需修复 LoopX 状态投影"; never say "no new user
action" for this case. Only when `notify=DONT_NOTIFY`,
`action_required=false`, and `open_count=0` may the heartbeat say
"无用户待办/无需通知" or stay quiet. Do not execute agent_command, adapter
work, write-control, production actions, or the gated path while asking.
- If the payload says notify_user_on_open_todo=true, treat the existing open
user_todo_summary as a blocker-push opportunity, not as a silent skip. This
is especially important for state=focus_wait, state=waiting, and
waiting_on=external_evidence, where a short user/owner answer can unlock a
quiet project or stop meaningless repeated polling. If the payload explicitly
includes open_todo_notification_policy=repeat_until_resolved, return
heartbeat NOTIFY until the user todo is done, deferred, or replaced. When
user_gate_notification_cooldown.notification_suppressed=true, preserve the
pending gate but return quiet DONT_NOTIFY until its bounded reminder window
or a material gate/host change. Otherwise, if the same blocker ask has not already been surfaced in
the recent visible thread, return heartbeat NOTIFY with one concise Chinese
ask listing at most three first_open_items, the open_todo_notify_reason, and
the expected reply format: done, defer/not now, or a new evidence
link/date/conclusion. Do not do implementation work, adapter work, file
edits, research, project exploration, or quota spend for that blocker-push
turn. If the same non-monitor blocker was already surfaced recently, return a
quiet DONT_NOTIFY skip reason and do not append quota spend.
- If the payload also says safe_bypass_allowed=true and the same gate has
already been surfaced, the gate blocks only the gated delivery path. You may
still read the active state and do exactly one bounded safe-bypass step from
the Priority Stack, such as read-only steering analysis, documentation, or
another P0/P1 item that does not depend on that gate. If you do a safe-bypass
step, validate it, write back progress/critic/next action, refresh accountable
progress, append exactly one spend event, and report compactly. If
`interaction_contract.user_channel.notify=NOTIFY` or
`user_todo_summary.open_count > 0`, include the projected user actions or
todos concretely and do not say there is "no new user action". If
agent_todo_summary.open_count > 0, the report should also name the first safe
agent todo it can execute next. If no useful
safe-bypass step exists, report the pending gate compactly instead of doing
work.
- Give each heartbeat a stable turn id by copying its `<current_time_iso>` into
`LOOPX_TURN`; reuse that id for guard retries in the same
heartbeat. `quota should-run` commits one idempotent receipt for every turn.
If effective_action=monitor_quiet_skip, that same guard idempotently appends
the no-spend stall observation and returns the follow-up decision. Do not
append a second manual monitor poll. If it remains monitor-only, return quiet
DONT_NOTIFY: no delivery edits and no spend. Keep the automation active:
unchanged monitor-only receipts are not self-stop signals. If the guard
reports autonomous_replan_required or another hard replan contract, follow
that contract.
- If waiting_on=external_evidence or state=waiting, and this automation is
explicitly a monitor, run at most one bounded read-only observation poll using
project-approved status/log/metric/marker surfaces named in active state,
recommended_action, or goal_boundary.next_probe. Unchanged evidence: quiet
DONT_NOTIFY, no edits, no spend. New eval/fail/complete/blocker/approval/CI/
deploy/data evidence: report, write back only allowed canonical
state/board/ledger, add todos if needed, then spend once after validation.
Still do not launch/stop/restart/sync/design code or mutate production unless
should_run=true or the user explicitly authorizes it.
- Otherwise, do not do implementation work, adapter work, file edits, research,
or project exploration in this turn. Return a quiet heartbeat DONT_NOTIFY
response with the skip reason.
If the result says should_run=true:
1. Read the active state, Priority Stack, recent progress, and critic.
When you inspect current LoopX routing, use the current status queue:
attention_queue.items and each item's project_asset are authoritative for
owner, gate, waiting party, and next action. If project_asset is absent or
legacy/raw fallback, raw queue fields are not owner/gate/stop authority.
Treat run_history.latest_runs as evidence and drill-down only; it may be
limited by status command limits or filters, so do not decide whether a gate
is pending or approved from latest runs alone. Also inspect goal_boundary and
guard user_todo_summary. Stop for an open user/owner todo only when it belongs
to this goal's guard payload or current project asset and blocks the selected
path; then use the blocker-push pattern above. Dependency or sibling-goal
todos found in `attention_queue.items` should be recorded as dependency
blockers; they must not consume the whole eligible turn; choose a
gate-independent P0/P1/P2 candidate for this goal when one exists.
If `effective_action=outcome_floor_recovery` or
`recovery_delivery_allowed=true` or
`safe_bypass_kind=outcome_floor_recovery`, produce the required
ranker/cross-domain evidence artifact named by `must_advance`, or write back
the concrete blocker. Do not fall through to ordinary delivery, surface
propagation, or synthetic-only chains.
Also read execution_obligation and heartbeat_recommendation from the quota
payload before inventing local automation behavior.
heartbeat_recommendation.notify is only the user-notification policy, not an
execution gate. If execution_obligation.must_attempt_work=true, attempt one
bounded segment even when notify=DONT_NOTIFY; a quiet no-op requires
execution_obligation.must_attempt_work=false and no
notify_user_on_open_todo=true blocker-push notification, such as a verified
mapped_noop_if_unchanged turn. If heartbeat_recommendation says
recommended_mode=run_first_read_only_map,
run exactly its command as a real read-only map, not another dry-run, then
validate/save the read_only_project_map result, refresh accountable progress,
append exactly one heartbeat spend, sync state if needed, and NOTIFY. If it says
recommended_mode=mapped_noop_if_unchanged with stop_if_unchanged=true, and
you find no new user instruction, owner evidence, agent todo, stale source,
or safe handoff, return quiet `DONT_NOTIFY`: do not run, edit, or spend.
Check `delivery_batch_scale`, `delivery_outcome`,
`post_handoff_outcome_gap_streak`, and `handoff_delivery_contract`; for
repeated-small or surface-only loops, obey the contract.
2. Run a short steering audit before choosing work: list at least three
plausible next-action candidates across different P0/P1/P2 lanes when
useful; if the same topic has consumed several recent delivery slices, apply
a continuation check and state why continuing still wins; keep compute quota
separate from focus quota; record any losing high-value candidate that should
not be forgotten. Include a product bottleneck lens: ask whether the core
goal is currently bottlenecked by user experience, agent capability,
evidence quality, adapter readiness, or priority-rule gaps, and promote one
concrete bottleneck candidate when it should outrank the nearest local TODO.
Plan/top todo/route changes need todo/Next Action writeback or
no-writeback rationale.
3. Run the no-progress self-repair check before choosing delivery work. First
obey any machine-readable `autonomous_replan_obligation` or
`execution_obligation.must_attempt_work=true` returned by
`quota should-run`; that hard contract overrides a quiet no-op even when the
heartbeat prompt is short. Inspect recent active-state progress and public
run history for consecutive eligible heartbeat turns. Count a turn as
no-progress only when it produced no substantive artifact, no adapter or
implementation progress, no new gate or user decision, no new validation
signal, and only repeated status/brief-check/compact-checkpoint state edits.
Treat `quota_monitor_poll` events as no-spend stall evidence for this guard.
If 2 consecutive eligible heartbeats are no-progress loops, run one bounded
self-repair/replan segment before another quiet no-op. Delete or pause the
automation only when that repair path is itself stuck for 2 more eligible
turns, do not append a quota spend for the self-cancel turn, and return
NOTIFY explaining that the automation was cancelled because it was spinning
without progress.
4. Choose one bounded, verifiable progress segment from that audit. It may be a
coherent batch across related implementation, test, doc, and state-writeback
files when the write scope is clear and validation is explicit; it should not
be forced into a tiny single-file step.
5. Do that segment only. Stay inside goal_boundary when present and keep
public/private boundaries intact. Public-safe repo publication is not an
operator gate by itself: for routine public project work, commit, push, and PR
creation may proceed autonomously after validation and a clean public/private
boundary scan. Stop and surface a user/controller gate only for private or
company-internal material, credentials, destructive git operations,
production actions, or repository rules that explicitly require review.
6. Run the smallest useful validation.
7. Write back changed files, validation, critic, and next action to the active
state. If a user/owner todo appears, do not hide it in prose:
`loopx todo add --goal-id <GOAL_ID> --role user --task-class user_gate --blocks-agent <agent-id>`
or `loopx todo add --goal-id <GOAL_ID> --role user --task-class user_action`.
Use `--role agent` for project-agent follow-up work.
For non-trivial feature slices, complete the current todo only after adding
a successor todo, or include a compact no-follow-up rationale.
For the full field contract, see `docs/project-agent-todo-contract.md` in
the LoopX checkout.
8. After validation and other writeback complete, record this turn's accountable
delivery before spending:
loopx refresh-state --goal-id <GOAL_ID> \
--classification <PUBLIC_SAFE_PROGRESS_CLASSIFICATION> \
--delivery-batch-scale <ACTUAL_DELIVERY_BATCH_SCALE> \
--delivery-outcome <ACTUAL_DELIVERY_OUTCOME>
Replace all three placeholders with values proven by this validated turn.
Never default or upgrade smaller/preparatory work to
`multi_surface` / `outcome_progress`.
This refresh is the causal delivery record consumed by `quota spend-slot`.
A plain state-only refresh is quota-neutral and cannot replace it. Then, for
a minute-based heartbeat, spend one slot:
loopx --registry "$HOME/.codex/loopx/registry.global.json" quota spend-slot --goal-id <GOAL_ID> --slots 1 --source heartbeat --execute
If the automation reserves a coarser fixed interval, set `--slots` to the
number of scheduler minutes consumed by that completed turn.
Do not append spend for quiet should_run=false skips, preflight failures,
pure dry-run previews, or duplicate accounting attempts. If
should_run=false but safe_bypass_allowed=true and you actually completed a
bounded safe-bypass step, append this same spend event once after
validation/writeback.
9. If the dashboard or controller needs a state-only update after
spend, run:
loopx refresh-state --goal-id <GOAL_ID>
Do not emit another accountable progress refresh after spend; that would
create a new unspent delivery record.
10. Return a compact final report. Use heartbeat NOTIFY only for meaningful
user visibility, such as a committed artifact, a user gate, a real blocker,
or the automation self-stop. Otherwise use DONT_NOTIFY.
<MATERIAL_QUEUE_RULE>
Do not ask for permissions when the current Codex session is already trusted.
Minimal User-Facing Form¶
When creating a heartbeat in Codex App, keep the visible instruction short and
put the lifecycle in the automation task body. The default onboarding cadence
starts at 3 minutes; after the first guard, follow
quota should-run.scheduler_hint to back off long waits and stop external loops
after a final quota/replan check confirms repeated unchanged polls. Codex App
heartbeats should search/use automation_update when available. If
scheduler_hint.action=stop_until_explicit_resume and
scheduler_hint.codex_app.host_action=pause_or_delete_current_heartbeat: in
that terminal case, call automation_update once to pause the current
heartbeat (delete only if pause is unavailable), verify the host result, spend
no quota, and end the turn without a scheduler ACK. Otherwise call it only when
scheduler_hint.codex_app.stateful_backoff.apply_needed=true and
scheduler_hint.codex_app.recommended_rrule is present. After a successful
RRULE update, run loopx with
scheduler_hint.codex_app.ack_hint.cli_args; current payloads use
quota scheduler-ack-current so LoopX re-reads the latest hint and owns the
progression/reset state. The ACK settles that RRULE; an immediate final guard
may verify the same target but must not be treated as another elapsed poll.
Attempt the host update at most once per hint and
turn. If it fails or times out, do not retry or ACK; run
scheduler_hint.codex_app.failure_hint.cli_args once to persist the failed
target and observed host RRULE without spending quota. Exact repeats are then
suppressed until either value changes. Continue any allowed delivery under the
observed host cadence. When the desired RRULE is already applied, skip
automation_update; if stateful_backoff.ack_needed=true, run the bound ack
hint directly, otherwise do nothing. For the uniquely matched current heartbeat,
quota should-run reconciles the installed RRULE with the ACK ledger; a
host_observation.status=drift_detected result reopens apply_needed:
Create a heartbeat automation starting at 3 minutes for the current thread;
then apply `quota should-run.scheduler_hint`: update RRULE only when
`apply_needed=true`, trying once per hint and turn; ack with the provided
`ack_hint.cli_args` only after the host update succeeds, or run the provided
`failure_hint.cli_args` once if that update fails or times out.
Task:
Advance <GOAL_ID> using <ACTIVE_GOAL_STATE_PATH>. Before any delivery work,
export `$HOME/.local/bin` onto PATH and run `loopx doctor`; if the CLI is
still unavailable, quietly report that preflight failure and do no work. Then
copy this trigger's `<current_time_iso>` into `LOOPX_TURN` and run
`loopx --format json --registry "$HOME/.codex/loopx/registry.global.json" quota should-run --goal-id <GOAL_ID> --runtime-profile codex_app_heartbeat --turn-instance-id "${LOOPX_TURN:?}"`. If it
returns `should_run=false`, ask about operator gates with NOTIFY using
`gate_prompt` unless the same unresolved gate was already surfaced recently. If
the payload says `notify_user_on_open_todo=true`, ask up to three open
`user_todo_summary` items as a blocker-push NOTIFY and do not spend quota for
that blocker-push turn. If
`open_todo_notification_policy=repeat_until_resolved`, repeat
that NOTIFY until the todo is done, deferred, or replaced. If
it returns `should_run=true` with `effective_action=outcome_floor_recovery` or
`recovery_delivery_allowed=true`, run only the bounded evidence/blocker
recovery before any ordinary delivery. If
it returns `state=operator_gate` plus `safe_bypass_allowed=true`, avoid the
gated command and do at most one independent read-only steering/analysis step
after the gate has already been surfaced.
If it returns `should_run=true`, first check `effective_action`, then compare candidate next actions across
the priority stack, use `attention_queue.items` / `project_asset` as the current
routing authority; if project_asset is absent or legacy/raw fallback, raw queue
fields are not owner/gate/stop authority. Treat `run_history.latest_runs` only as evidence,
read `goal_boundary`, check whether this goal's own open `user_todo_summary` is
a blocker-push opportunity for a gate / focus_wait / external-evidence wait,
record dependency or sibling-goal todos without letting them consume the whole
eligible turn,
apply a continuation check for
repeated topics, then read `execution_obligation` and `heartbeat_recommendation`:
when `execution_obligation.must_attempt_work=true`, do one bounded progress
segment even if `heartbeat_recommendation.notify=DONT_NOTIFY`; quiet no-op
requires
`execution_obligation.must_attempt_work=false` and no
`notify_user_on_open_todo=true` blocker-push notification. Run
`recommended_mode=run_first_read_only_map` as one real read-only map and spend
once after validation; for `recommended_mode=mapped_noop_if_unchanged`, return a
quiet no-op without another dry-run, file edit, or quota spend when no new
instruction/evidence/todo/stale source/safe handoff exists. Check
`delivery_batch_scale`, `delivery_outcome`,
`post_handoff_outcome_gap_streak`, and `handoff_delivery_contract`; for
repeated-small or surface-only loops, obey the contract. Then obey any
machine-readable `autonomous_replan_obligation` or
`execution_obligation.must_attempt_work=true`; if 2 consecutive eligible
heartbeats are no-progress loops, run bounded self-repair/replan before another
quiet no-op. Do one bounded verifiable progress batch when a real boundary
exists: implementation, validation, docs, and state writeback may belong in the
same batch. Do not stop at the first tiny substep when the validation/writeback
boundary is already clear. Validate it, write back changed files / validation /
critic / next action; for non-trivial feature slices, create a successor todo
or write a compact no-follow-up rationale; append one accountable
`refresh-state --delivery-outcome outcome_progress`, then exactly one
`loopx --registry "$HOME/.codex/loopx/registry.global.json" quota spend-slot --goal-id <GOAL_ID> --slots 1 --source heartbeat --execute`
event for the completed turn. Only an optional state-only refresh belongs after
spend. Use `--slots 1` for minute-based heartbeats; for coarser intervals,
spend the scheduler minutes consumed by that turn.
Agent Checklist¶
For every automatic heartbeat turn, the agent-facing checklist is:
- Guard first:
quota should-run --turn-instance-id <HEARTBEAT_TURN_ID>using this trigger's<current_time_iso>and reusing it for same-heartbeat retries. Ifloopxis not initially on PATH, export$HOME/.local/bin:$PATHand run the local installer fallback before declaring preflight failure. - If
should_run=falsewithstate=operator_gate, ask the user/controller the current gate unless the same unresolved gate was already surfaced recently. Every heartbeat guard commits one idempotent receipt. Ifeffective_action=monitor_quiet_skip, it also commits the no-spend stall observation and returns the follow-up decision; do not append another manual poll. Return quietDONT_NOTIFYif it remains monitor-only. Keep monitor todos visible but do no delivery edits and no spend until material evidence changes or the guard exposesautonomous_replan_required/execution_obligation.must_attempt_work=true. - If
notify_user_on_open_todo=true, ask up to three open user todos as a blocker-push notification and do not spend quota for that blocker-push turn. Ifopen_todo_notification_policy=repeat_until_resolved, repeat the notification until the todo is done, deferred, or replaced. Ifuser_gate_notification_cooldown.notification_suppressed=true, keep the gate pending but return quietDONT_NOTIFYuntil its bounded reminder window or a material gate/host change. Otherwise, ordinary blocker-push asks may be de-duplicated when the same blocker was surfaced recently. - If
effective_action=outcome_floor_recoveryorrecovery_delivery_allowed=true, treatshould_run=trueas a recovery turn: run only the bounded evidence/blocker recovery and spend only after validated writeback. - If the gate was already surfaced and
safe_bypass_allowed=true, either take one independent safe-bypass step or report the pending gate compactly. - If the current goal is eligible, dependency or sibling-goal open user todos must not stop the whole turn; record or surface them, then keep looking for a gate-independent P0/P1/P2 candidate for the current goal.
- Run the steering audit before choosing the work.
- Use
attention_queue.items/project_assetas current routing authority; if project_asset is absent or legacy/raw fallback, raw queue fields are not owner/gate/stop authority. Userun_history.latest_runsonly as evidence or drill-down. - Follow
execution_obligationbefore deciding quiet no-op:heartbeat_recommendation.notifyis not an execution gate. Ifmust_attempt_work=true, do one bounded progress batch or segment even whennotify=DONT_NOTIFY; quiet no-op only whenmust_attempt_work=falseand nonotify_user_on_open_todo=trueblocker-push notification is pending. Then followheartbeat_recommendation: first connected read-only goals should run one realread-only-map, while already mapped unchanged goals should return a quiet no-op without another dry-run or quota spend. - Check
delivery_batch_scale,delivery_outcome,post_handoff_outcome_gap_streak, andhandoff_delivery_contract; for repeated-small or surface-only loops, obey the contract. - Start bounded self-repair/replan if 2 consecutive eligible turns are only repeated no-progress status loops. Cancel or pause instead of spending only if that repair path is itself stuck for 2 more eligible turns.
- Nontrivial done -> successor todo or no-follow-up rationale.
- Plans/top todos/route changes need LoopX todo / Next Action writeback or a no-writeback rationale.
- Treat routine public commit, push, and PR creation as autonomous after clean validation and a public/private boundary scan; stop for private/company material, credentials, destructive git, production actions, or repo rules that explicitly require review.
- Work bounded when
should_run=true; a coherent implementation/test/doc/state batch is preferred over a tiny substep when scope and validation are clear. - Validate before reporting.
- After validation/writeback, refresh accountable progress with explicit delivery scale/outcome hints, then spend exactly once against that record.
- Refresh state-only metadata after spend only when needed; never emit another accountable progress refresh after accounting.
- Report compactly.
This prompt is intentionally a lifecycle template. Scheduling policy lives in
quota should-run.scheduler_hint, so per-project heartbeats, a shared
controller loop, Codex CLI TUI, Claude Code loop, or future Codex goal-mode
automations can all share the same LoopX quota guard without hard-coding
different wait loops. Host implementations should first honor a terminal
codex_app.host_action=pause_or_delete_current_heartbeat by stopping the
current heartbeat once, verifying the result, and ending without scheduler ACK
or quota spend. Otherwise they should read the compact
codex_app.stateful_backoff packet, call automation_update only when
apply_needed=true, and then let quota scheduler-ack-current persist the
applied RRULE state from the latest scheduler hint without spending quota. A
matching reset readback may instead set ack_needed=true; in that case skip the
host write and execute the bound ack directly.