Skip to content

fix(compression): fit summarizer input to small aux windows instead of lowering the session threshold - #84325

Open
chesterXalan wants to merge 2 commits into
NousResearch:mainfrom
chesterXalan:summary-window-fit-no-threshold-lowering
Open

fix(compression): fit summarizer input to small aux windows instead of lowering the session threshold#84325
chesterXalan wants to merge 2 commits into
NousResearch:mainfrom
chesterXalan:summary-window-fit-no-threshold-lowering

Conversation

@chesterXalan

Copy link
Copy Markdown
Contributor

What does this PR do?

Stops a small-context auxiliary compression model from silently halving the main model's usable context window.

Since #12898, when auxiliary.compression.model resolves to a window smaller than the session's compression threshold, the feasibility check auto-lowers the live session threshold to the aux window. Pairing a 272K main model with a 128K compression model (a natural choice — small models are fast and cheap summarizers) drops the compaction trigger from 231K to 128K: compaction fires at ~47% of the real window, roughly twice as often, each pass discarding conversation detail and invalidating the prompt-cache prefix. Measured on a live gateway, enabling a 128K summary model took a session from ~1 compaction/day to 20+/day with no visible cause beyond a one-line startup log.

The comparison itself is against the wrong quantity. The summarizer request is already bounded — per-message truncation (_CONTENT_MAX 6K chars/message) plus the _SUMMARY_INPUT_MAX_CHARS (160K chars) aggregate cap with an explicit omitted-middle marker — so it never approaches the session threshold it is compared to. What must fit the aux window is that bounded request plus the summary output budget, not the conversation.

The change:

  • check_compression_model_feasibility no longer touches the threshold (or tail_token_budget / threshold_percent). It stashes the resolved aux window on the compressor (summary_model_context_length) and logs one INFO line. The 64K hard floor and the no-provider warning are unchanged.
  • _generate_summary assembles the prompt through a single builder and, when the stashed window is smaller than the assembled request plus the output budget, shrinks the serialized-turns block — head + tail with the existing omitted-middle marker shape — until the whole prompt fits (estimate_tokens_rough over-counts every content class, so a passing fit cannot overflow the real window). A pathological prompt that cannot be shrunk below the floor is sent best-effort; a failure routes through the existing main-model fallback (_fallback_to_main_for_compression), exactly as other aux failures do.
  • _bound_summary_input gains an optional max_chars override used by the fit loop.

Net effect: a small aux model now summarizes a per-call-bounded digest while the main conversation keeps its full window. The threshold — since #80997/#81069 compared against projected real usage — stays a property of the main model only.

Related: #12898 (introduced the auto-lower alongside the 64K floor — the floor is kept), #67422 (the threshold-suggestion math in the removed warning; the suggestion is no longer needed because nothing needs correcting), #8499 (auxiliary.compression.context_length config override — still honored, it feeds the stashed window), #52392 (fallback-chain context screening — untouched).

Related Issue

No open issue describes this defect; the origin PR and adjacent reports are linked above.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/conversation_compression.py — feasibility check stashes the aux window instead of lowering the session threshold; removes the auto-lower bookkeeping (tail_token_budget lockstep, threshold_percent rewrite) and its multi-line user warning, which no longer has anything to warn about.
  • agent/context_compressor.py — new summary_model_context_length field; _bound_summary_input(max_chars=...) override; _generate_summary prompt assembly extracted into one builder + per-call fit loop with a floor guard and best-effort logging.
  • tests/agent/test_summary_window_fit.py — new: feasibility stash semantics (threshold untouched, no warning), per-call trim fits the window on CJK-dense content, fit inactive without a stashed window or when the main model summarizes, max_chars override.
  • tests/run_agent/test_compression_feasibility.py — three tests updated from auto-lower semantics to stash-and-fit semantics; the gateway-replay test now exercises the surviving no-provider warning; hard-floor rejection and config-override tests unchanged.

How to Test

  1. pytest tests/agent/test_summary_window_fit.py tests/run_agent/test_compression_feasibility.py -q — 16 passed.
  2. pytest tests/agent tests/run_agent -q -k "compress or compaction or summary or feasib" — 711 passed on this branch.
  3. Live repro: set auxiliary.compression.{provider,model} to any 128K model under a 200K+ main model. Before: startup logs auto-lowered session threshold to 128000 and long sessions compact at ~half the real window, several times per hour. After: the threshold log line is gone, Preflight compression still fires at the configured threshold, and when compaction runs the log shows Summarizer input trimmed to fit <model>'s 128000-token window while the summary call succeeds within it.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the compression/summary/feasibility suites listed above (711 passed); the full tests/ run on my machine has environment-dependent failures (i18n catalogs, models.dev fetch, etc.) that are identical on clean main
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15 (arm64), Python 3.11
  • Verified live on a production gateway (Codex Responses transport, 272K main model): deploy, graceful restart, healthy

Documentation & Housekeeping

  • I've updated relevant documentation (docstrings) — behavior documented on _bound_summary_input, the fit loop, and the feasibility stash
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config changes; existing keys keep their meaning)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — N/A (pure Python)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

…f lowering the session threshold

A summary model whose context window is smaller than the session
threshold used to trigger an auto-lower of the live threshold to the
aux window — halving a 272K main model's usable context when paired
with a 128K compression model: compaction fires at ~47% of the real
window, twice as often, discarding conversation detail early and
breaking the prompt-cache prefix each time.

The comparison itself was against the wrong quantity: the summariser
request is already bounded (per-message truncation plus the
_SUMMARY_INPUT_MAX_CHARS aggregate cap), so it never approaches the
session threshold. What actually needs to fit the aux window is that
bounded request — not the conversation.

check_compression_model_feasibility now stashes the resolved aux
window on the compressor and leaves the threshold alone; the 64K hard
floor is unchanged. _generate_summary assembles the prompt through a
single builder and, when the stashed window is smaller than the
assembled request plus the summary output budget, shrinks the
serialized-turns block (head+tail, explicit omitted-middle marker —
the same shape as the existing aggregate cap) until it fits.
estimate_tokens_rough over-counts, so a passing fit cannot overflow
the real window; a still-oversized pathological prompt is sent
best-effort and a failure routes through the existing main-model
fallback.

_bound_summary_input gains an optional max_chars override used by the
fit loop.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 12, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

PR: fix(compression): fit summarizer input to small aux windows instead of lowering the session threshold

  1. Overlaps feat(compression): fold oversized windows through sequential chunk summaries #84336 (agent/context_compressor.py, agent/conversation_compression.py): feat(compression): fold oversized windows through sequential chunk summaries #84336 contains the identical feasibility rewrite and per-call fit plus a sequential-fold mechanism on top. If both are open they conflict on the same hunks — worth confirming the intended stacking (e.g. this lands first, feat(compression): fold oversized windows through sequential chunk summaries #84336 rebases) so the duplicate isn't reviewed twice.

  2. User-facing warning removed (conversation_compression.py): previously a small aux model produced a visible "auto-lowered this session's threshold" message with config.yaml guidance; now it is a silent logger.info. The fix is arguably better (the threshold no longer needs lowering), but users with a genuinely too-small aux model now get silently trimmed summaries and no pointer to auxiliary.compression.model. A cooldown-bounded one-time status emission when the per-call fit actually engages would preserve the diagnostic without the noise.

  3. Fit loop can exit non-converged without logging (context_compressor.py, _generate_summary): the warning fires only when content hits the 8000-char floor; if the 4-iteration loop exhausts while content is still above the floor and the prompt is still over _fit_budget, it breaks silently and sends a possibly-oversized prompt. Consider logging when the loop exits without converging (iteration cap), not just on the floor case.

  4. Stashed window can go stale mid-session (minor): summary_model_context_length is set once at session start by check_compression_model_feasibility; if the aux model/window changes later (config reload, update_model), the fit targets the old window — a shrunken window could overflow, a grown one just over-trims. Worth refreshing the stash wherever update_model updates the other derived budgets.

Addresses automated-review findings on the window-fit change:

* Log when the trim loop exits via its iteration cap while the prompt is
  still over the fit budget — previously only the content-floor exit
  warned, and a non-converged prompt was sent silently. (Reaching the
  cap without converging requires a scaffold-dominated prompt, so this
  is defensive, but a silently oversized send deserves a trace.)
* Restore a user-facing diagnostic for small aux windows, without the
  old auto-lower warning's noise: when the per-call fit actually trims
  the summarizer input, compress_context emits a one-time-per-session
  status pointing at auxiliary.compression.model — mirroring the
  existing _last_summary_fallback_used surfacing pattern. Sessions
  whose windows never need trimming see nothing.
@chesterXalan

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all four points addressed or answered:

  1. Stacking: confirmed — this PR lands first; feat(compression): fold oversized windows through sequential chunk summaries #84336 is stacked on it (stated in its description's first line) and has just been rebased on this branch's updated head, so the overlap is the stack, not a duplicate.
  2. User-facing warning: restored in 6ce35a12e, without the old warning's noise — when the per-call fit actually trims, compress_context now emits a one-time-per-session status pointing at auxiliary.compression.model (mirrors the existing _last_summary_fallback_used surfacing). Sessions whose windows never need trimming see nothing, which the session-start auto-lower warning couldn't offer.
  3. Non-converged exit: also in 6ce35a12e — the iteration-cap exit now logs a warning distinct from the content-floor exit. (Reaching it requires a scaffold-dominated prompt, so it's defensive, but agreed a silently oversized send deserves a trace.)
  4. Stale stash: the stash tracks the auxiliary model, which update_model (main-model switches) doesn't change — aux config is resolved per session like every other auxiliary.* setting. The one mid-session aux transition that does exist (_fallback_to_main_for_compression) clears summary_model, which gates the fit, so a stale window is inert there. Happy to wire a refresh if there's a live mid-session aux-switch path I've missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants