fix(gateway): distinguish durable cached transcript rows
This commit is contained in:
parent
59f1aa8bf6
commit
6e8dcb8f47
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue