Skip to content

fix(run_agent): call should_compress_preflight() for sub-threshold engines (#20316) - #20424

Closed
Beandon13 wants to merge 1 commit into
NousResearch:mainfrom
Beandon13:fix/hermes-20316-lcm-preflight
Closed

fix(run_agent): call should_compress_preflight() for sub-threshold engines (#20316)#20424
Beandon13 wants to merge 1 commit into
NousResearch:mainfrom
Beandon13:fix/hermes-20316-lcm-preflight

Conversation

@Beandon13

Copy link
Copy Markdown
Contributor

Summary

  • run_conversation now consults ContextEngine.should_compress_preflight() when the request is below threshold_tokens, so engines like hermes-lcm can run incremental leaf-chunk compaction (or other deferred maintenance) without waiting for the 75% context fill cutoff.
  • Default ContextEngine.should_compress_preflight() still returns False — the built-in ContextCompressor is unaffected.
  • Exceptions raised by the engine hook are caught at debug level and treated as "skip preflight", so a buggy plugin can't break an otherwise-healthy turn.

Closes #20316

Testing

  • scripts/run_tests.sh tests/run_agent/test_run_agent.py::TestRunConversation::test_engine_preflight_fires_below_threshold tests/run_agent/test_run_agent.py::TestRunConversation::test_engine_preflight_skipped_when_returns_false tests/run_agent/test_run_agent.py::TestRunConversation::test_engine_preflight_exception_does_not_break_turn -q
▶ running pytest with 4 workers, hermetic env, in /tmp/hermes-r2-1-fix
  (TZ=UTC LANG=C.UTF-8 PYTHONHASHSEED=0; all credential env vars unset)
bringing up nodes...
bringing up nodes...

...                                                                      [100%]
3 passed in 4.03s
  • scripts/run_tests.sh tests/agent/test_context_engine.py -q
...................                                                      [100%]
19 passed in 1.73s
  • scripts/run_tests.sh tests/run_agent/test_run_agent.py::TestRunConversation::test_context_compression_triggered tests/run_agent/test_run_agent.py::TestRunConversation::test_glm_prompt_exceeds_max_length_triggers_compression -q
..                                                                       [100%]
2 passed in 6.34s

…gines (NousResearch#20316)

Context engines that override ``should_compress_preflight()`` (e.g. the
hermes-lcm plugin's incremental leaf-chunk compaction) never had their
hook fired by ``run_conversation`` because the preflight block exited
early once the hardcoded ``>= threshold_tokens`` check failed.  As a
result, ``LCM_DEFERRED_MAINTENANCE_ENABLED=1`` and friends were inert
and accumulated raw_backlog debt indefinitely.

Add an ``elif`` branch that delegates to the engine's preflight hook
when the legacy threshold check does not fire.  The default
``ContextEngine.should_compress_preflight()`` returns ``False`` so the
built-in ``ContextCompressor`` is unaffected; engines opting in get a
chance to ingest messages and request a single ``compress()`` pass for
deferred maintenance.  Exceptions are swallowed at debug level so a
buggy engine cannot break an otherwise-healthy turn.

Closes NousResearch#20316
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins labels May 5, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #15806 (open PR addressing same issue — should_compress_preflight() dead code). This PR fixes #20316 with a slightly different approach (preflight call below threshold vs per-turn ingest hook).

@barronlroth

Copy link
Copy Markdown
Contributor

Related UX/API concern from an LCM deployment: #25115.

This PR wires the preflight hook so alternative context engines can run below the normal threshold. That is exactly the path where host status wording becomes important: if should_compress_preflight() is being used for plugin-specific maintenance, the generic 📦 Preflight compression... status can be misleading.

It would be useful if this hook, or a companion ContextEngine capability, allowed the active engine to return/suppress a preflight status label. Default built-in compressor behavior could stay unchanged, while LCM/retrieval engines could use neutral or engine-specific wording.

@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 the dormant context-engine hook. The premise still holds on current main: agent/turn_context.py:420 only calls _compressor.should_compress(_preflight_tokens), while the optional hook remains defined at agent/context_engine.py:110-116.

Problems

  • The implementation is stale: run_agent.py:5775-5796 now forwards to the extracted conversation loop, and the live preflight block is agent/turn_context.py:350-463. GitHub currently reports this branch as conflicting.
  • A port must preserve the newer deferral, cooldown, and Codex-native guards at agent/turn_context.py:399-420; the alternate-engine decision should not bypass those protections.

Suggested changes

  • Rework the patch against agent/turn_context.py and add current-path coverage for a concrete engine whose preflight hook requests below-threshold maintenance, returns false, and raises.

Automated hermes-sweeper review.

Comment thread run_agent.py
)
if _preflight_tokens < self.context_compressor.threshold_tokens:
break # Under threshold
elif hasattr(self.context_compressor, "should_compress_preflight"):

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.

Current main extracted this preflight block into agent/turn_context.py:350-463; run_agent.py:5775-5796 is now only a forwarder. Please port this branch into the live turn-context path and preserve its current deferral, cooldown, and Codex-native-compaction guards.

@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 labels Jul 12, 2026
teknium1 added a commit that referenced this pull request Jul 23, 2026
…ght flow

Relocates the #20424 wiring: the preflight region moved out of
run_agent.py into agent/turn_context.py (and through the
compression.max_attempts unification, #69315), so the contributor's
elif branch is reapplied at its current home as the else arm of the
threshold dispatch chain.

Integration contracts:
- Byte-identical default: the built-in ContextCompressor inherits
  ContextEngine.should_compress_preflight() -> False, so the default
  path performs no compression and touches no turn bookkeeping
  (pinned by test_builtin_compressor_default_sub_threshold_path_unchanged).
- Attempt-cap: the engine gets exactly ONE compress() pass per turn,
  mutually exclusive with the cap-bounded threshold multi-pass loop,
  so turn-start passes stay within the resolved
  compression.max_attempts budget in every case.
- No-op blocking (#64382 / 377244f): an engine pass that no-ops
  (_compress_context returns the input list object) neither sets nor
  clears preflight_compression_blocked and does not re-baseline the
  flush history — a sub-threshold maintenance no-op proves nothing
  about over-threshold compressibility.
- Engine exceptions are swallowed at debug level; cooldown/defer/
  codex-native gates run before the hook is ever consulted.

Salvaged from #20424 by @Beandon13. Fixes #20316.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #69865 (commit 929c952). Your commit + tests were cherry-picked with authorship preserved; the wiring was reapplied at the code's current home (turn_context) integrated with the attempt cap and no-op-blocking machinery, with a byte-identical-default pin. Fixes #20316.

@teknium1 teknium1 closed this Jul 23, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…ght flow

Relocates the NousResearch#20424 wiring: the preflight region moved out of
run_agent.py into agent/turn_context.py (and through the
compression.max_attempts unification, NousResearch#69315), so the contributor's
elif branch is reapplied at its current home as the else arm of the
threshold dispatch chain.

Integration contracts:
- Byte-identical default: the built-in ContextCompressor inherits
  ContextEngine.should_compress_preflight() -> False, so the default
  path performs no compression and touches no turn bookkeeping
  (pinned by test_builtin_compressor_default_sub_threshold_path_unchanged).
- Attempt-cap: the engine gets exactly ONE compress() pass per turn,
  mutually exclusive with the cap-bounded threshold multi-pass loop,
  so turn-start passes stay within the resolved
  compression.max_attempts budget in every case.
- No-op blocking (NousResearch#64382 / 0e73a78): an engine pass that no-ops
  (_compress_context returns the input list object) neither sets nor
  clears preflight_compression_blocked and does not re-baseline the
  flush history — a sub-threshold maintenance no-op proves nothing
  about over-threshold compressibility.
- Engine exceptions are swallowed at debug level; cooldown/defer/
  codex-native gates run before the hook is ever consulted.

Salvaged from NousResearch#20424 by @Beandon13. Fixes NousResearch#20316.
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/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have 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.

run_agent.py never calls should_compress_preflight() — LCM deferred maintenance is dead code

4 participants