fix(agent): bound managed staging by reclaiming superseded deltas - #4610
Conversation
Every frame staged by the managed attempt transaction carries the whole accumulated partial twice (as `message` and as `assistantMessageEvent.partial`), so staged bytes grow quadratically with the streamed response length. A reasoning-heavy turn of a few thousand tokens therefore exhausted the 16 MiB provisional cap and terminated the run with `Managed fallback attempt exceeded the provisional event buffer limit`, even though no single event came close to the cap and the attempt itself was healthy. Raising the cap only moves the threshold, because the growth is quadratic. Reaching the cap now reclaims the staged `*_delta` increments first. Each increment's complete value is re-published by its block's retained `*_end` frame and by the terminal `message_end` / `done` frames, so the retained replay still delivers the full turn. Only a batch that still cannot fit after reclamation fails. Attempt semantics are unchanged: nothing is published early, so a discarded attempt stays unobservable; the local overflow still carries no transport facts and never consumes the fallback chain; and a single oversized event keeps its pre-clone rejection.
Reconstruct PR Yeachan-Heo#4610 onto dev 6696988: keep the managed-staging compaction fix's changelog entry under [Unreleased]; dev rolled 0.14.0 after the PR base, so the auto-merge would otherwise land the new bullet inside the released 0.14.0 section.
Yeachan-Heo
left a comment
There was a problem hiding this comment.
Independent adversarial review of PR #4610 (fix(agent): bound managed staging by reclaiming superseded deltas).
Defect independently reproduced: applied only the PR's regression test to parent commit c83ffe3 and it fails with the exact user-facing string Managed fallback attempt exceeded the provisional event buffer limit; on the fix head it passes. Quadratic staged-byte growth (each staged frame carries the whole accumulated partial twice) is real, and managed fallback is the default session path whenever a fallback chain has >= 2 entries, so this kills ordinary long reasoning turns.
Correctness audit of the compaction design: all four reclaimed delta types (text_delta, thinking_delta, reasoning_summary_delta, toolcall_delta) have their complete value re-published by retained frames — text_end/thinking_end/reasoning_summary_end carry full content, toolcall_end carries the full parsed tool call, and terminal done/message_end carry the full message (verified in packages/agent/src/proxy.ts). The committed message is built from the live provider partial, never from staged frames, so no content can be lost. Lossless staging path untouched. Attempt atomicity preserved: compaction never publishes, only frees accounting.
Verification on exact submitted head cc48e54: bun test packages/agent/test/ 783 pass / 0 fail (clean env; the single remote-compaction failure seen in a polluted shell is ambient OPENAI_BASE_URL leakage, unrelated and passing standalone), bun --cwd=packages/agent run check (biome + tsc) clean.
Verification on fix-forward head f83d08b (merge of cc48e54 into dev 6696988): agent suite 783/783, package check clean, bun scripts/changelog-history-guard.ts pass, targeted managed-attempt-transaction/agent-loop/verify-pr-verdict tests 112/112.
Maintainer fix-forward applied under maintainerCanModify (one merge commit, no code changes): dev rolled 0.14.0 after the PR base, so the original head's changelog entry would land inside the released section on merge. The merge resolves packages/agent/CHANGELOG.md so the new bullet sits under ## [Unreleased]; git diff origin/dev f83d08b5 -- packages/agent/CHANGELOG.md is exactly one insertion. Contributor commit cc48e54 is preserved verbatim as the first parent.
Advisory (non-blocking, strictly better than today's hard failure): on threshold-crossing turns the replayed delta stream becomes a subsequence, so chunk-folding consumers (e.g. ACP agent_message_chunk) see a suffix-only live stream for those turns; full content is still delivered by the retained *_end/message_end frames, and those turns previously failed the entire run.
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (59 pass) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (59 pass) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (59 pass) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (59 pass) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 10000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 4 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (61 pass, incl. ceiling clamp + zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 10000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 4 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (61 pass, incl. ceiling clamp + zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 10000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 4 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with Yeachan-Heo#4610 (superseded-delta reclamation) semantically: - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (61 pass, incl. ceiling clamp + zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…achan-Heo#4610) Managed fallback staged every streaming frame with the WHOLE accumulated partial carried twice (once as message, once as assistantMessageEvent.partial), so staged bytes grew quadratically with response length; a reasoning-heavy turn of a few thousand tokens crossed the 16 MiB provisional cap and killed the run with 'Managed fallback attempt exceeded the provisional event buffer limit' even though the attempt itself was healthy. Reaching the cap now reclaims the staged *_delta increments first — each one's complete value is re-published by its block's retained *_end frame and by the terminal message_end/done frames — and only a batch that still cannot fit fails. Nothing is published early (discarded attempts stay unobservable), the overflow still carries no transport facts (fallback chain never consumed), and a single oversized event keeps its pre-clone rejection. Lossless staging is untouched. Original fix and regression test by @chlee1001 (cc48e54); maintainer fix-forward merged dev 6696988 in to keep the changelog entry under [Unreleased] after the 0.14.0 roll (changelog-history-guard verified; released section byte-identical). Verified: regression test fails on parent c83ffe3 with the exact user-facing string and passes on head; bun test packages/agent/test/ 783/0; bun --cwd=packages/agent run check clean; Dev CI green on f83d08b. Co-authored-by: Chaehyeon Lee <chlee1001@users.noreply.github.com> (cherry picked from commit 44d7b6e)
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 2000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 1 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: every retained batch item is measured and charged BEFORE retention — stageAssistantMessageEvent now counts its assistant pair, so actual retention cannot exceed the caps and the typed overflow fires before the allocation that would OOM (review 2) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (62 pass, incl. exact-at-ceiling + above-ceiling clamp, charged-batch adversarial growth, zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 2000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 1 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: every retained batch item is measured and charged BEFORE retention — stageAssistantMessageEvent now counts its assistant pair, so actual retention cannot exceed the caps and the typed overflow fires before the allocation that would OOM (review 2) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (62 pass, incl. exact-at-ceiling + above-ceiling clamp, charged-batch adversarial growth, zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ble via env Managed fallback stages streamed events in a provisional buffer whose caps were hardcoded (10,000 events / 16 MiB). Reasoning-heavy streaming can trip the cap during transient provider failures, and the resulting ManagedAttemptBufferOverflowError terminates the whole agent turn — in gjc team runs this leaves workers dead at the prompt (Yeachan-Heo#4618). Expose both caps as operator knobs, read once per transaction: - GJC_FALLBACK_MAX_STAGED_EVENTS (default 10000, ceiling 2000000) - GJC_FALLBACK_MAX_STAGED_BYTES (default 16 MiB, ceiling 1 GiB) Rebuilt onto current dev by the maintainer from the original submission (a050ca4), resolving the overlap with - keep the exported MANAGED_ATTEMPT_MAX_STAGED_* constants as the canonical defaults and feed them to $envpos from @gajae-code/utils, so exponents, hex, and unsafe integers can no longer silently unbind the memory guard (positive integer, digits only; anything else silently falls back to the default) - read the caps through field initializers; the reclamation logic from Yeachan-Heo#4610 is untouched and still runs before the configurable caps reject a batch - #wouldOverflow is shared by managed and non-managed lossless staging, so the knobs bound the provisional transaction in both modes; in non-managed sessions the cap only decides how much reasoning buffers before the batch flushes and streams through (Yeachan-Heo#4601) - port the env tests without as-any casts: events-cap trip, bytes-cap trip, "0" -> default, and 3e0/0x3 digits-only rejection; scrub both env keys in afterEach so baseline tests stay host-env independent - document both knobs in the agent/runtime toggles section with the dual-mode scope and silent-fallback contract; add the agent CHANGELOG [Unreleased] entry alongside the Yeachan-Heo#4610 sibling entry Lore-id: 4602-fixforward Constraint: managed overflow stays fatal/non-retryable (1b4fc60); knobs move only the threshold Constraint: hard ceilings clamp above-ceiling overrides with a warning (review: the guard must never be disableable; GJC_SESSION_CONTEXT_BUDGET_BYTES convention) Constraint: every retained batch item is measured and charged BEFORE retention — stageAssistantMessageEvent now counts its assistant pair, so actual retention cannot exceed the caps and the typed overflow fires before the allocation that would OOM (review 2) Constraint: lossless flush-on-overflow (Yeachan-Heo#4601) and superseded-delta reclamation (Yeachan-Heo#4610) unchanged Tested: bun test packages/agent/test/managed-attempt-transaction.test.ts (62 pass, incl. exact-at-ceiling + above-ceiling clamp, charged-batch adversarial growth, zero/negative/non-numeric fallback) Tested: bun test packages/coding-agent/test/agent-session-fallback-attempt-transaction.test.ts (20 pass) Tested: bun --cwd=packages/agent run check; bun --cwd=packages/agent run test (793 pass) Tested: verify-gjc-state-writers --fail; changelog-history-guard; git diff --check Confidence: high Scope-risk: narrow Reversibility: easy Co-authored-by: Veritas-7 <koreacmc.kr@gmail.com>
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
…ow (#4618) ManagedAttemptBufferOverflowError surfaced as one static sentence, so a subagent killed by the provisional staging cap was indistinguishable from a provider or context-window failure — the exact misread reported in recordored." and the error text carried no stage, counts, or limits. The overflow now reports its shape everywhere it can reach: - agent: the typed error carries stage, staged event/byte counts at rejection, and both caps; the message keeps its stable prefix (session retry policy prefix-classifies on it) and appends a shape-only parenthetical stating this is a local staging-buffer limit that reproduces on re-issue, not a provider/context-window failure. Baked into the error itself because the non-retryable local exit path surfaces the thrown error, not the managedFailureMessage wrapper. - coding-agent: the executor retains a bounded, redaction-safe localErrorSummary (closed kind set, sanitized summary) from the subagent's terminal assistant error; receipt preview, errorSummary, subagent tool output, and the await renderer name the local kind and carry the diagnostic instead of the generic error preview. Does not touch cap configurability (#4602) or superseded-delta reclamation (#4610). Lore-id: 4618-buffer-diagnostics Constraint: message prefix must stay byte-identical for session prefix classification Constraint: diagnostics must be shape-only (no provider/prompt text can reach a parent receipt) Constraint: do not duplicate #4602 configurability or #4610 delta reclamation Rejected: enriching managedFailureMessage only | the non-retryable local exit surfaces the thrown error, not the wrapper Rejected: enlarging the caps | workaround path ships with #4602 Confidence: high Scope-risk: moderate Reversibility: trivial Tested: byte-cap + event-cap surfaced diagnostics (agent), propagation, redaction, foreign-kind degradation, generic-error fallback isolation (coding-agent) Not-tested: live provider delta-storm reproduction Closes: #4618
What
Managed fallback stages a whole attempt in
ManagedAttemptTransactionso a discarded one stays unobservable. Every staged streaming frame carries the WHOLE accumulated partial twice — once asmessage, once asassistantMessageEvent.partial— so staged bytes grow with the square of the response length.Reaching the cap now reclaims the staged
*_deltaincrements first; only a batch that still cannot fit fails. Each increment's complete value is re-published by its block's*_endframe and by the terminalmessage_end/doneframes, and those are retained, so the replayed sequence still delivers the full turn.Unchanged by design:
witnessReads === 1contract)replays mutating provider partials as event-time snapshotsand the degraded-snapshot sibling still pass unmodified)Why
A reasoning-heavy turn of a few thousand tokens crossed the 16 MiB cap and killed the run with
Managed fallback attempt exceeded the provisional event buffer limit, even though no single event came close to the cap and the attempt itself was healthy.Measured on 0.13.3 before the fix, driving the real
agentLoopwith a mock provider:fallbackManaged: falseField case: session with profile chain
zai/glm-5.2:medium->commandcode-goat/meta/muse-spark-1.2-contributor. Three consecutive turns died on this error; the turns that succeeded were 1,636 and 1,731 output tokens, and the dead ones streamed 45-70s before reportingusage=0.Related, but neither covers this:
zai/anthropic-messages, which never touches the Codex websocket path.Testing
Added
completes a long managed stream by reclaiming superseded increments instead of failing: 200 x 1 KiB increments stage ~40 MiB uncompacted. It fails on the parent commit with the exact user-facing string, and passes with the fix:It also pins that the fallback chain is never consumed (
outcomeCalls === 0), that increments were actually reclaimed (fewer replayedtext_deltathan pushed), and that the retained path still delivers the block's complete content (text_start/text_endpresent,text_end.contentis the full text).bun test packages/agent/test/managed-attempt-transaction.test.ts-> 55 pass / 0 fail (all 54 pre-existing assertions unchanged)bun test packages/agent/test/-> 783 pass / 0 failbun --cwd=packages/agent run check-> biome + tsc cleanScope note on the root
bun run check:ts: it does not pass on a pristinedevworktree either, before this change —packages/coding-agent/src/internal-urls/gjc-protocol.ts(21,51): error TS2307: Cannot find module './docs-index.generated'(generated module absent untilgenerate-docs-indexruns). A second failure,telegram-daemon-generation-guard.test.tsreporting ENOENT for a file that exists, appears only under the parallel run and passes standalone in the same tree. Neither touchespackages/agent.GJC verdict
devbun checkpasses — scopedbun --cwd=packages/agent run checkis clean; the root check is red on pristinedevfor the unrelated reasons aboveMaintainer fix-forward (2026-08-18): dev rolled
0.14.0after this PR's base, so merging the original head verbatim would have placed the new changelog bullet inside the released## [0.14.0]section. Head is advanced by one merge commit (f83d08b596dcccff5a6e070ee1ecf3da065f0cea) merging dev6696988b6din and resolvingpackages/agent/CHANGELOG.mdso the bullet sits under## [Unreleased](diff vs dev: exactly one insertion;changelog-history-guardpasses). The original contributor commitcc48e54b0dis preserved verbatim as the merge's first parent — code and tests are untouched. Digest above is recomputed for the new exact head with the gate's own command (git diff --binary --full-index --no-ext-diff <merge-base>...<head>).