fix(output): output budgets and receipts measure the delivered bytes (GH-1678, GH-1662, pm-sj66lm)
Summary
Output budgets and their receipts now describe the bytes the caller actually receives. This closes three reports in one lineage: GH-1678 (pm-gh1678), GH-1662 (pm-gh1662) and pm-sj66lm. All three sit under the declared-budget feature pm-5t33or.
The shared cause: each receipt measured a representation the CLI did not deliver. Native TOON was measured while a custom renderer printed the output. Compact JSON was measured while the CLI printed pretty JSON or TOON. An intent receipt was estimated before the read_output receipt and a requested row contract joined the envelope.
Changes
GH-1678: custom renderers and output_format services answer to --output-budget
withDeliveredOutputRenderer(internal,sdk/read-output-budget.ts) scopes every read-output estimate to the renderer the host delivers: a scopedregisterRendereroverride, anoutput_formatservice string, the calendar markdown default, or lean JSON.- A string that cannot fit returns the bounded native
output_budget_exceededrefusal with exit 2. A refusal is always rendered and measured natively. - A renderer's own nested SDK contract calls measure their own representation, never the outer delivery.
deliverContractedReadchecks the final custom rendering against every receipt. When a renderer that depends on its own receipts makes a receipt understate the rendering, the read is measured and rendered natively, so no unmeasured byte is delivered. Bounded estimate fixpoints prevent hangs.- Commands without a
read_outputdeclaration and--output-budget unboundedkeep unbounded custom rendering.
GH-1662: one budget contract for intent reads
- The intent estimate measures the delivered encoding, without a
row_contractthe caller did not request. It is settled over the complete envelope on every path. - A numeric
--output-budgetbinds the intent budget exactly like--token-budget, both through SDK options and through the CLI global flag. The publicapplyContextIntentProjectionsignature is unchanged. - A settled envelope over its intent budget is decided again with the measured overhead charged, so row compaction leaves room for the receipts. A read is omitted only when it still cannot fit.
- Every omitted result exits 2, and
context_intent,read_outputand the omission branch agree.--output-includeshaping and continuation-page collapse never strip a refusal. - The legacy
restore_withcarries the 25% recovery margin and the caller's--fields, so the retry it names returns the result. - New SDK export
isReadResultOmittedForBudgetrecognizes both omission branches (additive surface change).
pm-sj66lm: TOON explicit selection
- A complete omission receipt renders as one line,
omission_receipt: complete. JSON, SDK and MCP keep the typed receipt. get --fieldsrenders each requested empty collection.pm get <id> --fields id,title,commentsdrops from 162 B (receipt share 59%) to 105 B (26%).
Gates
token-budget-gate.mjs: the infeasible-intent negative control now expects exit 2 and reads the receipt from the failed run.context-intent-calibration.jsonre-approved. The only change is the 2,243-item session orientation (9,945 → 8,210 tokens, seen items 111 → 83, suppressed repeats unchanged at 8): each session read now fits its own intent budget over its complete envelope.docs/agent-task-token-baseline.json+1 token for the orientation context read.
Review
Six local Codex rounds (gpt-6.1-sol, xhigh) on this branch found 1, 2, 3, 1, 2 and 4 P2s. All were fixed with regressions and negative controls. Details are on pm-gh1678 and pm-gh1662.
Verification
tests/unit/core/output/delivered-output-budget.spec.tsandtests/integration/read-output/delivered-output-budget.integration.spec.ts:- renderer, service, nested and self-referential renderer cases;
- the budget-spelling matrix;
- executed
restore_withretries; - fit-or-exit-2 budget sweeps;
- shaped and resumed refusals;
- exact session charging;
- TOON selection.
- Full suite: 12,155 tests passed, exact coverage 100/100/100/100.
pnpm quality:static, including the token-budget, calibration, agent-task and defect-evidence gates.- SDK surface and contract snapshots.
Docs
docs/READ_OUTPUT_CONTRACTS.md gains the section "Delivered bytes, intent budgets and complete receipts". docs/SDK.md documents renderer and service measurement and isReadResultOmittedForBudget.
Summary by Sourcery
Make output budgets and receipts consistently reflect the bytes callers actually receive across renderers, intent reads, and TOON output.
New Features:
- Support measuring output budgets against bytes delivered by custom renderers and output-format services.
- Bind numeric --output-budget to intent reads and expose SDK detection for either budget-omission result.
- Render complete TOON omission receipts compactly while preserving explicitly requested empty collections.
Bug Fixes:
- Ensure budget receipts, intent projections, refusals, retries, and session charges accurately describe the complete delivered envelope.
- Make omitted intent results consistently unsuccessful with exit status 2 and preserve refusals through shaping and continuation handling.
- Prevent custom renderers from delivering bytes that exceed or are not covered by their receipts.
Enhancements:
- Add bounded handling for self-referential or non-converging renderer estimates and fall back to native rendering when receipts understate delivery.
- Preserve caller field projections and recovery margins in legacy intent retry commands.
CI:
- Update token-budget gate expectations and calibration data for unsuccessful intent omissions and revised delivered-byte accounting.
Documentation:
- Document delivered-byte measurement, intent budgets, complete receipts, renderer constraints, and the new SDK omission helper.
Tests:
- Add unit and integration coverage for custom renderers, output services, intent budget spellings, retries, shaped and resumed refusals, session charging, and TOON rendering.
Chores:
- Update the changelog and internal issue tracking records for the three related budget and receipt fixes.