Skip to content

fix(compaction): cap post-tool compression attempts per turn - #63870

Closed
dombejar wants to merge 1 commit into
NousResearch:mainfrom
dombejar:fix/post-tool-compression-attempt-cap
Closed

fix(compaction): cap post-tool compression attempts per turn#63870
dombejar wants to merge 1 commit into
NousResearch:mainfrom
dombejar:fix/post-tool-compression-attempt-cap

Conversation

@dombejar

Copy link
Copy Markdown
Contributor

Problem

The preflight and overflow compression paths share a compression_attempts < 3 per-turn cap, but the post-tool compression path in run_conversation (agent/conversation_loop.py) neither checked nor incremented the counter. During a long tool turn that rapidly regenerates context (large tool responses / file reads adding 60-90K tokens between passes), post-tool compression can therefore re-fire indefinitely.

Observed in production (v0.18.2, gpt-5.6-sol via openai-codex, compression threshold at 50%): one session compacted 4 times in ~13 minutes. Each compaction genuinely reduced estimated message tokens 30-43%, but the same long tool turn regrew past threshold and re-triggered — with each compaction itself a full main-model summarization call (auxiliary.compression.provider: main), the session spent most of its wall clock compacting.

Fix

Apply the same shared guard the other two paths use: gate post-tool compression on compression_attempts < 3 and increment the shared counter before compacting.

Tests

  • New regression test tests/test_post_tool_compression_attempt_cap.py — failed before the change, passes after.
  • Full related compression suite: 94/94 pass.

https://claude.ai/code/session_015StmTvr85VriETdW9Gy9D5

@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 labels Jul 13, 2026

@teknium1 teknium1 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.

Thanks for identifying a real missing guard. Current main’s post-tool path at agent/conversation_loop.py:4956-4962 can compact without checking or incrementing the per-turn counter, while the equivalent pre-API path already uses the proposed compression_attempts < 3 / increment pattern at agent/conversation_loop.py:1043-1051.

Problems

  • tests/run_agent/test_post_tool_compression_attempt_cap.py:14-29 reads run_conversation source with inspect.getsource() and asserts substrings. AGENTS.md:1370-1421 explicitly bans source-shape tests; this would pass even if the counter were not reached on the runtime path.

Suggested changes

  • Replace it with a behavioral conversation-loop test that simulates more than three qualifying post-tool compression opportunities and asserts _compress_context runs exactly three times in one turn.

Automated hermes-sweeper review.



def _post_tool_compression_block() -> str:
from agent import conversation_loop

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.

Please replace this source inspection with a behavioral conversation-loop test. AGENTS.md:1370-1421 bans source-shape tests; substring assertions do not prove that post-tool compression is actually limited to three runtime invocations.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/compression Context compression and continuation sessions labels Jul 16, 2026
teknium1 added a commit that referenced this pull request Jul 22, 2026
Follow-up to the salvaged #64010 (Kenmege) and #63870 (dombejar) commits,
making one resolved compression.max_attempts cap govern ALL per-turn
compression attempt sites:

- conversation_loop: resolve max_compression_attempts ONCE at turn start
  (it was previously re-resolved inside the API-call loop) and route the
  pre-API pressure gate through it — that gate still hardcoded
  'compression_attempts < 3' and logged 'attempt=%s/3'.
- conversation_loop: the salvaged post-tool compaction gate now uses the
  resolved cap instead of a hardcoded 3.
- turn_context: the preflight compaction loop was 'for _pass in range(3)';
  it now sizes itself from the same resolved cap.
- agent_init: harden the max_attempts parser — reject booleans (bool
  subclasses int; 'true' would coerce to 1), reject fractional floats
  instead of truncating them, keep accepting integral floats and numeric
  strings; anything else falls back to 3 (floor 1, ceiling 10 unchanged).
- tests: replace #63870's inspect.getsource source-shape test with
  behavioral loop tests (post-tool compaction fires <= cap times per turn,
  shares its budget with the pre-API gate, resets between turns); add an
  e2e test proving a 4th preflight pass runs at config cap=6 while the
  unset default still stops at 3; extend the #64010 config tests with the
  bool/float parser semantics.

Salvages #64010 by @Kenmege and #63870 by @dombejar.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #69315 (commit 1c2faed) — your post-tool attempt-cap commit was cherry-picked with authorship preserved, unified with #64010 so all four compression sites honor compression.max_attempts. The source-shape test was replaced with behavioral loop tests during salvage. Thanks!

@teknium1 teknium1 closed this Jul 23, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Follow-up to the salvaged NousResearch#64010 (Kenmege) and NousResearch#63870 (dombejar) commits,
making one resolved compression.max_attempts cap govern ALL per-turn
compression attempt sites:

- conversation_loop: resolve max_compression_attempts ONCE at turn start
  (it was previously re-resolved inside the API-call loop) and route the
  pre-API pressure gate through it — that gate still hardcoded
  'compression_attempts < 3' and logged 'attempt=%s/3'.
- conversation_loop: the salvaged post-tool compaction gate now uses the
  resolved cap instead of a hardcoded 3.
- turn_context: the preflight compaction loop was 'for _pass in range(3)';
  it now sizes itself from the same resolved cap.
- agent_init: harden the max_attempts parser — reject booleans (bool
  subclasses int; 'true' would coerce to 1), reject fractional floats
  instead of truncating them, keep accepting integral floats and numeric
  strings; anything else falls back to 3 (floor 1, ceiling 10 unchanged).
- tests: replace NousResearch#63870's inspect.getsource source-shape test with
  behavioral loop tests (post-tool compaction fires <= cap times per turn,
  shares its budget with the pre-API gate, resets between turns); add an
  e2e test proving a 4th preflight pass runs at config cap=6 while the
  unset default still stops at 3; extend the NousResearch#64010 config tests with the
  bool/float parser semantics.

Salvages NousResearch#64010 by @Kenmege and NousResearch#63870 by @dombejar.
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:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) 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