fix(gateway): distinguish durable cached transcript rows

This commit is contained in:
TomAce7 2026-08-03 13:46:41 -04:00 committed by Teknium
parent 59f1aa8bf6
commit 6e8dcb8f47
2 changed files with 86 additions and 7 deletions

View File

@ -1452,21 +1452,33 @@ def _select_cached_agent_history(
persisted_history: List[Dict[str, Any]],
live_history: Any,
) -> List[Dict[str, Any]]:
"""Prefer a cached agent's live in-memory transcript over a shorter
persisted one.
"""Prefer a cached live transcript only when it is longer and contains at
least one real, non-ephemeral unpersisted row.
Guards the FTS write-corruption case (#50502): when message writes fail
silently through corrupt FTS triggers, the next turn reloads a stale/empty
``conversation_history`` from disk even though the same cached ``AIAgent``
still holds the full live ``_session_messages``. Replacing the live
transcript with that shorter persisted copy causes immediate same-session
amnesia. When the live transcript is strictly longer, keep it.
still holds unpersisted real rows in ``_session_messages``. Replacing those
rows with the shorter persisted copy causes immediate same-session amnesia.
Length alone does not trigger retention.
Returns ``persisted_history`` unchanged unless the live copy is a longer
list, in which case a copy of the live transcript is returned.
list containing at least one real transcript row without the intrinsic
``_db_persisted`` marker. A longer all-durable list can be an expected
replay-filtering delta (for example, cleanup of an interrupted read-only
tool block). Deliberately unpersisted retry scaffolding is ignored.
"""
if isinstance(live_history, list) and len(live_history) > len(persisted_history):
return list(live_history)
from run_agent import _is_ephemeral_scaffolding
has_unpersisted_row = any(
isinstance(message, dict)
and not message.get("_db_persisted")
and not _is_ephemeral_scaffolding(message)
for message in live_history
)
if has_unpersisted_row:
return list(live_history)
return persisted_history

View File

@ -0,0 +1,67 @@
"""Regression tests for cached gateway transcript selection."""
from gateway.run import _build_gateway_agent_history, _select_cached_agent_history
def test_select_cached_history_keeps_expected_replay_cleanup():
"""A durable read-only tool tail removed by stock replay cleanup stays removed."""
live = [
{"role": "user", "content": "check status", "_db_persisted": True},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call-1",
"type": "function",
"function": {"name": "web_search", "arguments": "{}"},
}
],
"_db_persisted": True,
},
{
"role": "tool",
"tool_call_id": "call-1",
"content": "[Command interrupted]",
"_db_persisted": True,
},
]
persisted, observed = _build_gateway_agent_history(live)
assert observed is None
assert len(persisted) < len(live)
assert persisted == [{"role": "user", "content": "check status"}]
assert _select_cached_agent_history(persisted, live) is persisted
def test_select_cached_history_preserves_live_when_a_real_row_is_unpersisted():
persisted = [
{"role": "user", "content": "hello"},
{"role": "assistant", "content": "hi"},
]
live = [
{"role": "user", "content": "hello", "_db_persisted": True},
{"role": "assistant", "content": "hi", "_db_persisted": True},
{"role": "user", "content": "not written"},
]
selected = _select_cached_agent_history(persisted, live)
assert selected is not persisted
assert selected == live
def test_select_cached_history_ignores_unmarked_ephemeral_scaffolding():
"""Internal retry nudges are deliberately unpersisted, not evidence of DB lag."""
persisted = [{"role": "user", "content": "ship the fix"}]
live = [
{"role": "user", "content": "ship the fix", "_db_persisted": True},
{
"role": "user",
"content": "Verify your work before stopping.",
"_pre_verify_synthetic": True,
},
]
assert _select_cached_agent_history(persisted, live) is persisted