Skip to content

fix(heartbeat): hint deferred prompt upgrades through budgeted turn-start hooks - #4440

Merged
huangruiteng merged 8 commits into
mainfrom
codex/automation-prompt-obligation
Sep 15, 2026
Merged

huangruiteng merged 8 commits into
mainfrom
codex/automation-prompt-obligation

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Change

When loopx update apply cannot migrate an eligible automation prompt, retain a compact private receipt and inject a repair hint only while that same automation still runs the old body.

  • Fixed loading through the existing typed turn-start capability hook in the shared live decision path. The hint uses the existing Agent/CLI required_reads channel and points to a fresh, automation-scoped automation-prompts plan.
  • The receipt contains identity, the old prompt digest and CLI routing. It stores no prompt body or host update request. Registry/home isolation, exact thread binding and agreement between SQLite/TOML prevent stale or ambiguous adoption hints; RRULE changes do not erase the reminder.
  • No pending receipt means no host-store read or hook dispatch. An adopted/customized prompt stops producing a hint. Observation is read-only; normal quota, scheduler, notification and authority decisions remain unchanged.

No new prompt template, permanent heartbeat instruction, skill clause, scheduler action or standalone capability is introduced. Detection remains update-time; arbitrary external edits are reviewed through the existing plan command.

Hook-owned prompt budget

The active hint declares prompt_budget_bytes=1536. The existing typed hook contract admits at most 2048 bytes per read. Only an emitted hook read carries that allowance into the Agent/CLI command projection and adds it to the Turn envelope's 8192-byte budget. Inactive hooks contribute zero; existing unbudgeted reads retain their 360-character projection. This budget grants no execution or quota authority.

Validation

  • 55 final focused Python hook/live-decision/output tests passed; the existing lifecycle and additional quota/scheduler suites also passed.
  • 27 TypeScript capability-hook/envelope tests passed; npm run typecheck:control-plane passed.
  • Strict mypy for the new hook and Ruff for changed Python paths passed.
  • Heartbeat prompt smoke, CLI output budget regression and control-plane maintainability ratchet passed.
  • Public boundary scan and git diff --check passed.
  • Real isolated SQLite/TOML stores, the actual quota should-run / plan CLI, current/pending parity, other-host isolation, stale/custom/deleted/ambiguous entries and long command projection are covered.

Entry points: Codex App heartbeat quota and shared managed-Turn decision. No new configuration or frontend control is needed: the existing Agent/CLI required-read channels carry the hint; other host decisions retain parity.

Qualification and merge hold

  • Exact scope: db82c25987380dd8bb713a72e5128bba3b0b456f52f605d556243ac62e90f3be, 12 files; head 7dd4332b8, immutable baseline c979cf11c.
  • One authorized simplification pass; no source findings. Receipt cqr_db82c25987380dd8bb71 records the remaining failed installer validation and is not a passing merge qualification.
  • The broad premerge run completed 17/18 selected checks successfully, plus all four direct checks. examples/install-local-smoke.py:367 fails its materialized_skill_ids assertion. The same assertion fails on an isolated clean c979cf11c baseline and on the final head. There are no test skips.
  • The budget changes were revalidated with the final focused Python/TypeScript suites, TypeScript compile, mypy/Ruff, hot-path/output-budget smokes, maintainability ratchet and public-boundary scan. Default command/envelope budgets and non-hook behavior retain parity.

PR remains for review; merge is held on the pre-existing installer validation failure.

Update-time reconciliation can only report a prompt migration it cannot write
while the Codex App is running; the pending request lived in that one command's
stdout. Record it per lane under the runtime root, with the same reviewed
prompt-only request update-time already builds, so the obligation survives the
report that discovered it.

- share automation_update_request between the plan/reconcile paths instead of
  rebuilding the same App request inline
- resolve only this host home's records, so one runtime root fronting several
  Codex homes keeps its other pending adoptions
- drop a record once its reviewed body is installed, without re-classifying
  any lane

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The heartbeat contract has to describe the automation the host actually runs. A
recorded pending adoption is projected as
scheduler_hint.app_automation.prompt_adoption with the reviewed prompt-only
automation_update request, its no-spend policy, and both prompt digests, so the
turn applies it once and reads the automation back without spending quota.

The projection reuses the existing payload obligation channel rather than
adding a scheduler-hint parameter, and a satisfied record stops projecting
itself once the exact body is installed.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The agent-facing CLI output differential measures the thin heartbeat prompt
markdown against a 32-character hot-path allowance. Spelling the new
obligation as `prompt_adoption=adopt(no-spend); ` grew those rows by 33
characters in all three thin variants (small, multi_agent, crowded), which
fails `examples/control_plane/cli-output-budget-regression-smoke.py` and the
kernel static checks job.

Keep the clause and the no-spend marker, but drop the `prompt_` prefix inside
the thin variant so the obligation costs 27 characters and leaves headroom.
The full `prompt_adoption` field name stays visible in the compact and
application rule variants and in the `scheduler_hint` payload.

Validation: heartbeat-prompt-smoke.py, cli-output-budget-regression-smoke.py
and cli-output-base-head-differential-smoke.py (base=102 candidate=102) pass;
test_prompt_adoption_projection.py, test_automation_prompt_upgrade.py and
test_cli_output_budget.py report 70 passed.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Hot-path prompt budget fix (head 4d11f774c)

The first head (80ca91509) turned kernel-static-checks, checks, pytest and merge-gate red. Root cause was mine, not a flake:

examples/control_plane/cli-output-base-head-differential-smoke.py (invoked by examples/control_plane/cli-output-budget-regression-smoke.py) measures the thin heartbeat prompt markdown against a 32-character hot-path allowance. My thin-rule clause prompt_adoption=adopt(no-spend); added 33 characters, so three rows failed with chars grew by 33; allowance is 32: surface/heartbeat_prompt_thin/{small,multi_agent,crowded}/markdown. The remaining three failures were downstream gates (checks requires KERNEL_RESULT=success, pytest requires CHECKS_RESULT=success, merge-gate rejects any non-success need).

Fix

  • loopx/control_plane/heartbeat/rules.py: the thin variant now states the obligation as adoption=adopt(no-spend); (27 characters, 5 under the allowance) instead of prompt_adoption=adopt(no-spend); . The no-spend marker and the clause position are unchanged; the full prompt_adoption field name remains in the compact and application rule variants and in the scheduler_hint payload (app_automation.prompt_adoption).
  • examples/control_plane/heartbeat-prompt-smoke.py: the two thin-variant assertions that pinned the old wording were updated to the new clause. That smoke is the guard on the rendered prompt, so it had to move with the rule.

No production logic, schemas, projection fields or payload keys changed in this commit.

Validation at 4d11f774c

Check Result
examples/control_plane/cli-output-base-head-differential-smoke.py ok — base=102 candidate=102 candidate_only=0 review_required=0
examples/control_plane/cli-output-budget-regression-smoke.py ok
examples/control_plane/heartbeat-prompt-smoke.py ok
pytest tests/control_plane/test_prompt_adoption_projection.py tests/control_plane/test_automation_prompt_upgrade.py tests/control_plane/test_cli_output_budget.py -q 70 passed

Failures or skips: none in the set above. The earlier, unrelated examples/install-local-smoke.py failure reported for the previous head is pre-existing on origin/main and is not touched by this change. Host-side readback of the projected app_automation.prompt_adoption on a real Codex App wake remains a manual hold for the operator.

…pt-obligation

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…pt-obligation

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…ng turn-start hook

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng huangruiteng changed the title fix(heartbeat): keep and project an unapplied automation prompt adoption fix(heartbeat): hint deferred prompt upgrades through budgeted turn-start hooks Sep 15, 2026
@huangruiteng
huangruiteng merged commit 934085c into main Sep 15, 2026
22 of 23 checks passed
@huangruiteng
huangruiteng deleted the codex/automation-prompt-obligation branch September 15, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant