fix(compression): anchor compaction on the latest actionable user turn - #69291
Merged
Conversation
Contributor
૮ >ﻌ< ა ci reviewrunning on ea2e9e5 looks good to me! |
teknium1
force-pushed
the
salvage/62747-actionable-user-turn
branch
from
July 22, 2026 14:08
995bf9a to
ea2e9e5
Compare
This was referenced Jul 22, 2026
teknium1
added a commit
that referenced
this pull request
Jul 23, 2026
…or-preserving default Follow-up fixes on top of the salvaged #22566 mechanism: - N-collector now counts only REAL actionable user turns via _is_actionable_user_turn + _is_synthetic_compression_user_turn — the same filter pair _find_last_user_message_idx uses post-#69291. The contributor's bare role=='user' + _is_context_summary_content check let blank platform echoes and continuation/todo rows consume N slots, silently degrading the guarantee. - Default flipped 3 -> 1 (behavior-preserving): a default of 3 was measured to change the tail cut on transcripts whose budget covers only the last turn. min_tail_user_messages=1 delegates to the existing single-user anchor; N>1 is opt-in, and the call site is gated so the default path is byte-identical to main. - Hardened config parse in agent_init (bool rejected, fractional floats rejected, floor 1) matching the max_attempts parser shape. - Wired the recurring external-PR config gaps: hermes_cli/config.py DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py). - Regression tests: blank echoes / synthetic rows don't count toward N; tool-call/result pairs never split by the N-boundary (no-orphan both directions); N-guarantee wins over tail_token_budget and the _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default parity pin; DEFAULT_CONFIG pin.
teknium1
added a commit
that referenced
this pull request
Jul 23, 2026
…or-preserving default Follow-up fixes on top of the salvaged #22566 mechanism: - N-collector now counts only REAL actionable user turns via _is_actionable_user_turn + _is_synthetic_compression_user_turn — the same filter pair _find_last_user_message_idx uses post-#69291. The contributor's bare role=='user' + _is_context_summary_content check let blank platform echoes and continuation/todo rows consume N slots, silently degrading the guarantee. - Default flipped 3 -> 1 (behavior-preserving): a default of 3 was measured to change the tail cut on transcripts whose budget covers only the last turn. min_tail_user_messages=1 delegates to the existing single-user anchor; N>1 is opt-in, and the call site is gated so the default path is byte-identical to main. - Hardened config parse in agent_init (bool rejected, fractional floats rejected, floor 1) matching the max_attempts parser shape. - Wired the recurring external-PR config gaps: hermes_cli/config.py DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py). - Regression tests: blank echoes / synthetic rows don't count toward N; tool-call/result pairs never split by the N-boundary (no-orphan both directions); N-guarantee wins over tail_token_budget and the _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default parity pin; DEFAULT_CONFIG pin.
teknium1
added a commit
that referenced
this pull request
Jul 23, 2026
…or-preserving default Follow-up fixes on top of the salvaged #22566 mechanism: - N-collector now counts only REAL actionable user turns via _is_actionable_user_turn + _is_synthetic_compression_user_turn — the same filter pair _find_last_user_message_idx uses post-#69291. The contributor's bare role=='user' + _is_context_summary_content check let blank platform echoes and continuation/todo rows consume N slots, silently degrading the guarantee. - Default flipped 3 -> 1 (behavior-preserving): a default of 3 was measured to change the tail cut on transcripts whose budget covers only the last turn. min_tail_user_messages=1 delegates to the existing single-user anchor; N>1 is opt-in, and the call site is gated so the default path is byte-identical to main. - Hardened config parse in agent_init (bool rejected, fractional floats rejected, floor 1) matching the max_attempts parser shape. - Wired the recurring external-PR config gaps: hermes_cli/config.py DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py). - Regression tests: blank echoes / synthetic rows don't count toward N; tool-call/result pairs never split by the N-boundary (no-orphan both directions); N-guarantee wins over tail_token_budget and the _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default parity pin; DEFAULT_CONFIG pin.
teknium1
added a commit
that referenced
this pull request
Jul 24, 2026
…or-preserving default Follow-up fixes on top of the salvaged #22566 mechanism: - N-collector now counts only REAL actionable user turns via _is_actionable_user_turn + _is_synthetic_compression_user_turn — the same filter pair _find_last_user_message_idx uses post-#69291. The contributor's bare role=='user' + _is_context_summary_content check let blank platform echoes and continuation/todo rows consume N slots, silently degrading the guarantee. - Default flipped 3 -> 1 (behavior-preserving): a default of 3 was measured to change the tail cut on transcripts whose budget covers only the last turn. min_tail_user_messages=1 delegates to the existing single-user anchor; N>1 is opt-in, and the call site is gated so the default path is byte-identical to main. - Hardened config parse in agent_init (bool rejected, fractional floats rejected, floor 1) matching the max_attempts parser shape. - Wired the recurring external-PR config gaps: hermes_cli/config.py DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py). - Regression tests: blank echoes / synthetic rows don't count toward N; tool-call/result pairs never split by the N-boundary (no-orphan both directions); N-guarantee wins over tail_token_budget and the _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default parity pin; DEFAULT_CONFIG pin.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Aug 2, 2026
- thread_context: propagate_context_to_thread (fail-closed callbacks) - windows_ssh_runtime: process_state (nonce-anchored identity) - gateway: launchd_start (self-heal + kickstart), _wait_for_systemd_service_restart - local env: _make_run_env (provider strip + PATH ladder), get_temp_dir (Windows-safe forward-slash cache) - model_switch: _configured_provider_matches (NousResearch#45006 exact routing) - gui_uninstall: uninstall_gui (userdata opt-out) - credential_lifecycle: _scrub_config_yaml_mirrors (value-matched) - main: _stop_desktop_processes_locking_build (release-scope) - plugins_cmd: _prompt_plugin_env_vars (rich spec) - auth: persist_nous_credentials (label embed + shared mirror) - context_compressor: _ensure_last_n_user_messages_in_tail (NousResearch#69291) - yuanbao: parse_json_push (Pascal+snake dual) - session_export_md: _export_body_without_hash (verification footer) - skill_manager_tool: _find_skill_in_other_profiles (fail-quiet) - kanban_tools: _handle_attach_url (leaf-derived filename) - webhook_filters: filter_matches (all/any/not ops) - nous_subscription: ensure_nous_portal_access (coverage category) - pet/imagegen: resolve_provider (ref-capable ladder)
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…or-preserving default Follow-up fixes on top of the salvaged NousResearch#22566 mechanism: - N-collector now counts only REAL actionable user turns via _is_actionable_user_turn + _is_synthetic_compression_user_turn — the same filter pair _find_last_user_message_idx uses post-NousResearch#69291. The contributor's bare role=='user' + _is_context_summary_content check let blank platform echoes and continuation/todo rows consume N slots, silently degrading the guarantee. - Default flipped 3 -> 1 (behavior-preserving): a default of 3 was measured to change the tail cut on transcripts whose budget covers only the last turn. min_tail_user_messages=1 delegates to the existing single-user anchor; N>1 is opt-in, and the call site is gated so the default path is byte-identical to main. - Hardened config parse in agent_init (bool rejected, fractional floats rejected, floor 1) matching the max_attempts parser shape. - Wired the recurring external-PR config gaps: hermes_cli/config.py DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py). - Regression tests: blank echoes / synthetic rows don't count toward N; tool-call/result pairs never split by the N-boundary (no-orphan both directions); N-guarantee wins over tail_token_budget and the _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default parity pin; DEFAULT_CONFIG pin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Context compaction now anchors on the latest actionable user turn, so a blank platform-echo user row can no longer displace the user's real request (e.g. an async-completion event) into the summarized middle. Root cause:
_find_last_user_message_idxanchored on ANY non-summary user row, including genuinely empty platform echoes.Changes
agent/context_compressor.py: new_is_actionable_user_turnclassifier (conservative: image/audio/unknown structured blocks count as actionable; only genuinely empty text is blank) and_blank_echo_indices_after(removes blank echoes only when contiguous after the actionable event and assistant-followed); tail anchor now uses actionability; bridge retention guarded bybridge_idx > compress_start; docstring note that blank-echo removal runs in the cheap pre-pass and therefore survives a summary abort (precedented by Phase-1 pruning).tests/agent/test_compressor_actionable_tail_anchor.py: new suite covering blank-echo displacement, leading blanks,input_textblanks, multimodal actionability, bridge retention.tests/agent/test_context_compressor.py: one expectation updated (dropped_count3→2) — intentional behavior change: the blank echo is now stripped pre-summary instead of counted as dropped.Validation
Targeted tests:
tests/agent/ -k 'actionable or anchor or last_user'→ 56 passed; actionable-anchor + assistant-anchor + context-compressor suites → 222 passed, 0 failed.Credit
Salvaged from #62747 by @John-Lussier.
Infographic