Skip to content

fix(agent): use ground-truth prompt_tokens in the compaction trigger - #49

Merged
monatis merged 2 commits into
altaidevorg:mainfrom
efecnc:feat/compaction-ground-truth-tokens
Jun 4, 2026
Merged

fix(agent): use ground-truth prompt_tokens in the compaction trigger#49
monatis merged 2 commits into
altaidevorg:mainfrom
efecnc:feat/compaction-ground-truth-tokens

Conversation

@efecnc

@efecnc efecnc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

The auto-compaction trigger sized the context with a bytes/4 heuristic (estimate_context_tokens). That under-counts the code/JSON/non-English payloads this agent generates, so a context that has really blown past the model's window can read as "under threshold" — and the next request overflows, which is unrecoverable once the provider rejects it.

The provider already returns the exact input size in usage.prompt_tokens, but it was only emitted as telemetry and discarded. This tracks the most recent value per turn and feeds it into the trigger:

effective_context_tokens(estimate, last_prompt_tokens) = max(estimate, last_prompt_tokens)

At the end-of-turn compaction check, last_prompt_tokens covers nearly the entire current context (only the just-produced final assistant message is newer, and the estimate side of the max includes that), so the max corrects the heuristic's under-count and compaction fires when it should.

Correctness / why it's safe

  • Per-inbound scoping. last_prompt_tokens is a local Option<u32> reset to None each run_reasoning_loop call — no stale cross-turn value.
  • Self-correcting, no spurious double-compaction. Every iteration that reaches the end-of-turn check has just made a fresh chat call and updated last_prompt_tokens in the same iteration. The mid-turn overflow-recovery path continues before the usage update, so the next iteration re-counts the smaller post-compaction context and overwrites last_prompt_tokens — the pre-compaction value is never observed by the trigger.
  • Fallback. Providers that return no usage leave last_prompt_tokens = None → falls back to bytes/4. A zero usage block is guarded (if usage.prompt_tokens > 0) so it can't clobber a valid value.
  • max can only raise the count toward the ground truth (a request that actually fit), so the worst case is compacting slightly earlier — the intended conservative behavior.

Testing

  • cargo check --all-targets, cargo clippy --release --all-targets (no new warnings), cargo test --lib0 failed.
  • effective_context_tokens_prefers_ground_truth: fallback (None), ground-truth-larger, estimate-larger, and Some(0) never lowers.

Out of scope (follow-up)

An OpenAI context-window override for provider.context_window_tokens() so the window-aware threshold (effective_compaction_threshold) also tightens for OpenAI, not just providers that report a window.


Branch merges cleanly into current main; independently reviewed before upstreaming (see review comment).

The auto-compaction trigger sized the context with a bytes/4 heuristic
(`estimate_context_tokens`). That under-counts the code/JSON/non-English payloads
this agent generates, so a context that has really blown past the model's window
can read as "under threshold" and the next request overflows — an unrecoverable
failure once the provider rejects it.

The provider already returns the exact input size in `usage.prompt_tokens`, but it
was only emitted as telemetry and discarded. Track the most recent value per turn
and feed it into the trigger via `effective_context_tokens(estimate, last)` =
`max(estimate, last_prompt_tokens)`. At the end-of-turn compaction check,
`last_prompt_tokens` covers nearly the whole current context (only the just-
produced final assistant message is newer), so the max corrects the heuristic's
under-count and compaction fires when it should.

Self-correcting by construction: after any compaction the next request re-counts a
smaller context (overflow recovery `continue`s the loop; the threshold path ends
the turn), and `last_prompt_tokens` resets to `None` per inbound — so a stale large
value can't cause a spurious re-trigger.

Out of scope (follow-up): an OpenAI context-window override for
`provider.context_window_tokens()` so the window-aware threshold tightens there too.

Tests: `effective_context_tokens_prefers_ground_truth` (fallback, ground-truth
wins, estimate wins, zero never lowers).
@efecnc

efecnc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Independent code review — APPROVE (upstream-ready)

Rust review of the ground-truth-token compaction trigger. No CRITICAL/HIGH.

  • Fallback to bytes/4 when no usage is correct (Some(0) guarded, never lowers the estimate).
  • No spurious double-compaction: the end-of-turn check lives only in the final-text branch which returns; the mid-turn overflow path is gated independently on the provider's real ContextOverflow, not last_prompt_tokens.
  • Self-correcting: last_prompt_tokens is a per-inbound local (resets each turn); after any compaction the next request refreshes it to the smaller size before the check. The end-of-turn omission of the just-produced assistant message is covered by the estimate side of the max.
  • Idiomatic; clippy/fmt diffs in the tree are pre-existing and shared with upstream main (none on PR-added lines).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves the context-size estimation for the compaction trigger by using the maximum of the heuristic estimate and the actual usage.prompt_tokens from the last LLM call. While this helps prevent context window overflows on code/JSON-heavy payloads, a review comment identifies an issue where last_prompt_tokens is not reset to None after a successful emergency compaction. This could lead to redundant compactions if subsequent LLM calls do not return usage statistics, as the stale, pre-compaction token count would persist.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agent/mod.rs
// server-counted). The bytes/4 heuristic under-counts code/JSON/non-English — exactly what
// this agent generates — so the compaction trigger uses `max(estimate, last_prompt_tokens)`
// to avoid silently overflowing the context window. Updated after each provider response.
let mut last_prompt_tokens: Option<u32> = None;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When emergency compaction succeeds in the ContextOverflow recovery path, the context is compacted and becomes much smaller. However, last_prompt_tokens is not reset to None. If the subsequent LLM call does not return usage statistics (e.g., due to a mock provider, local model, or temporary provider issue), last_prompt_tokens will retain the stale, pre-compaction huge value. This will cause the end-of-turn compaction check to immediately trigger a redundant compaction right after we just compacted. To prevent this, we should reset last_prompt_tokens to None when emergency compaction succeeds.

Addresses review feedback: when emergency compaction succeeds in the
ContextOverflow recovery path, the context shrinks but last_prompt_tokens
still held the pre-compaction value. If the retried call then returned no
usage stats (mock/local provider, transient gap), the end-of-turn check
would read that stale huge value through effective_context_tokens and fire
a redundant compaction immediately after this one. Reset it to None on
successful emergency compaction so the check falls back to the fresh
post-compaction estimate.
@efecnc

efecnc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. On a successful emergency compaction in the ContextOverflow recovery path, last_prompt_tokens is now reset to None before continuing. Otherwise, as you noted, a retried call that returns no usage stats would leave the stale pre-compaction value in place and effective_context_tokens would re-trigger compaction at end-of-turn right after we just compacted. (The reset sits in the reasoning-loop overflow path, which is integration-level to drive end-to-end, so no isolated unit test was added for it; the pure effective_context_tokens helper keeps its existing tests.)

@monatis
monatis merged commit a84a510 into altaidevorg:main Jun 4, 2026
1 check passed
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.

2 participants