From 9e62232b0762663fd7c677d878bdfdbbb7c8452a Mon Sep 17 00:00:00 2001 From: webtecnica Date: Sat, 26 Sep 2026 01:25:34 -0300 Subject: [PATCH] fix(gateway): carry _db_persisted stamp in _build_replay_entry to prevent duplicate transcript flushes (#123462) (cherry picked from commit 7366ba26e91907641527d698a72f0f57de530eba) --- gateway/run.py | 2 + .../test_cached_agent_history_guard.py | 2 +- .../test_gateway_history_persistence_stamp.py | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/gateway/test_gateway_history_persistence_stamp.py diff --git a/gateway/run.py b/gateway/run.py index 248e6438f5..2faf45ddb9 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1148,6 +1148,8 @@ def _build_replay_entry( entry[_rkey] = _rval if preserve_timestamp and msg.get("timestamp"): entry["timestamp"] = msg["timestamp"] + if msg.get("_db_persisted"): + entry["_db_persisted"] = True return entry diff --git a/tests/gateway/test_cached_agent_history_guard.py b/tests/gateway/test_cached_agent_history_guard.py index 63f412ad29..7a962ffe67 100644 --- a/tests/gateway/test_cached_agent_history_guard.py +++ b/tests/gateway/test_cached_agent_history_guard.py @@ -31,7 +31,7 @@ def test_select_cached_history_keeps_expected_replay_cleanup(): assert observed is None assert len(persisted) < len(live) - assert persisted == [{"role": "user", "content": "check status"}] + assert persisted == [{"role": "user", "content": "check status", "_db_persisted": True}] assert _select_cached_agent_history(persisted, live) is persisted diff --git a/tests/gateway/test_gateway_history_persistence_stamp.py b/tests/gateway/test_gateway_history_persistence_stamp.py new file mode 100644 index 0000000000..86ceaf4fba --- /dev/null +++ b/tests/gateway/test_gateway_history_persistence_stamp.py @@ -0,0 +1,40 @@ +from types import SimpleNamespace +from agent.session_persistence import _db_flush_collect +from gateway.run import _build_gateway_agent_history + + +def test_build_gateway_agent_history_preserves_db_persisted_marker(): + """Verify plain user and assistant rows preserve the _db_persisted marker (#123462).""" + stored = [ + {"role": "user", "content": "first", "_db_persisted": True, "timestamp": 1.0}, + {"role": "assistant", "content": "reply", "_db_persisted": True}, + ] + history, _ = _build_gateway_agent_history(stored) + + assert len(history) == 2 + assert history[0]["role"] == "user" + assert history[0].get("_db_persisted") is True + assert history[1]["role"] == "assistant" + assert history[1].get("_db_persisted") is True + + +def test_db_flush_collect_does_not_recollect_gateway_history_rows(): + """Verify tool turn flushes do not re-append rebuilt user/assistant rows to state.db (#123462).""" + stored = [ + {"role": "user", "content": "first", "_db_persisted": True, "timestamp": 1.0}, + {"role": "assistant", "content": "reply", "_db_persisted": True}, + ] + history, _ = _build_gateway_agent_history(stored) + new = {"role": "user", "content": "new"} + + agent = SimpleNamespace( + session_id="s", + _flushed_db_message_ids=None, + _last_flushed_db_idx=0, + _db_flush_scan_prefix=None, + _persist_user_message_idx=None, + _pending_cli_user_message=None, + ) + rows, msgs = _db_flush_collect(agent, list(history) + [new], None) + + assert msgs == [new]