Skip to content

fix(compression): break the infinite compression loop (salvage of #31730) - #31930

Merged
teknium1 merged 2 commits into
mainfrom
salvage/pr31730-compression-loop
May 25, 2026
Merged

fix(compression): break the infinite compression loop (salvage of #31730)#31930
teknium1 merged 2 commits into
mainfrom
salvage/pr31730-compression-loop

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Compression no longer infinite-loops. Salvage of #31730 (@someaka) with a polish + two invariant-pinning tests on top.

Root cause (from #29335)

Three coupled defects cause an infinite compression loop:

  1. _compress_context() rolls the agent forward into a new session, but run_conversation() never put session_id in its result dict — the gateway's guard for "did the session change?" was dead code.
  2. Preflight compression compared _preflight_tokens >= threshold_tokens directly, bypassing should_compress() which carries the anti-thrash logic. When the system prompt + tool schemas dominate token count, compression saves <10% but preflight re-fires every turn anyway.
  3. Gateway updated session_entry.session_id in memory after compression but never called session_store._save() — the two sibling sites in the same file (hygiene rewrite at L8437, manual /compress at L12122) already persisted; this third site was missed.

Changes

  • agent/conversation_loop.py: add "session_id": agent.session_id to the result dict; gate preflight on agent.context_compressor.should_compress(_preflight_tokens) instead of a raw threshold compare. (Contributor commit, authorship preserved.)
  • agent/conversation_loop.py (polish): drop the redundant _preflight_tokens >= threshold_tokens clause the contributor PR added next to the new should_compress() call — should_compress() already short-circuits when tokens < threshold_tokens, so the explicit comparison was dead on the True branch.
  • gateway/run.py: call self.session_store._save() after the post-compression session_entry.session_id = ... assignment. (Contributor commit.)
  • tests/run_agent/test_413_compression.py: one new test pinning that preflight calls should_compress() so anti-thrash has a vote — mocks should_compress to return False even with tokens past the raw threshold and asserts no compression runs.
  • tests/gateway/test_compression_session_id_persistence.py: new file, one test. AST scan over gateway/run.py asserts every session_entry.session_id = ... assignment is followed by a session_store._save() call within the same block. Empirically verified the test catches the bug: drop the new _save() line → test reports line 8694 missing _save(), restore it → green.
  • scripts/release.py: AUTHOR_MAP entry mapping ed@bebop.crewsomeaka.

Validation

Before After
200k-token session resume infinite compress loop one compress, normal
Anti-thrash on preflight bypassed applies
Gateway restart after compression reverts to old session survives
tests/run_agent/test_413_compression.py                        16 passed
tests/gateway/test_compression_session_id_persistence.py        1 passed
                                                              17 passed in 5.31s

Notes

Infographic

compression-loop-fix

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage/pr31730-compression-loop vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9137 on HEAD, 9136 on base (🆕 +1)

🆕 New issues (1):

Rule Count
not-iterable 1
First entries
tests/gateway/test_compression_session_id_persistence.py:75: [not-iterable] not-iterable: Object of type `object` is not iterable

✅ Fixed issues: none

Unchanged: 4872 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery labels May 25, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Salvage of #31730 by @someaka. Competes with #29505 (same 3-part fix for #29335). Both address the infinite compression loop.

@talwayh1

Copy link
Copy Markdown

CI Test Fix: borrowed-source credential pruning

test_auth_remove_copilot_suppresses_all_variants fails because _prune_stale_seeded_entries() (from #31416) trims the gh_cli borrowed-source entry when resolve_copilot_token returns empty in CI.

Root cause: The test writes a gh_cli entry into auth.json, then calls auth_remove_command() which invokes load_pool()_seed_from_singletons()resolve_copilot_token() returns ("", "")gh_cli not in active_sources_prune_stale_seeded_entries() removes the entry → pool has 0 entries → resolve_target("1") raises SystemExit: No credential #1.

Fix: Added monkeypatch for resolve_copilot_token and get_copilot_api_token so the entry is re-seeded into active_sources and survives pruning.

Fix available at d32cad3fc on talwayh1/hermes-agent:salvage/pr31730-compression-loop:

git fetch https://github.com/talwayh1/hermes-agent.git salvage/pr31730-compression-loop
git cherry-pick d32cad3fc

Verified: pytest tests/hermes_cli/test_auth_commands.py::test_auth_remove_copilot_suppresses_all_variants -xvs → PASSED

Radical Edward and others added 2 commits May 25, 2026 01:36
Three compounding root causes:

A) run_conversation() result dict missing session_id — gateway's
   dead-code guard at gateway/run.py:8700 never triggers
B) preflight compression bypasses should_compress() anti-thrashing —
   re-triggers every turn when tool schemas dominate token budget
C) gateway updates session_entry.session_id in memory but doesn't
   persist via session_store._save()

Fixes: #29335
… persistence

Follow-up to @someaka's fix.

Polish:
- Drop the redundant `_preflight_tokens >= threshold_tokens` clause.
  `should_compress(tokens)` already short-circuits when tokens < threshold,
  so the explicit comparison was dead code on the True branch.

Tests:
- Preflight: pin that should_compress() is called (anti-thrash has a vote).
  Mocks should_compress to return False even with tokens past the raw
  threshold and asserts no compression runs — exact bug shape from #29335.
- Gateway: AST scan of gateway/run.py asserts every
  `session_entry.session_id = ...` assignment is followed by a
  `session_store._save()` call within the same block. Three sites mutate
  the session_id after compression; all three must persist or the next
  turn loads the pre-compression transcript and re-loops. Empirically
  verified the test catches the bug (drops the new _save() line → red).

AUTHOR_MAP:
- Map ed@bebop.crew -> someaka so the salvaged commit resolves to
  @someaka in release notes.
@teknium1
teknium1 force-pushed the salvage/pr31730-compression-loop branch from 76fa6f0 to 2b5043b Compare May 25, 2026 08:37
@teknium1
teknium1 merged commit 11c40d6 into main May 25, 2026
26 checks passed
@teknium1
teknium1 deleted the salvage/pr31730-compression-loop branch May 25, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Context compression creates new session but gateway never sees it — infinite compression loop

3 participants