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-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; - 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_v0preempts 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_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.
To inspect the thin execution body for the current turn:
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:
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>.
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:
- 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.
- Ordinary completion links a successor before settlement. Final no-follow-up completion happens only after the accountable refresh and matching spend receipt.
- 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, 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.
- 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
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.