hermes - ✅(Solved) Fix Bug: gateway /compress assumes legacy ContextCompressor and crashes with LCM context engine [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14886Fetched 2026-04-24 10:44:31
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×1

Root Cause

GatewayRunner._handle_compress_command() contains a preflight boundary check coupled to the legacy ContextCompressor implementation instead of using the public context-engine / agent compression boundary.

This violates the pluggable context-engine contract: gateway command dispatch should not depend on private methods from one concrete engine.

Fix Action

Fix / Workaround

This violates the pluggable context-engine contract: gateway command dispatch should not depend on private methods from one concrete engine.

PR fix notes

PR #14917: fix(gateway): guard /compress against pluggable context engines

Description (problem / solution / changelog)

Summary

Fixes #14886

The gateway /compress command crashes with an AttributeError when a pluggable context engine (e.g. LCMEngine) is used instead of the legacy ContextCompressor, because it directly calls private methods _align_boundary_forward() and _find_tail_cut_by_tokens() that only exist on the legacy class.

Root Cause

In _handle_compress_command(), lines 7104-7107:

compressor = tmp_agent.context_compressor
compress_start = compressor.protect_first_n
compress_start = compressor._align_boundary_forward(msgs, compress_start)
compress_end = compressor._find_tail_cut_by_tokens(msgs, compress_start)

These are private methods specific to ContextCompressor. Pluggable engines like LCMEngine don't expose them.

Changes

gateway/run.py_handle_compress_command():

  • Added hasattr guards to detect whether the compressor has the legacy private API
  • When legacy API is available: existing code path runs unchanged (zero behavioral change)
  • When legacy API is missing: falls back to the public compressor.compress() API that all engines must implement
  • Added len(compressed) >= len(msgs) check for the fallback path to detect no-op compression

Testing

  • Legacy compressor: hasattr returns True for both methods → existing path runs unchanged → all existing tests pass
  • LCM engine: hasattr returns False → public compress() API used → no crash
  • MagicMock (used in existing tests) auto-creates attributes, so hasattr returns True → existing test fixtures work

Checklist

  • Follows Conventional Commits format
  • Single logical change
  • No breaking changes
  • Existing tests unaffected (MagicMock preserves hasattr behavior)

Changed files

  • gateway/run.py (modified, +28/-9)

Code Example

Compression failed: 'LCMEngine' object has no attribute '_align_boundary_forward'

---

context:
  engine: lcm
plugins:
  enabled:
    - hermes-lcm

---

/compress

---

compressor = tmp_agent.context_compressor
compress_start = compressor.protect_first_n
compress_start = compressor._align_boundary_forward(msgs, compress_start)
compress_end = compressor._find_tail_cut_by_tokens(msgs, compress_start)
RAW_BUFFERClick to expand / collapse

Bug Description

Gateway /compress assumes the active context manager is the legacy ContextCompressor and calls legacy private methods before running compression. When Hermes is configured with a pluggable context engine such as LCM (context.engine: lcm), the active context_compressor is LCMEngine, so manual /compress fails before it reaches the engine's public compression path.

Observed Telegram response:

Compression failed: 'LCMEngine' object has no attribute '_align_boundary_forward'

Steps to Reproduce

  1. Configure Hermes with LCM as the context engine:
context:
  engine: lcm
plugins:
  enabled:
    - hermes-lcm
  1. Use a gateway platform such as Telegram.
  2. Build a session with enough transcript history for /compress to be accepted.
  3. Send:
/compress

Expected Behavior

Manual /compress should work with any active ContextEngine implementation that satisfies the public compression interface. The gateway should not assume legacy ContextCompressor private helper methods exist.

If the active context engine decides there is nothing to compact and returns the transcript unchanged, /compress should report a no-op without rotating sessions, rewriting transcript storage, or creating a continuation session.

Actual Behavior

The gateway handler fails before compression because it calls private legacy methods on the active engine:

compressor = tmp_agent.context_compressor
compress_start = compressor.protect_first_n
compress_start = compressor._align_boundary_forward(msgs, compress_start)
compress_end = compressor._find_tail_cut_by_tokens(msgs, compress_start)

LCMEngine does not implement _align_boundary_forward() / _find_tail_cut_by_tokens(), so /compress returns the AttributeError above.

Root Cause

GatewayRunner._handle_compress_command() contains a preflight boundary check coupled to the legacy ContextCompressor implementation instead of using the public context-engine / agent compression boundary.

This violates the pluggable context-engine contract: gateway command dispatch should not depend on private methods from one concrete engine.

Suggested Production Fix

Lowest-blast-radius approach:

  1. In gateway/run.py, route manual /compress through the existing canonical compression path, e.g. AIAgent._compress_context(...), instead of calling legacy private compressor helpers directly.
  2. Persist/rewrite the gateway transcript only if the compression result actually changes.
  3. In AIAgent._compress_context(), treat an unchanged result from the active context engine as a no-op so a pluggable engine that returns messages unchanged does not rotate sessions or mutate storage.
  4. Add regression coverage for:
    • /compress with an LCM-like context engine that lacks legacy private methods.
    • no-op compression does not split/rotate the session.

Why This Matters

context.engine is a public extension seam. A gateway command that assumes ContextCompressor private methods breaks all non-legacy engines even when those engines correctly implement the public compress() interface.

Environment

  • Hermes Agent gateway
  • Platform observed: Telegram
  • Context engine: lcm via hermes-lcm
  • Active model in observed environment: GPT-5.5 via OpenAI Codex

extent analysis

TL;DR

The most likely fix is to modify the GatewayRunner._handle_compress_command() to use the public context-engine compression boundary instead of legacy private methods.

Guidance

  • Identify the GatewayRunner._handle_compress_command() function and refactor it to use the AIAgent._compress_context(...) method for compression, ensuring compatibility with pluggable context engines.
  • Verify that the compression result is checked for changes before persisting or rewriting the gateway transcript to prevent unnecessary updates.
  • Test the /compress command with an LCM-like context engine to ensure it works as expected and does not throw an AttributeError.
  • Add regression tests to cover no-op compression scenarios where the context engine returns the messages unchanged.

Example

# Example of refactored GatewayRunner._handle_compress_command()
def _handle_compress_command(self, ...):
    # ...
    compressor = tmp_agent.context_compressor
    compressed_context = tmp_agent._compress_context(...)  # Use public compression method
    if compressed_context != original_context:
        # Persist/rewrite transcript only if compression result changed
        # ...

Notes

This fix assumes that the AIAgent._compress_context(...) method is correctly implemented to work with pluggable context engines. Additional testing and verification may be necessary to ensure compatibility with different context engines.

Recommendation

Apply the suggested production fix to modify the GatewayRunner._handle_compress_command() function to use the public context-engine compression boundary, ensuring compatibility with pluggable context engines like LCM. This approach has the lowest blast radius and allows for easy regression testing.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING