Compression locking, forking, and concurrency bugs

NousResearch/hermes-agent · cluster 38727 · agent

Data as of

Nothing open on GitHub. Fix merged in #41708. Check it covers your case.

The short answer

0open issues
0open PRs
13items grouped
Known?
Yes. 13 related items grouped here, 4 issues already closed.
Fix event
A fix was merged to main in PR #41708. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #41708. That is separate from the fix event.
Still open
Nothing open on GitHub.

These items concern bugs and fixes around concurrent context compression: lock failure modes (fail-open vs. fail-closed), session forking from concurrent or cross-turn compression, and interrupt handling during in-flight compression. Several test reliability issues related to these race conditions are also included.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #41708 fix(compression): disable compression on background-review fork (#38727)

    merged to main

    files: agent/background_review.py, tests/agent/test_compression_concurrent_fork.py

    Show the diff
    • agent/background_review.py
      @@ -449,6 +449,17 @@ def _bg_review_auto_deny(command, description, **kwargs):
                   # if a future code path bypasses the cache.
                   review_agent.session_start = agent.session_start
                   review_agent.session_id = agent.session_id
      +            # Never let the review fork compress. It shares the parent's
      +            # session_id, so if it won a compression race it would rotate the
      +            # parent into a NEW child that the gateway never adopts (the fork
      +            # is single-lifecycle and dies right after this run_conversation).
      +            # The foreground turn would then start from the stale parent and
      +            # compress it again, leaving the same parent with two sibling
      +            # children (issue #38727). Review also needs full context to
      +            # produce a good memory/skill summary — compressing would strip
      +            # detail. Both compression triggers in conversation_loop.py gate on
      +            # agent.compression_enabled, so this short-circuits both paths.
      +            review_agent.compression_enabled = False
       
                   from model_tools import get_tool_definitions
                   from hermes_cli.plugins import (
    • tests/agent/test_compression_concurrent_fork.py
      @@ -238,3 +238,75 @@ def test_missing_lock_subsystem_fails_open_not_infinite_loop(tmp_path: Path) ->
           )
           # Session rotated (compression succeeded end-to-end).
           assert agent.session_id != parent_sid
      +
      +
      +def test_review_fork_disables_compression_to_prevent_stale_parent_fork() -> None:
      +    """The background-review fork must set ``compression_enabled = False``
      +    so it can never compress the parent it shares a session_id with
      +    (issue #38727).
      +
      +    The per-session compression lock only serialises a SAME-WINDOW concurrent
      +    race. It does NOT stop a stale parent from being compressed again in a
      +    LATER turn: if ``review_agent`` had won the race, its new child session is
      +    never adopted by the gateway (the fork is single-lifecycle and dies right
      +    after one ``run_conversation``), so the foreground path would start the
      +    next turn from the stale parent and compress it AGAIN — leaving the same
      +    parent with two sibling children.
      +
      +    The fix makes the review fork never trigger compression at all. Both
      +    compression trigger sites in ``agent/conversation_loop.py`` gate on
      +    ``agent.compression_enabled`` BEFORE calling ``_compress_context``:
      +      • preflight (``if agent.compression_enabled and len(messages) > ...``)
      +      • mid-loop  (``if agent.compression_enabled and _compressor.should_compress(...)``)
      +    so a fork with the flag cleared never reaches the rotation path.
      +
      +    This test pins the contract at the source: ``_run_review_in_thread``
      +    must set ``review_agent.compression_enabled = False`` on the fork it
      +    builds. It calls the real worker synchronously with
      +    ``AIAgent.run_conversation`` patched (so no LLM call happens) and
      +    captures the constructed review agent to assert the flag.
      +    """
      +    import tempfile
      +
      +    import agent.background_review as br
      +
      +    captured = {}
      +
      +    def _fake_run_conversation(self, *_a, **_k):
      +        captured["compression_enabled"] = self.compression_enabled
      +        captured["session_id"] = self.session_id
      +        return {"final_response": "", "messages": []}
      +
      +    parent_sid = "REVIEW_FORK_FLAG_TEST"
      +
      +    with tempfile.TemporaryDirectory() as td:
      +        db = SessionDB(db_path=Path(td) / "state.db")
      +        db.create_session(parent_sid, source="discord")
      +        parent = _build_agent_with_db(db, parent_sid)
      +
      +        # The worker does a local ``from run_agent import AIAgent``; patching
      +        # the class method covers that import path.
      +        from run_agent import AIAgent
      +
      +        with patch.object(AIAgent, "run_conversation", _fake_run_conversation):
      +            br._run_review_in_thread(
      +                parent,
      +                [{"role": "user", "content": "hi"}],
      +                "review this conversation",
      +            )
      +
      +    assert captured, (
      +        "_run_review_in_thread never reached run_conversation — the spawn path "
      +        "changed; update this test to capture the review AIAgent."
      +    )
      +    assert captured["session_id"] == parent_sid, (
      +        "Review fork should inherit the parent's session_id (shared id is the "
      +        "whole reason compression must be disabled)."
      +    )
      +    assert captured["compression_enabled"] is False, (
      +        "FIX REGRESSION: background-review fork did NOT disable compression. "
      +        "It shares the parent's session_id, so an enabled fork can rotate the "
      +        "parent into an orphan child (issue #38727). The trigger gates in "
      +        "conversation_loop.py only short-circuit when compression_enabled is "
      +        "False — this flag MUST be cleared on the review fork."
      +    )
  • #56416 fix(gateway): queue interrupts during in-flight context compression (#56391)

    merged to main

    files: gateway/run.py, scripts/release.py, tests/gateway/test_compression_interrupt_demotion_56391.py

7 PRs closed without merging

From the close comments: done elsewhere 4 · merged via another change 2 · closed without comment 1

  • #38746 [codex] Disable compression in background review fork · done elsewhere → a77bc2c, #41708
  • #52547 fix(test): barrier eliminates race in compression fork test (#52526) · done elsewhere → d2d470e, #52526
  • #56404 fix(gateway): queue interrupts during in-flight context compression · merged via another change → #56416, #56391
  • #56410 fix(gateway): wait for in-flight compression before enqueuing interrupted messages · merged via another change → #56391, #56416, #56442
  • #56414 fix(gateway): stop compression sibling forks from message bursts (salvage #56404, fixes #56391) · done elsewhere → 5eaccf5, #56391, #56404
Show 2 more
  • #63132 fix(agent): fail closed on unexpected compression-lock acquisition errors · closed without comment
  • #64183 fix(compression): fail CLOSED on transient lock errors, OPEN on structural absence (#63129) · done elsewhere → 2627933
Closed issues · 4
  • Closed#38727[Bug]: `per-session compression lock` only prevents same-window concurrent fork, but not cross-turn stale-parent fork
  • Closed#52526Flaky test: test_compression_concurrent_fork.py::test_concurrent_compression_does_not_fork_session
  • Closed#56391Interrupt-protected compression + default busy_input_mode=interrupt can fork multiple orphaned compression siblings from one stale parent during a message burst
  • Closed#63129[Bug]: Compression lock fails OPEN on any acquisition error — concurrent compression can fork the session
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.13: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#41708, #56416
Closed issues
4
Attempts
7 PRs closed without merging
Fix link checked
1 Jul 2026

For your agent

The same facts, typed.