fix(state): heal durable alternation violations at the restore boundary

A turn that persists a user row with no assistant row (suppressed reply,
or two concurrent turns interleaving their flushes) leaves a user;user
pair in state.db. The defensive pre-request repair_message_sequence then
re-fires on EVERY request for the rest of the session's life — it mutates
only the per-request list, never the stored transcript.

Add repair_alternation (default False) to get_messages_as_conversation
and pass it from the three live-replay restore sites (gateway
load_transcript, CLI session resume x2). Inspection/export consumers
(trace upload, context guard, api_server history) keep the verbatim
default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Regina 2026-07-15 18:07:24 +07:00 committed by Teknium
parent d73a6f5ac2
commit ee659d1d8f
4 changed files with 111 additions and 3 deletions

View file

@ -286,7 +286,9 @@ class CLIAgentSetupMixin:
resolved_meta = self._session_db.get_session(self.session_id)
if resolved_meta:
session_meta = resolved_meta
restored = self._session_db.get_messages_as_conversation(self.session_id)
restored = self._session_db.get_messages_as_conversation(
self.session_id, repair_alternation=True
)
if restored:
restored = [m for m in restored if m.get("role") != "session_meta"]
self.conversation_history = restored
@ -484,7 +486,9 @@ class CLIAgentSetupMixin:
if resolved_meta:
session_meta = resolved_meta
restored = self._session_db.get_messages_as_conversation(self.session_id)
restored = self._session_db.get_messages_as_conversation(
self.session_id, repair_alternation=True
)
if restored:
restored = [m for m in restored if m.get("role") != "session_meta"]
self.conversation_history = restored