Addresses the egilewski/CodeRabbit and teknium1 reviews on PR #52355.
1) Persisted-row chat scope (egilewski/CodeRabbit). The sessions table stored
only source + user_id, so an identity-bearing caller could resume/list an
INACTIVE persisted row that matched source+user_id but belonged to a
DIFFERENT chat (probe: same user moves `same_user_chat_b` into chat-a).
Persist the messaging origin and compare it:
- schema: sessions gains origin_chat_id / origin_thread_id (declarative
auto-migration via the existing column reconciler).
- SessionDB._insert_session_row accepts + writes the two columns.
- the gateway records them at every origin-bearing creation: both
SessionStore create paths (get_or_create_session + reset/switch) and the
/title path that materializes a store-only session into the DB.
- _resume_target_allowed's identity branch now also requires
origin_chat_id AND origin_thread_id to match the caller. Legacy rows with
NULL origin (created before this change) cannot prove chat origin and
fail closed — resume them via a live session or an admin --all override.
The /sessions listing inherits the fix (non-Matrix rows route through the
same helper).
2) DM key-contract mirror (teknium1). _same_origin_chat's DM branch only
compared user_id and allowed when either side was missing, diverging from
build_session_key (no-chat_id DM keys are built from user_id_alt or
user_id). It now: treats an equal non-blank chat_id as sufficient (the DM
key IS the chat_id when present), and otherwise compares the effective
participant id (user_id_alt or user_id), failing closed on a
missing/different participant so two no-chat_id DM origins are never
conflated.
Tests: add same-user/different-chat (e2e + unit) and chat-scope unit cases;
add DM no-chat_id / user_id_alt / no-identity / same-chat_id cases; update
existing fixtures to record origin_chat_id like the gateway does; make the
cross-room `/resume --all` listing test run as admin (cross-room listing is
admin-gated) and give the boundary-state resume runner a live same-origin so
its post-resume clearing assertions exercise an authorized resume.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AIAgent.flush_memories pre-compression save, the gateway
_flush_memories_for_session, and everything feeding them are
obsolete now that the background memory/skill review handles
persistent memory extraction.
Problems with flush_memories:
- Pre-dates the background review loop. It was the only memory-save
path when introduced; the background review now fires every 10 user
turns on CLI and gateway alike, which is far more frequent than
compression or session reset ever triggered flush.
- Blocking and synchronous. Pre-compression flush ran on the live agent
before compression, blocking the user-visible response.
- Cache-breaking. Flush built a temporary conversation prefix
(system prompt + memory-only tool list) that diverged from the live
conversation's cached prefix, invalidating prompt caching. The
gateway variant spawned a fresh AIAgent with its own clean prompt
for each finalized session — still cache-breaking, just in a
different process.
- Redundant. Background review runs in the live conversation's
session context, gets the same content, writes to the same memory
store, and doesn't break the cache. Everything flush_memories
claimed to preserve is already covered.
What this removes:
- AIAgent.flush_memories() method (~248 LOC in run_agent.py)
- Pre-compression flush call in _compress_context
- flush_memories call sites in cli.py (/new + exit)
- GatewayRunner._flush_memories_for_session + _async_flush_memories
(and the 3 call sites: session expiry watcher, /new, /resume)
- 'flush_memories' entry from DEFAULT_CONFIG auxiliary tasks,
hermes tools UI task list, auxiliary_client docstrings
- _memory_flush_min_turns config + init
- #15631's headroom-deduction math in
_check_compression_model_feasibility (headroom was only needed
because flush dragged the full main-agent system prompt along;
the compression summariser sends a single user-role prompt so
new_threshold = aux_context is safe again)
- The dedicated test files and assertions that exercised
flush-specific paths
What this renames (with read-time backcompat on sessions.json):
- SessionEntry.memory_flushed -> SessionEntry.expiry_finalized.
The session-expiry watcher still uses the flag to avoid re-running
finalize/eviction on the same expired session; the new name
reflects what it now actually gates. from_dict() reads
'expiry_finalized' first, falls back to the legacy 'memory_flushed'
key so existing sessions.json files upgrade seamlessly.
Supersedes #15631 and #15638.
Tested: 383 targeted tests pass across run_agent/, agent/, cli/,
and gateway/ session-boundary suites. No behavior regressions —
background memory review continues to handle persistent memory
extraction on both CLI and gateway.
Follow-up to the /resume and /branch cleanup in the previous commit:
/new is a conversation-boundary operation too, so session-scoped
dangerous-command approvals and /yolo state must not survive it.
Adds a scoped unit test for _clear_session_boundary_security_state that
also covers the /new path (which calls the same helper).