Repository navigation
Enforce context-window admission check on report requests (#5157) - #5210
Merged
Merged
Conversation
Owner decision D33 (2026-10-25): admission check now. At the 128k report
cap (D28) a 1M window leaves 872k tokens of input. Kids First's measured
regate input was already 880,265 tokens at the 96k cap, and the regate
also carries the report it rejected. The receipt completion and the typed
audit stages count before they send; the report family did not.
Why: a request that cannot fit should be refused before it is sent, with
its numbers, and not discovered as a provider error after the usage call
has begun. On the regate path that error was caught by
`_regenerate_report`, so the run completed with the unregated report and
no recorded reason.
On shared generation 1, `_call_with_usage` now counts the exact request
`_call_with_retry` will send for reconcile_full, report, report_regate and
report_after_repair. It uses the receipt completion's helpers: the
single-attempt client, `messages.count_tokens` and `_context_check`,
against the registration's `audit_limits` window, as the typed stages do.
When count + max_tokens exceeds context_limit_tokens it raises
UsageLedgerError before the usage call begins. Nothing is sent, and there
is no pending call, usage row, abandoned attempt or consumed regate
allowance. `_generate_phase` and `_regenerate_report` both re-raise it, so
MAX_ATTEMPTS is not spent, and a resume recounts the same request and
refuses it again. The batch canary records it as it records the other
admission refusals ("it raised UsageLedgerError"). The dollar readout is
unchanged and prices nothing for a refused call. An admitted count is
pinned as `{project}_{phase}_context.json` in the snapshot index and the
provenance record's intermediates. The receipt completion module gains
`_measure_context`, the count before its verdict, and a `label` on its
messages so the refusal names the phase and its numbers; its own messages
are unchanged. Shared generation 2 already counts every ordinary call and
is untouched.
reconcile_full is included although #5157 names only the report family.
D28 raised it to 128k with the report (`SHARED_PHASE_MAX_TOKENS`), and it
carries the same records, audit findings and schema context, so the same
872k room applies. An over-window reconcile_full already ended the run
with a provider error; the check changes only that it stops before
sending, with the count, at the cost of one unbilled count call.
A refused regate or report refresh after repair now stops the run, where
before the request was sent and any error it drew left the stale report
in place. Re-sizing the AI_READI and KIDS_FIRST inputs, #5157's other
proposed fix, is still open.
No model-facing bytes change. The CHORUS fixture's reconcile_full, report
and regate requests hash the same at the base f8c5ac7 and here, and the
same with the admission bypassed (tests/test_report_admission.py).
Refs #5157, #5112
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eview) Why: review R9 of 225a3cc (verdict "fix first") found four problems. A refused regate or refresh after repair raised out of the run with nothing recorded. The regate re-refused on every resume, so the run could never publish, and the refresh left a resume that died on the receipt-completion boundary. The CHORUS parity test hashed the author's checkout path, so it failed in every other checkout and in CI. A single transient error on the unbilled count stopped a run whose dispatch would have retried it. A refused request now raises ContextNotAdmitted, a UsageLedgerError that carries what the refusal rests on: the phase, the reason, the count, max_tokens, the window and its basis, and the request hash. The refused count is pinned as {project}_{phase}_context_refused.json. reconcile_full and the first report still stop the run before dispatch, because there is no artifact to keep. A refused regate is now treated as a regate that failed (#967). The report as written stays, and report_gate records regenerated false, regeneration_attempted false (the allowance is unspent), the reason and a regate_refused block. The run then writes its record before the post-record checks decide (#1818). A refused refresh after repair is now latched as a terminal evidence refusal, report_refresh_not_admitted with its numbers. A resume re-raises it before any drift logic (#1820). The report as written describes the records before repair, and the gate's source review refuses it in any case, which is how the parent ended the same request when the provider refused it. report_regenerated_after_repair also states whether the refresh was written. The count now uses the same transport as the call it admits: the caller's client, with the transient errors _call_with_retry retries retried up to MAX_ATTEMPTS, with the same backoff and rate-limit pause. Both now use one _transient classification. Non-transient errors and unusable counts still refuse at once. The parity test now normalises the checkout the schema resolved from as well as the run root. The constants were re-derived from f8c5ac7 in a separate tree and match the reviewer's. Typed stage counts now carry their stage's label, and an over-window count states its numbers. New tests cover the regate, refresh, count-retry and resume paths. Not done. Progress is not pinned between repair and refresh: the refresh trigger and the repair log live in that invocation's memory, so a pin alone would let a resumed run skip the refresh and drop the log. A refused regate is not kept in the ledger; a resumed gate recounts the same request, unbilled, and reaches the same recorded outcome. Refs #5157, #967 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #5210 / #5157, finding M1: use the same-tree admission-on/off wire test from #5212 head 6041adf. Omit only the D57 monitor opt-in because this packet has no latch-journal fixture; #5212 retains that opt-in when refreshed. The exact-wire canary passes without changing protected production code or prompt pins.
realmarcin
added a commit
that referenced
this pull request
Oct 29, 2026
realmarcin
marked this pull request as ready for review
October 30, 2026 16:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #5157 (
D33) report-family context-window admission check onclaude/5157-report-admission(0674193f60ca895c7c2acff5b6bc7b4274be9c94, stacked onclaude/dc-a13-receipt-floor-v301264370e2ca51312cb534d42a4c7b9f2721b1c6) and resolves all findings from independent review R9 (pr5157_225a3cce4_review_2026-10-26.md, F1–F8):src/data_sheets_schema/report_admission.pyenforcingcount_tokens + max_tokens <= context_limitprior to provider dispatch acrossreport,report_after_repair,report_regate, andreconcile_full(plus pre-fullsizing verification).usage_ledger.pyandprovenance.admission_checks.Verification
pytest tests/test_report_admission.py: 43 passed.