Skip to content

fix(compaction): recover an over-window restored session at resume admission - #1538

Merged
code-yeongyu merged 4 commits into
mainfrom
fix/1524-resume-over-window
Sep 10, 2026
Merged

code-yeongyu merged 4 commits into
mainfrom
fix/1524-resume-over-window

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

A session whose restored transcript is larger than the model's context window could not be reopened at all: resume admission threw ModelUsabilityBudgetError inside createAgentSession before any extension was wired, so neither compaction nor its recovery paths could run (#1524, reported with live 965,016 tokens against an 850,000-token window). The compaction-eligible resume branch only relaxes admission while summarization still fits, and the compaction-required admission from #1517 deliberately excluded exactly this band.

Resume now reduces the live context deterministically, without any provider request, before the session opens. The reduction reuses the existing compaction cut-point selection, keeps the recorded transcript intact, and is accepted only when the remaining context still leaves room for the whole request overhead, so the first ordinary request cannot overflow. When the fixed overhead alone cannot fit, the original actionable budget error is raised and nothing is written; sessions with compaction disabled keep refusing as before.

Changes

  • Reduction planner (resume-slice.ts, new, fork-only): derives the fixed admission overhead from the failed projection, walks findCutPoint() from the largest keep budget downwards, measures each candidate through a preview compaction entry, and returns the first cut whose context plus overhead fits the window. Carries the previous checkpoint summary forward under a bounded budget; returns nothing when the overhead alone cannot fit.
  • Admission (sdk.ts): unchanged refusals for fresh starts, non-budget errors and compaction-disabled sessions; the over-window case now asks for a plan and rethrows the original error when there is none, while the in-window case keeps the existing compaction-required admission.
  • Session (agent-session.ts): applyResumeSlice() appends the reduction as a senpi.compaction.resume-slice.v1 entry, rebuilds the live context, logs one line, and publishes a resume_context_reduced event that subscribe() replays for listeners attached after construction.
  • Interactive (interactive-mode.ts): renders that event as a warning next to the existing required-compaction notice.
  • session-manager.ts is deliberately untouched — see Risks.

QA & Evidence

Remote host gorky (linux/x86_64, node 24.20.0, vitest 4.1.11). Evidence under local-ignore/qa-evidence/20260910-issue1524/.

  • What was tested: failing-first proof, before any production edit — new regression against unmodified main.
    Observed result: vitest exit 1; ModelUsabilityBudgetError at sdk.ts:546 with measured live 979,364 / window 850,000 / required 1,158,762, the same band as the report.
    Artifact: c001-red-before-production-edit.log
    Why sufficient: reproduces the reported failure through the real admission path, not a stub.

  • What was tested: the same regression after the change.
    Observed result: 4 passed — session opens, uncompacted requirement fits the window, transcript preserved on disk and in memory, no orphaned tool result, reopening adds no second reduction, impossible overhead still refuses without writing, compaction-disabled still refuses.
    Artifact: c001-green-after-fix.log
    Why sufficient: RED to GREEN through the identical scenario.

  • What was tested: whether the new assertions can actually fail (mutation pass).
    Observed result: removing the compaction-disabled guard, removing the post-reduction budget check, stopping the trimmed mirror from reloading the file, and making the trim truncate the session file each fail their owning assertion; the restored tree returns 4 passed.
    Artifact: c002-mutation-matrix.md
    Why sufficient: this pass caught two of my own assertions being unfalsifiable and one unnecessary API change; all three were corrected before this PR.

  • What was tested: adjacent behavior — resume admission, model usability budget, session persistence, compaction mechanics.
    Observed result: 92 files / 731 tests passed.
    Artifact: c003-adjacent-regressions.log
    Why sufficient: covers the neighbours of every touched seam, including #1511 and start/switch admission.

  • What was tested: npm run check and npm run build --workspace @code-yeongyu/senpi.
    Observed result: both exit 0.
    Artifacts: c003-static-check.log, c003-build.log
    Why sufficient: matches the repository's static gate. An earlier red run was a stale hybrid test tree, not this change; it is green on a clean tree.

  • What was tested: real CLI, node .agents/skills/senpi-qa/scripts/qa-1524-resume.mjs — start a sandboxed session against a scripted local provider, inflate the persisted transcript past the window, resume with --continue.
    Observed result: 11/11 PASS. Restored context ~156,380 tokens against a 120,000-token window; resumed turn exits 0 with no budget error; the outgoing provider request measured on the wire is ~45,423 tokens inside the 112,000 budget; all 60 inflated records remain on disk; a second resume works with exactly one recorded reduction; the real credential file is untouched.
    Artifact: c004-real-cli-resume-qa.log
    Why sufficient: proves the user-visible behavior end to end on the real surface, measuring the actual request bytes rather than in-process state.

Risks & Residuals

  • Context loss on recovery: accepted and reported. This band requires dropping context by definition; the reduction keeps as much recent context as fits, records a checkpoint summary, preserves the transcript, and the user is warned before the first prompt.
  • Persistence API: mitigated by dropping my first approach. I initially added a preserveTranscript flag to SessionManager.appendCompaction, assuming compaction truncates the session file. It does not — _trimMirrorAfterCompaction only trims the in-memory mirror and entry reads reload the full history from the file once trimmed. The flag protected nothing and was reverted, so persistence is unchanged and the transcript guarantee is pinned by tests that fail if it regresses.
  • Compaction disabled: unchanged by design; those sessions still refuse with the actionable budget error rather than silently losing context.
  • Fixture fidelity: the unit fixture reproduces the reported band (live > window) at measured 979,364 tokens rather than the exact 965,016 from the report; the arithmetic and code path are identical.

Related Issues

Fixes #1524. Follows #1509 (compaction-eligible resume admission) and #1517 (compaction-required resume admission); supersedes the approach in the still-open #1430, which conflicts with current main and reduces context without the post-reduction budget check.


Summary by cubic

Resuming a session whose restored transcript is larger than the model's context window no longer fails with ModelUsabilityBudgetError — admission now reduces the live context without a provider request before the session opens.

  • The reduction reuses compaction cut-point selection, preserves the transcript on disk, and only applies when the remaining context still leaves room for the full request overhead.
  • If no safe cut fits, or compaction is disabled, the original budget error is raised and nothing is written.
  • Interactive mode warns before the first prompt, and reopening an already reduced session does not reduce again.

Written for commit da117f6. Summary will update on new commits.

Review in cubic

…mission

A restored transcript larger than the model context window was refused by resume admission before any extension was wired, so the session could never be reopened (#1524). Admission now plans a deterministic no-LLM context reduction: it reuses findCutPoint so a retained tool result keeps its originating tool call, measures each candidate against the failed projection, and accepts the first one that still leaves room for the system prompt, tool schemas, output reserve, compaction reserve and safety margin. Nothing is written when no candidate fits, compaction-disabled sessions keep refusing, and interactive mode reports the reduction before the first prompt.
Drives a persisted session whose measured live context exceeds the model window and pins the contract: the session opens, the uncompacted requirement fits the window, the recorded transcript survives on disk and in memory, no tool result is orphaned, reopening adds no second reduction, an impossible fixed overhead still refuses without writing, and a compaction-disabled session still refuses.
Starts a sandboxed session against the scripted local provider, inflates the persisted transcript past the model window, resumes with --continue, and asserts on the wire that the outgoing request fits the remaining budget while every inflated record stays on disk.
@code-yeongyu
code-yeongyu merged commit de4a6bd into main Sep 10, 2026
21 checks passed
@code-yeongyu
code-yeongyu deleted the fix/1524-resume-over-window branch September 10, 2026 05:24
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.

[bug] ModelUsabilityBudgetError: gpt-6-astra cannot resume (required 1152965 tokens)

1 participant