Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions agent/conversation_compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,21 @@ def _release_lock() -> None:
set_current_session_id(agent.session_id)
except Exception:
os.environ["HERMES_SESSION_ID"] = agent.session_id
# The gateway/tools session context (ContextVar + env) and the
# logging session context are SEPARATE mechanisms. The call above
# moves the former; the ``[session_id]`` tag on log lines comes
# from ``hermes_logging._session_context`` (set once per turn in
# conversation_loop.py). Without this, post-rotation log lines in
# the same turn keep the STALE old id while the message/DB/gateway
# state carry the new one — breaking log correlation exactly at the
# compaction boundary (see #34089). Guarded separately so a logging
# failure can never regress the routing update above.
try:
from hermes_logging import set_session_context

set_session_context(agent.session_id)
except Exception:
pass
agent._session_db_created = False
agent._session_db.create_session(
session_id=agent.session_id,
Expand Down
1 change: 1 addition & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"dirtyren@users.noreply.github.com": "dirtyren",
"islam666@users.noreply.github.com": "islam666",
"25539605+lsaether@users.noreply.github.com": "lsaether",
"30080538+JimStenstrom@users.noreply.github.com": "JimStenstrom",
"zhaolei.vc@bytedance.com": "zhaoleibd",
"jeffrobodie@gmail.com": "jeffrobodie-glitch",
"kyssta-exe@users.noreply.github.com": "kyssta-exe",
Expand Down
80 changes: 80 additions & 0 deletions tests/agent/test_compression_logging_session_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Regression: compaction must move the LOGGING session context with the id.

When ``compress_context`` rotates ``agent.session_id`` it updates the
gateway/tools session context (``gateway.session_context.set_current_session_id``,
which moves ``HERMES_SESSION_ID`` env + ContextVar). The ``[session_id]`` tag on
log lines comes from a SEPARATE mechanism — ``hermes_logging._session_context``
(a threading.local read by the global LogRecord factory), set once per turn in
``conversation_loop.py``. Before the fix, the rotation block never updated it, so
log lines emitted after a mid-turn compaction carried the STALE old id while the
message body / session DB / gateway state carried the new one (see #34089). This
asserts the logging context follows the rotation.
"""

from __future__ import annotations

import os
from pathlib import Path
from unittest.mock import MagicMock, patch

import hermes_logging
from hermes_state import SessionDB


def _build_agent_with_db(db: SessionDB, session_id: str):
"""Mirror tests/agent/test_compression_concurrent_fork.py's harness."""
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
from run_agent import AIAgent

agent = AIAgent(
api_key="test-key",
base_url="https://openrouter.ai/api/v1",
model="test/model",
quiet_mode=True,
session_db=db,
session_id=session_id,
skip_context_files=True,
skip_memory=True,
)

compressor = MagicMock()
compressor.compress.return_value = [
{"role": "user", "content": "[CONTEXT COMPACTION] summary"},
{"role": "user", "content": "tail"},
]
compressor.compression_count = 1
compressor.last_prompt_tokens = 0
compressor.last_completion_tokens = 0
compressor._last_summary_error = None
compressor._last_compress_aborted = False
compressor._last_aux_model_failure_model = None
compressor._last_aux_model_failure_error = None
agent.context_compressor = compressor
return agent


def test_logging_session_context_follows_compression_rotation(tmp_path: Path) -> None:
db = SessionDB(db_path=tmp_path / "state.db")
parent_sid = "PARENT_LOGCTX_SESSION"
db.create_session(parent_sid, source="cli")

agent = _build_agent_with_db(db, parent_sid)

# conversation_loop.py pins the logging tag to the ORIGINAL id at turn start.
hermes_logging.set_session_context(parent_sid)
try:
messages = [{"role": "user", "content": f"m{i}"} for i in range(20)]
agent._compress_context(messages, "sys", approx_tokens=120_000)

# The id actually rotated (sanity — otherwise the assertion is vacuous).
assert agent.session_id != parent_sid

# The logging context must now match the NEW id, not the stale one.
current = getattr(hermes_logging._session_context, "session_id", None)
assert current == agent.session_id, (
"Logging session context did not follow the compaction rotation: "
f"log tag still {current!r}, agent.session_id is {agent.session_id!r} "
"(see #34089)."
)
finally:
hermes_logging.clear_session_context()
Loading