Skip to content

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 for goal_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 CLI interaction_contract; use loopx-project for lifecycle/registry and loopx-self-repair for 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:

loopx heartbeat-prompt --goal-id <GOAL_ID>

If you are testing a detached state file or a goal that has not been connected to a registry yet, pass an explicit override:

loopx heartbeat-prompt \
  --goal-id <GOAL_ID> \
  --active-state <ACTIVE_GOAL_STATE_PATH>

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:

loopx heartbeat-prompt \
  --goal-id <GOAL_ID> \
  --compact

The expanded prompt remains available as the explicit audit source:

loopx heartbeat-prompt \
  --goal-id <GOAL_ID> \
  --full

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-todo and optionally select a registered peer with --next-claimed-by;
  • when independent review is required, use --next-action-kind review with an ordinary independent_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 separate continuous_monitor for the PR lifecycle when merge/readback must be observed. Do not turn review latency into a gate;
  • use user_gate only for an exact authority boundary such as approval to merge an aggregate branch into main, 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:

loopx heartbeat-prompt \
  --goal-id <GOAL_ID> \
  --brief

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:

loopx heartbeat-prompt \
  --goal-id <GOAL_ID> \
  --thin

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:

loopx-canary heartbeat-prompt \
  --brief \
  --cli-bin loopx-canary \
  --goal-id <GOAL_ID>

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 goal state_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:

  1. 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. If loopx is not initially on PATH, export $HOME/.local/bin:$PATH and run the local installer fallback before declaring preflight failure.
  2. If should_run=false with state=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. If effective_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 quiet DONT_NOTIFY if it remains monitor-only. Keep monitor todos visible but do no delivery edits and no spend until material evidence changes or the guard exposes autonomous_replan_required / execution_obligation.must_attempt_work=true.
  3. 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. If open_todo_notification_policy=repeat_until_resolved, repeat the notification until the todo is done, deferred, or replaced. If user_gate_notification_cooldown.notification_suppressed=true, keep the gate pending but return quiet DONT_NOTIFY until 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.
  4. If effective_action=outcome_floor_recovery or recovery_delivery_allowed=true, treat should_run=true as a recovery turn: run only the bounded evidence/blocker recovery and spend only after validated writeback.
  5. If the gate was already surfaced and safe_bypass_allowed=true, either take one independent safe-bypass step or report the pending gate compactly.
  6. 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.
  7. Run the steering audit before choosing the work.
  8. Use attention_queue.items / project_asset as current routing authority; if project_asset is absent or legacy/raw fallback, raw queue fields are not owner/gate/stop authority. Use run_history.latest_runs only as evidence or drill-down.
  9. Follow execution_obligation before deciding quiet no-op: heartbeat_recommendation.notify is not an execution gate. If must_attempt_work=true, do one bounded progress batch or segment even when notify=DONT_NOTIFY; quiet no-op only when must_attempt_work=false and no notify_user_on_open_todo=true blocker-push notification is pending. Then follow heartbeat_recommendation: first connected read-only goals should run one real read-only-map, while already mapped unchanged goals should return a quiet no-op without another dry-run or quota spend.
  10. 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.
  11. 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.
  12. Nontrivial done -> successor todo or no-follow-up rationale.
  13. Plans/top todos/route changes need LoopX todo / Next Action writeback or a no-writeback rationale.
  14. 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.
  15. 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.
  16. Validate before reporting.
  17. After validation/writeback, refresh accountable progress with explicit delivery scale/outcome hints, then spend exactly once against that record.
  18. Refresh state-only metadata after spend only when needed; never emit another accountable progress refresh after accounting.
  19. 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.