diff --git a/api/compression_anchor.py b/api/compression_anchor.py index 98799efc..3a457d57 100644 --- a/api/compression_anchor.py +++ b/api/compression_anchor.py @@ -1,5 +1,36 @@ """ Shared helpers for session compression anchor metadata. + +Manual compression anchoring versus automatic compression paths +=============================================================== + +When ``auto_compression=True`` is passed to ``visible_messages_for_anchor()``, +the function accepts a broader set of message content types (including +provider-style ``input_text`` / ``output_text`` parts) and metadata markers +(``reasoning``, ``thinking``, etc.) from any non-tool role. This enables the +streaming auto-compression path to determine which messages should anchor +compression UI metadata without being limited to the legacy manual-compression +rules. + +When ``auto_compression=False`` (the default), the function applies the +historical manual-compression rules: only plain ``text`` content parts from +non-assistant roles are counted. + +Why this module exists +====================== + +Compression anchoring needs to identify which messages in a session transcript +are semantically significant enough to seed the compression UI metadata (e.g., +message count, token budget display). The original implementation hard-coded +these rules in multiple places. This module consolidates the logic so that: + +1. Manual compression anchoring (CLI/legacy path) uses the stricter ruleset. +2. Automatic compression (streaming/agent path) can leverage the relaxed ruleset + when it knows it is handling provider-style messages. + +Callers specify ``auto_compression=True`` when the messages may originate from +an automatic/compression-aware pipeline, and ``False`` (default) for manual +compression contexts. """ diff --git a/api/profiles.py b/api/profiles.py index 8f02bd16..bebd8201 100644 --- a/api/profiles.py +++ b/api/profiles.py @@ -41,7 +41,7 @@ _tls = threading.local() _SKILL_HOME_MODULES = ("tools.skills_tool", "tools.skill_manager_tool") -def _patch_skill_home_modules(home: Path) -> None: +def patch_skill_home_modules(home: Path) -> None: """Patch imported skill modules that cache HERMES_HOME at import time.""" for module_name in _SKILL_HOME_MODULES: module = sys.modules.get(module_name) @@ -628,7 +628,7 @@ def _set_hermes_home(home: Path): """Set HERMES_HOME env var and monkey-patch cached module-level paths.""" os.environ['HERMES_HOME'] = str(home) - _patch_skill_home_modules(home) + patch_skill_home_modules(home) # Patch cron/jobs module-level cache try: diff --git a/api/streaming.py b/api/streaming.py index fd772414..d60c4880 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -2286,7 +2286,7 @@ def _run_agent_streaming( # process-level active-profile global. Falls back gracefully. try: from api.profiles import ( - _patch_skill_home_modules, + patch_skill_home_modules, get_hermes_home_for_profile, get_profile_runtime_env, ) @@ -2296,7 +2296,7 @@ def _run_agent_streaming( except ImportError: _profile_home = os.environ.get('HERMES_HOME', '') _profile_runtime_env = {} - _patch_skill_home_modules = None + patch_skill_home_modules = None # Capture the resolved profile name now, while profile context is # reliable. Used in the compression migration block to stamp s.profile @@ -2349,8 +2349,8 @@ def _run_agent_streaming( # above, so we only do lightweight sys.modules lookups and # attribute assignments here — no first-time import under # the lock (#2024). - if _patch_skill_home_modules is not None: - _patch_skill_home_modules(Path(_profile_home)) + if patch_skill_home_modules is not None: + patch_skill_home_modules(Path(_profile_home)) # Lock released — agent runs without holding it # ── MCP Server Discovery (lazy import, idempotent) ── # MUST run AFTER the HERMES_HOME mutation above — `discover_mcp_tools()` diff --git a/tests/test_issue2024_env_lock_skill_imports.py b/tests/test_issue2024_env_lock_skill_imports.py index b2a5c952..9f7a5819 100644 --- a/tests/test_issue2024_env_lock_skill_imports.py +++ b/tests/test_issue2024_env_lock_skill_imports.py @@ -165,7 +165,7 @@ class TestSysModulesLookupInEnvLock: lock_lines.append(line) lock_source = "\n".join(lock_lines) - assert "_patch_skill_home_modules" in lock_source, ( + assert "patch_skill_home_modules" in lock_source, ( "Inside `_ENV_LOCK`, streaming must use the shared skill module " "cache patch helper instead of duplicating module-specific logic " "(#2023/#2024)" @@ -179,15 +179,15 @@ class TestSysModulesLookupInEnvLock: node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef) - and node.name == "_patch_skill_home_modules" + and node.name == "patch_skill_home_modules" ), None, ) - assert helper is not None, "_patch_skill_home_modules() must be defined" + assert helper is not None, "patch_skill_home_modules() must be defined" helper_source = ast.get_source_segment(source, helper) or "" assert "sys.modules.get" in helper_source, ( - "_patch_skill_home_modules() must use sys.modules.get(), not import, " + "patch_skill_home_modules() must use sys.modules.get(), not import, " "so env-lock callers do not trigger first-time imports (#2024)" ) assert "HERMES_HOME" in helper_source