Bug Description
The Gateway's _handle_compress_command (manual /compress) and the Gateway Session Hygiene auto-compress paths both create a temporary AIAgent (tmp_agent / _hyg_agent) without passing session_db= to the constructor. This causes tmp_agent._session_db = None, which makes _compress_context()'s session-rotate block (if self._session_db:) silently no-op.
The downstream session_store.rewrite_transcript(session_entry.session_id, compressed) then overwrites the original session's transcript with the compressed message list, destroying searchable history — exactly the data-loss scenario commits 1544638f4 and cd2e180ef intended to prevent.
Those two earlier commits added the if new_session_id != session_entry.session_id: swap after _compress_context(), but they assume _compress_context() actually rotated the session. When _session_db is None, it doesn't, so new_session_id == session_entry.session_id and the rewrite still hits the original session_id.
This is the same class of bug as PR #20021 (ACP adapter missing session_db) and PR #4802 (API server adapter missing session_db). The Gateway compress/hygiene paths have the same omission.
Steps to Reproduce
- Run gateway with Telegram or Discord bot.
- Have a long enough conversation (60+ messages).
- Send
/compress in Telegram or Discord.
- Inspect
state.db:
SELECT id, source, parent_session_id, end_reason, message_count
FROM sessions WHERE source = 'telegram' ORDER BY started_at DESC LIMIT 5;
- Observe:
- No new session row with
parent_session_id pointing to the original.
- The original session's
end_reason is NULL (not 'compression').
- The original session's
message_count reflects the compressed count (e.g., 10) rather than the original (e.g., 69) — original messages are physically gone.
Expected Behavior
Per the documented compression behavior (sessions.md "Auto-Lineage on Compression"):
- The original session should be closed with
end_reason='compression' and its messages preserved intact.
- A new continuation session should be created with
parent_session_id pointing to the original.
- The compressed message list should be written to the new session, not the original.
session_search should still find the original transcript.
Actual Behavior
- The original session's messages are overwritten with the compressed (head + summary + tail) list.
end_reason stays NULL, parent_session_id stays NULL.
- No new continuation session is created.
- The original transcript (e.g., 69 messages of conversation history) is physically destroyed and unrecoverable from
state.db.
- No
context compression started: info log is emitted (the session-rotate block at run_agent.py:9048 is entirely skipped).
Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Debug Report
Debug report available on request (contains PII in default output, withheld from public issue).
Operating System
macOS 14.8.5
Python Version
3.11.15
Hermes Version
0.12.0 (2026.4.30)
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
Affected Locations
gateway/run.py:_handle_compress_command (around L8780):
tmp_agent = AIAgent(
**runtime_kwargs,
model=model,
max_iterations=4,
quiet_mode=True,
skip_memory=True,
enabled_toolsets=["memory"],
session_id=session_entry.session_id,
# ← session_db= is missing
)
Same pattern in Gateway Session Hygiene path (around L5688) with _hyg_agent = AIAgent(...).
Why _session_db is None matters
run_agent.py:_compress_context() session rotate is guarded by if self._session_db: (L9048). When _session_db is None, the entire rotate block is skipped and tmp_agent.session_id is never changed.
Why existing safeguards don't help
Commits 1544638f4 and cd2e180ef added:
new_session_id = tmp_agent.session_id
if new_session_id != session_entry.session_id:
session_entry.session_id = new_session_id
self.session_store._save()
self.session_store.rewrite_transcript(new_session_id, compressed)
When _session_db is None, new_session_id == session_entry.session_id (no rotation happened), so rewrite_transcript overwrites the original session.
Proposed Fix (optional)
Pass session_db=self._session_db to both tmp_agent and _hyg_agent constructors:
tmp_agent = AIAgent(
**runtime_kwargs,
model=model,
max_iterations=4,
quiet_mode=True,
skip_memory=True,
enabled_toolsets=["memory"],
session_id=session_entry.session_id,
+ session_db=self._session_db,
)
Same for _hyg_agent. With this, _compress_context()'s session-rotate block runs, the original transcript is preserved (closed with end_reason='compression'), and the new continuation session gets a fresh session_id with parent_session_id pointing to the original.
We've verified this fix works in production (Telegram /compress with 112-message session — original preserved, new session created with correct parent linkage).
Until upstream merges the fix, ContextEngine plugins can work around this by detecting parent_agent._session_db is None and instantiating SessionDB() directly to perform the rotate themselves.
Are you willing to submit a PR for this?
Bug Description
The Gateway's
_handle_compress_command(manual/compress) and the Gateway Session Hygiene auto-compress paths both create a temporaryAIAgent(tmp_agent/_hyg_agent) without passingsession_db=to the constructor. This causestmp_agent._session_db = None, which makes_compress_context()'s session-rotate block (if self._session_db:) silently no-op.The downstream
session_store.rewrite_transcript(session_entry.session_id, compressed)then overwrites the original session's transcript with the compressed message list, destroying searchable history — exactly the data-loss scenario commits1544638f4andcd2e180efintended to prevent.Those two earlier commits added the
if new_session_id != session_entry.session_id:swap after_compress_context(), but they assume_compress_context()actually rotated the session. When_session_db is None, it doesn't, sonew_session_id == session_entry.session_idand the rewrite still hits the original session_id.This is the same class of bug as PR #20021 (ACP adapter missing
session_db) and PR #4802 (API server adapter missingsession_db). The Gateway compress/hygiene paths have the same omission.Steps to Reproduce
/compressin Telegram or Discord.state.db:parent_session_idpointing to the original.end_reasonis NULL (not'compression').message_countreflects the compressed count (e.g., 10) rather than the original (e.g., 69) — original messages are physically gone.Expected Behavior
Per the documented compression behavior (sessions.md "Auto-Lineage on Compression"):
end_reason='compression'and its messages preserved intact.parent_session_idpointing to the original.session_searchshould still find the original transcript.Actual Behavior
end_reasonstays NULL,parent_session_idstays NULL.state.db.context compression started:info log is emitted (the session-rotate block atrun_agent.py:9048is entirely skipped).Affected Component
Gateway (Telegram/Discord/Slack/WhatsApp)
Messaging Platform (if gateway-related)
Telegram
Debug Report
Debug report available on request (contains PII in default output, withheld from public issue).Operating System
macOS 14.8.5
Python Version
3.11.15
Hermes Version
0.12.0 (2026.4.30)
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
Affected Locations
gateway/run.py:_handle_compress_command(around L8780):Same pattern in Gateway Session Hygiene path (around L5688) with
_hyg_agent = AIAgent(...).Why
_session_db is Nonemattersrun_agent.py:_compress_context()session rotate is guarded byif self._session_db:(L9048). When_session_db is None, the entire rotate block is skipped andtmp_agent.session_idis never changed.Why existing safeguards don't help
Commits
1544638f4andcd2e180efadded:When
_session_db is None,new_session_id == session_entry.session_id(no rotation happened), sorewrite_transcriptoverwrites the original session.Proposed Fix (optional)
Pass
session_db=self._session_dbto bothtmp_agentand_hyg_agentconstructors:tmp_agent = AIAgent( **runtime_kwargs, model=model, max_iterations=4, quiet_mode=True, skip_memory=True, enabled_toolsets=["memory"], session_id=session_entry.session_id, + session_db=self._session_db, )Same for
_hyg_agent. With this,_compress_context()'s session-rotate block runs, the original transcript is preserved (closed withend_reason='compression'), and the new continuation session gets a freshsession_idwithparent_session_idpointing to the original.We've verified this fix works in production (Telegram /compress with 112-message session — original preserved, new session created with correct parent linkage).
Until upstream merges the fix, ContextEngine plugins can work around this by detecting
parent_agent._session_db is Noneand instantiatingSessionDB()directly to perform the rotate themselves.Are you willing to submit a PR for this?