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 体系,推进项目.
  • Persistent heartbeat entrypoint: generated by loopx heartbeat-prompt --bootstrap --thin --codex-app; save the v2 loader in the automation. It binds the task's goal and agent and fetches the current contract on every wake.
  • Execution or audit body: generated without --bootstrap. Thin is the normal fresh execution body; compact, brief and full expose other levels of detail. These changing bodies are not the persistent automation prompt.

Prompt compression must preserve both conditions and required responses. A missing concrete user action under NOTIFY calls for state-projection repair, not just a diagnostic label. Under DONT_NOTIFY, repair stays internal; it does not grant notification authority. Keep these semantics in brief and thin prompts as well as the expanded contract, within their existing size budgets.

Reward Memory outcome guidance is a deliberate prompt increment, not a reason to remove the validator, digest/evidence, writeback/readback, zero-provider-call, or private-material boundaries. When automatic outcome ingestion is enabled, the generated heartbeat body receives a fixed +640-character headroom (and the corresponding bounded line/UTF-8 allowance) for this contract. The CLI output differential also grants that allowance once during the none-to-v1 migration; unrelated later growth still uses the ordinary gate. Keep the explanation readable, while visible Goal prompts and other surfaces retain their own limits.

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.

Native Goal bootstrap and live execution instructions

Brief automation now uses the same fully qualified notification/execution rule as thin, including heartbeat_recommendation.agent_must_attempt and execution_obligation.must_attempt_work. Brief no longer embeds a second static refresh/spend sequence: after validated work it follows the current interaction_contract.cli_channel.settlement_plan.ordered_steps, or current next_cli_actions when there is no plan. Generator command fields remain for compatibility, not as a stale fallback. Todo acceptance alone is not Turn settlement or terminal vision closure. The brief budget remains 3,500 characters.

Brief 与 thin 共用完整执行义务路径;这次有意移除 brief 固定结算配方,而不是 删除结算义务。真实 App preflight、registry scope、完整 guard 和静态安全规则 均保留;结算顺序与身份以本轮动态 contract 为准,vision replan 不由历史成功清账。

New supported host activations use heartbeat-prompt --bootstrap: a saved loader requests the installed rules rather than freezing a long execution body. The loader and automation bootstrap share rendering and successful-response checks. Registry-derived state is resolved at load time; explicitly supplied policy remains bound. The inner command does not request another bootstrap. Claude Code loads its inner body through the bound MCP host_prompt tool. TraeX's separate capability projection remains separate, not embedded by this loader. See prompt upgrade lifecycle for automatic exact-managed adoption during update --apply, including the closed-App SQLite/TOML adapter and conflict recovery boundary. Running Apps require their native automation API.

Static semantics retained across hosts

Thinning removes duplicated recipes, not authority boundaries. The shared runtime body keeps repository rules, credential/private-material protection, explicit authorization for destructive Git/production actions, and exception routes (loopx-project for lifecycle/registry, loopx-self-repair for drift). These routes are conditional, not mandatory skill calls on every iteration. Ordinary Claude MCP iterations still use MCP; the CLI route is not a second accounting path.

Semantic Owner after thinning
Privacy, repository rules, dangerous-action authority Shared static safety rule; a trusted host is not blanket permission
Lifecycle or projection repair Conditional static repair route; repair does not bypass gates
Selection, claims, vision replan, exact settlement identities/order Current successful interaction contract, not saved command recipes
Blocked path vs whole Goal Gate only the affected path; continue independent admitted work; only terminal no-follow-up completes the Goal
Git branch/worktree/PR policy User and repository rules; no generic No project branches restriction
Prompt authoring/maintenance advice Documentation, not per-iteration executor instructions

For heartbeat shells, assign LOOPX_TURN in a separate statement before the guard, in the same shell invocation. A command-prefix assignment does not make the variable available to argument expansion in Bash/zsh. Reuse the same value on retries. Native Goal entry remains host-specific and does not inherit this heartbeat bootstrap.

Thin's ceiling is 2,500 characters (previously 1,900), and compact's is 6,500 (previously 6,200): the additional room covers shared safety and an executable Turn/guard block rather than omitting identities or static obligations.

The automation lifecycle is the reference for shared execution, not a wrapper around native Goal behavior. Thin automation and Codex CLI/SSH, TraeX and Ark Managed Agent Goal bodies share quota dispatch: selection/re-entry, admitted work and validation, then the current writeback/settlement instructions. They do not share scheduler ownership, host completion, or blocked/resume rules.

Native Goal bodies share a compact bootstrap. Generate it with the host's existing profile (for example heartbeat-prompt --runtime-profile codex_cli --goal-id <GOAL_ID> --agent-id <AGENT_ID>). The persistent body binds the Goal/Agent and quota entrypoint; each work iteration reads the current complete, successful quota JSON. The inner execution instructions remain in interaction_contract, including selection/re-entry, admitted work, and exact cli_channel.settlement_plan.ordered_steps.

Native Goal bodies no longer embed a second static accountable refresh/spend template. Those command fields remain available in the generator response for compatibility/inspection, but are not a fallback for the live settlement plan. When no ordered settlement plan applies, consume the current next_cli_actions, including any required re-entry; do not substitute a saved generator command. Execute selection/re-entry before work and writeback/spend only after the corresponding validated work; a projected accounting command is not evidence that work happened. Preserve the plan's identity and flags, and follow readback or recovery after an ambiguous write instead of retrying a guessed command. Failed or incomplete contract reads permit neither work nor spend.

An unbound Codex CLI or Ark Goal with selected Todo/replan work now receives a quota re-entry template with --turn-instance-id. Fill it with one public-safe unique work-iteration id and reuse that id on retries. The next packet supplies the same ordered settlement machinery used by automation, with visible-goal attribution. SSH Goal continues to use its existing --begin-turn path. This fixes the previous unbound native refresh/spend projection: those commands could not satisfy the existing settlement identity guard. It does not turn CLI/Ark Goals into App heartbeat receipts or move scheduler ownership into LoopX.

The bootstrap retains work-sizing guidance and the distinction between progress and Goal completion. A new Todo is not a new host Goal; quiet/blocked states are not terminal no-follow-up. Codex alone retains its native blocked/resume rule. User/repository authority still applies; a trusted host is not blanket permission. This changes newly generated native Goal bodies and thin automation dispatch, not active host Goal objectives or benchmark prompts already pinned to a run. An installed runtime supplies updated dynamic contracts on later reads; upgrading it does not retroactively remove old text from an existing Goal.

Claude Code's MCP-backed loop.md follows the same work-sizing rule and current quota contract, without a fixed one-segment limit or empty-Todo-list completion shortcut. Its complete_task tool already owns the ordered writeback/spend transaction: callers must not perform a second accounting sequence through CLI. Partial work is not Todo completion. Native /loop remains Claude's scheduler; only the current Goal's wakeup may be cancelled after terminal no-follow-up. These changes apply when loop.md is regenerated, not by editing active user files. The MCP tool now exposes successor_todo_ids, reusing CLI/TS completion semantics to link known follow-up without creating another Todo. Ordinary acceptance and Turn settlement are distinct: the adapter validates/completes work before its writeback/spend; only terminal closeout requires the full receipt chain. This removes the former delivery-class circular prerequisite, not validation or accounting. See the release test guide.

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.

For a recurring Codex App automation, generate the persistent entrypoint:

loopx --format json --registry <GLOBAL_REGISTRY> heartbeat-prompt \
  --bootstrap --thin --codex-app --goal-id <GOAL_ID> --agent-id <AGENT_ID>

Require ok=true and save the returned task_body through automation_update. It starts with LoopX managed heartbeat bootstrap v2 and loads the current thin contract on every wake. The embedded command omits --bootstrap, preventing recursive loaders. New $loopx App activation and existing automation upgrades use this same renderer. Native /goal hosts retain their own scheduler binding.

--thin, --compact, --brief and --full without --bootstrap return the current execution or audit body; do not save that changing policy as the recurring prompt. Connected goals resolve their active-state path from the registry. Explicit detached-state overrides remain available through --active-state.

Use loopx automation-prompts plan --codex-home <ACTIVE_CODEX_HOME> to inspect existing prompts, then apply the reviewed desired_prompt through the App API. Preserve each automation's own goal/agent/task binding, cadence and notification setting, and read back the result. The offline adapter requires the App closed; while running, its cached scheduler can overwrite direct database/file changes. Installing a binary or changing a TOML file alone does not prove migration.

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> \
  --bootstrap --thin --codex-app \
  --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;
  • when an open advancement Todo reaches an external-only wait before completion, do not end the heartbeat after polling it. Keep it open and atomically bind resume_when=monitor_changed:<monitor-todo-id> plus an independent runnable --successor-todo-id, rerun quota, and continue the successor. The typed wait transition is a no-spend lifecycle repair; only later validated delivery is accountable work;
  • every selected Todo-bound, delivery-enabled must-attempt guard persists closeout_required=true. A fresh heartbeat checks the immediately preceding flagged guard against its exact writeback/spend receipts and typed Todo lifecycle. If neither is present, unsettled_host_turn_recovery_v0 preempts ordinary work selection. The host must repair the prior closeout, rerun the same current Turn, and then continue an eligible successor. Recovery is idempotent and no-spend; receipts created before this explicit flag are not retroactively treated as unsettled;
  • 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.

To inspect the thin execution body for the current turn:

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 --thin --format json emits the versioned heartbeat_agent_input_v1 Agent-input envelope. Its compact interface_budget reports mode, normalized budget_char_count, max_chars, and within_budget; generator diagnostics and duplicate commands stay out of the recurring Agent hot path. Human-readable Markdown and non-thin JSON modes retain the richer generator packet, including char_count and line_count. upgrade-plan --format json also carries that richer 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>.

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 scope-bounded work toward a verifiable result. Size by task, evidence
   and risk, not calls, files or wake cadence. Related implementation, research,
   tests, docs and writeback may form one coherent effort; a focused correction
   can also be sufficient. One operation/writeback is not a stop condition;
   obey budgets, explicit stops, settlement and replan requirements.
5. Execute that scoped work. 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 validation proportionate to the change and risk.
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.
   If the selected advancement Todo now has only an external lifecycle wait,
   keep it open and bind `resume_when=monitor_changed:<monitor-todo-id>` with an
   independent runnable successor before any quiet return. Rerun quota and
   continue the successor; do not spend quota for the wait transition.
   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 --format json --registry "$HOME/.codex/loopx/registry.global.json" quota spend-slot --goal-id <GOAL_ID> --todo-id <SELECTED_TODO_ID> --slots 1 --source heartbeat --execute

   Run it exactly once as rendered; no pipe/filter/retry. If spend output is
   ambiguous, verify with read-only quota status; never rerun.

   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. App-hosted heartbeats should search/use automation_update when available. If scheduler_hint.action=stop_until_explicit_resume and scheduler_hint.app_automation.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.app_automation.stateful_backoff.apply_needed=true and scheduler_hint.app_automation.recommended_rrule is present. After a successful RRULE update, run loopx with scheduler_hint.app_automation.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.app_automation.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:

If automation_update is unavailable in the session and scheduler_hint.app_automation.fallback_hint.available=true, run the bound fallback_hint.cli_args (loopx-apply-rrule) once instead. It backs up codex-dev.db, syncs the automation TOML and SQLite row, and runs the bound ACK; direct SQLite edits bypass the app API, so this is a bounded fallback and never the routine path. The bridge reuses the provided parent Turn for its internal quota should-run query. That same-Turn replay preserves the committed receipt's bound Todo, observed capabilities, and settlement identity; an explicitly conflicting identity still fails closed. When the automation id could not be resolved, fallback_hint.available=false and the pasteable heartbeat gate is the correct stop - never guess an automation id.

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. If
`automation_update` is unavailable and `fallback_hint.available=true`, run the
provided `fallback_hint.cli_args` once instead; if it is unavailable, surface
the pasteable heartbeat gate.

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 --format json --registry "$HOME/.codex/loopx/registry.global.json" quota spend-slot --goal-id <GOAL_ID> --todo-id <SELECTED_TODO_ID> --slots 1 --source heartbeat --execute`
event for the completed turn; run it as rendered, without pipes or filters,
and never rerun it. 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. Ordinary completion links a successor before settlement. Final no-follow-up completion happens only after the accountable refresh and matching spend receipt.
  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, follow the current typed settlement plan. An exact committed receipt-bound monitor poll already closes that Turn with no accountable refresh or quota spend, including a material poll that releases an independent successor. Other accountable delivery refreshes use explicit scale/outcome hints and 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 app_automation.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 app_automation.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.