From f008ee1019b3533f09e3ae20c72bac31f78b0694 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:18:00 -0700 Subject: [PATCH] fix(session): preserve reasoning fields in rewrite_transcript (#3311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rewrite_transcript (used by /retry, /undo, /compress) was calling append_message without reasoning, reasoning_details, or codex_reasoning_items — permanently dropping them from SQLite. Co-authored-by: alireza78a --- gateway/session.py | 6 ++++- tests/gateway/test_session.py | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/gateway/session.py b/gateway/session.py index b85ac3e3a..d22c6d043 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -955,13 +955,17 @@ class SessionStore: try: self._db.clear_messages(session_id) for msg in messages: + role = msg.get("role", "unknown") self._db.append_message( session_id=session_id, - role=msg.get("role", "unknown"), + role=role, content=msg.get("content"), tool_name=msg.get("tool_name"), tool_calls=msg.get("tool_calls"), tool_call_id=msg.get("tool_call_id"), + reasoning=msg.get("reasoning") if role == "assistant" else None, + reasoning_details=msg.get("reasoning_details") if role == "assistant" else None, + codex_reasoning_items=msg.get("codex_reasoning_items") if role == "assistant" else None, ) except Exception as e: logger.debug("Failed to rewrite transcript in DB: %s", e) diff --git a/tests/gateway/test_session.py b/tests/gateway/test_session.py index f31a80c3a..8d4131ab1 100644 --- a/tests/gateway/test_session.py +++ b/tests/gateway/test_session.py @@ -859,3 +859,46 @@ class TestLastPromptTokens: billing_base_url=None, model="openai/gpt-5.4", ) + + +class TestRewriteTranscriptPreservesReasoning: + """rewrite_transcript must not drop reasoning fields from SQLite.""" + + def test_reasoning_survives_rewrite(self, tmp_path): + from hermes_state import SessionDB + + db = SessionDB(db_path=tmp_path / "test.db") + session_id = "reasoning-test" + db.create_session(session_id=session_id, source="cli") + + # Insert a message WITH all three reasoning fields + db.append_message( + session_id=session_id, + role="assistant", + content="The answer is 42.", + reasoning="I need to think step by step.", + reasoning_details=[{"type": "summary", "text": "step by step"}], + codex_reasoning_items=[{"id": "r1", "type": "reasoning"}], + ) + + # Verify all three were stored + before = db.get_messages_as_conversation(session_id) + assert before[0].get("reasoning") == "I need to think step by step." + assert before[0].get("reasoning_details") == [{"type": "summary", "text": "step by step"}] + assert before[0].get("codex_reasoning_items") == [{"id": "r1", "type": "reasoning"}] + + # Now simulate /retry: build the SessionStore and call rewrite_transcript + config = GatewayConfig() + with patch("gateway.session.SessionStore._ensure_loaded"): + store = SessionStore(sessions_dir=tmp_path, config=config) + store._db = db + store._loaded = True + + # rewrite_transcript receives the messages that load_transcript returned + store.rewrite_transcript(session_id, before) + + # Load again — all three reasoning fields must survive + after = db.get_messages_as_conversation(session_id) + assert after[0].get("reasoning") == "I need to think step by step." + assert after[0].get("reasoning_details") == [{"type": "summary", "text": "step by step"}] + assert after[0].get("codex_reasoning_items") == [{"id": "r1", "type": "reasoning"}]