Skip to content

compression.threshold_tokens: null takes effect live; cap survives a failed config load (follow-up to #115986) - #119406

Merged
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:fix/threshold-tokens-default-seams
Sep 22, 2026
Merged

kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:fix/threshold-tokens-default-seams

Conversation

@kshitijk4poor

Copy link
Copy Markdown

Setting compression.threshold_tokens: null now takes effect on a running gateway, and a failed config load no longer silently drops the 256K cap.

Follow-up to #115986 (the 256K default cap). The community report on that PR noted that the documented opt-out — hermes config set compression.threshold_tokens null — only took effect after a gateway restart. Confirmed in code, plus two smaller seams found while re-reviewing the merged range.

Changes

  • gateway/run_agent_cache.py::_extract_cache_busting_config — the cache signature reads the raw config file and mapped an absent key to None, so threshold_tokens: null produced the same signature as "never set" and the cached agent kept the cap. Absent keys now carry DEFAULT_CONFIG's value (what the agent was built with): absent == explicit default (no spurious rebuild), absent != explicit null (the opt-out rebuilds). Applies to every key in _CACHE_BUSTING_CONFIG_KEYS; only keys with a non-None default change behaviour.
  • agent/agent_init.py::_parse_compression_config — every compression key had an inline fallback matching DEFAULT_CONFIG except threshold_tokens, so the {} config-load-failure path kept threshold=0.50 but dropped the cap. Now reads the default like its siblings; explicit null still means ratio-only.
  • agent/context_compressor.py — the min(cap, context_length) clamp was duplicated in _derive_trigger and _apply_threshold_tokens_cap; both call one _effective_threshold_cap(). Dropped a dead getattr(self, "_config_threshold_percent", …) fallback (__init__ sets it unconditionally; no bare-__new__ test instance reaches the method). Behaviour-preserving.

Validation

Check Result
New tests red on origin/main test_explicit_null_differs_from_absent_when_default_is_set, test_missing_keys_yield_the_shipped_default, test_absent_threshold_tokens_falls_back_like_every_other_key[absent] fail on base, pass on the branch
Mutation check on the refactor _effective_threshold_cap → return None fails 5 cap tests across 3 files; restored from snapshot
Mirror-dir tests (12 files, gateway/agent/tui_gateway) 327 passed
ruff, check-windows-footguns.py --all, check_compat_pointers.py, git diff --check clean

Not in this PR: whether the default cap should yield to a user-set compression.threshold / model_thresholds (#117915, needs-decision; #117929 is the leading fix). Docs already describe null as the ratio-only opt-out and make no restart claim, so no doc change.

…d of waiting for a restart

The agent-cache signature reads the raw config file (no DEFAULT_CONFIG merge) and mapped
an absent key to None. Since NousResearch#115986 shipped compression.threshold_tokens=256K, the
documented opt-out (`hermes config set compression.threshold_tokens null`) produced the
same None as "never set", so the signature did not change and live gateway sessions
kept compacting at 256K until the process restarted (community report on NousResearch#115986).

Absent keys now carry DEFAULT_CONFIG's value — what the agent was actually built with —
so absent == explicit default (no spurious rebuild) and absent != explicit null.
…ke every other compression key

`_parse_compression_config` documents "Defaults here MUST match DEFAULT_CONFIG" because the
config-load failure path hands it `{}`. Every key had an inline fallback except the cap
added in NousResearch#115986, so a broken config.yaml kept `threshold=0.50` but silently dropped the
256K cap — the pre-NousResearch#115986 500K trigger on 1M-window models. An explicit null still
means ratio-only.
…reshold_percent fallback

`min(threshold_tokens_cap, context_length)` behind the same `cap is not None and cap > 0`
guard was written in both `_derive_trigger` and `_apply_threshold_tokens_cap`, contradicting
`_derive_trigger`'s "one place the trigger math lives". Both now call
`_effective_threshold_cap(context_length)`. The `getattr(self, "_config_threshold_percent",
...)` fallback was dead: `__init__` sets the attribute unconditionally and no bare-`__new__`
test instance reaches `_derive_trigger`. Behaviour-preserving; the cap tests still fail when
the helper is mutated to return None.
@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 comp/gateway Gateway runner, session dispatch, delivery area/compression Context compression and continuation sessions area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 22, 2026
@kshitijk4poor
kshitijk4poor merged commit 94e8a25 into NousResearch:main Sep 22, 2026
36 checks passed
kshitijk4poor added a commit that referenced this pull request Sep 22, 2026
…t limits, one cfg_get per key

#119406 made absent keys carry DEFAULT_CONFIG's value in the cache signature, but the
legacy bool branch still emitted None for max_snapshots/max_total_size_mb/max_file_size_mb
while `_checkpoint_agent_kwargs` builds that agent from DEFAULT_CONFIG — so migrating to
`checkpoints: {enabled: true}` rebuilt the cached agent for nothing. The dict/absent branches
were also re-implementing cfg_get's own semantics; one `cfg_get(cfg, section, key, default=…)`
covers absent section, non-dict section, absent key and explicit null. DEFAULT_CONFIG is
imported at module top (hermes_cli.config already is; gateway/restart.py sets the precedent).
kshitijk4poor added a commit that referenced this pull request Sep 22, 2026
…trim comments and tests

`_cap_binds` was the last inline copy of the cap-clamped-to-window predicate that #119406
centralised; the banner still prints the raw configured cap. Plugin engines without the
helper report "not binding", as before when no cap was set. The threshold_tokens comment
and the new test module stop narrating the incident; the parametrize takes the whole agent
config so the `{}` failure path is spelled out rather than derived from a ternary.
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 area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants