Keep state.db tool-call rows before final answers - #4893
Conversation
73b7f1a to
d5edece
Compare
d5edece to
4bb936d
Compare
|
| Filename | Overview |
|---|---|
| api/models.py | Adds _tool_call_assistant_should_precede_content_assistant helper and plumbs it into _insert_state_message_chronologically for tie-breaking at equal timestamps; replaces the pass/tail-append path with a chronological insert, intentionally dropping pre-window resurrection candidates when _insert_state_message_chronologically returns False. The guard works for content-only final answers but leaves a narrow gap when a final answer carries both content and tool_calls at the same timestamp as the state.db row. |
| tests/test_merge_key_tool_calls.py | Adds three regression tests covering older-timestamp, equal-timestamp, and pre-window cases; all accurately target the ordering invariant that the final answer must remain the transcript tail. Test helper and assertions are well-structured and clearly document the intentional drop behavior for pre-window rows. |
Reviews (2): Last reviewed commit: "Handle equal-timestamp tool-call tail or..." | Re-trigger Greptile
|
Read the changed branch in The bug this fixes is realOn master, a state.db assistant row with distinct if _ck in seen_content_keys and dedup_key not in seen_dedup_keys:
pass # different tool_calls from sidecar — preserveThat On greptile's "silently dropped when
|
|
Nice fix — the core is correct and a strict improvement over master: routing the distinct-tool-call row through One CORE edge to finish before merge — the equal-timestamp tie (both gates reproduced it independently): When the state.db tool-call row's timestamp exactly ties the final answer's (realistic under second-granularity stamps — a fast tool call + the final answer in the same integer second; note your test helpers default to integer Root cause is two-part, and the second part is the trap:
So the complete fix needs BOTH: (a) the equal-timestamp I held off applying this myself because guard (b) is shared with the user-role insert path on the crown-jewel append-only merge, and getting the scoping wrong risks reordering user turns — your call on the cleanest shape is better here. Please also add two tests to |
|
Addressed the equal-timestamp review blocker on current What changed:
Verification:
Ready for re-review. |
…wer incl equal-ts (#4893)
|
Shipped in v0.51.670 (Release XZ, just deployed) — thanks @franksong2702! The equal-timestamp tie edge is resolved: settled tool-call rows now stay before the final answer even when a fast tool call and the final answer land in the same second. Gate: Codex SAFE + Opus SHIP (user-path provably byte-identical — the guard-b exception is scoped to the tool-call-before-content-assistant case and can't fire for user rows; tool-result block integrity + watermark reconciliation unchanged); I added the two coverage tests Opus flagged (multi-tool tie + block-no-split). Suite 10643. Verified on prod. |
…ie + tool-result block no-split
Thinking Path
/api/sessionmerges WebUI JSON sidecar messages with Hermes Agentstate.dbmessages before returning a paginated transcript window.state.dbstill contributed older empty-content assistant rows carrying distincttool_calls.What Changed
merge_session_messages_append_only()so state.db assistant rows with distincttool_callsinside the sidecar timestamp range are inserted by timestamp.CHANGELOG.mdentry from this PR to avoid release-note conflicts.Why It Matters
Paginated session loads should not make a complete final answer look incomplete just because an older process/tool-call row was reconciled from
state.db. This keeps WebUI/state.db reconciliation from breaking the final visible reply boundary while still preserving distinct tool activity rows.This is related to the WebUI sidecar / Hermes
state.dbreconciliation work in #4834, but it is a separate ordering bug in the shared append-only merge path. It also follows the tool-call preservation behavior introduced around #3665 and the later edit/retry/undo reconciliation work for #4767/#4772: those rows should be preserved, but not moved after settled final answers.Verification
python3 -m py_compile api/models.pygit diff --check origin/master...HEAD./scripts/test.sh tests/test_merge_key_tool_calls.py tests/test_webui_state_db_reconciliation.py tests/test_core_data_loss_cases.py tests/test_session_message_window_renderable_tail.py -q— 62 passed.Risks / Follow-ups
state.dbtranscript merge used by/api/sessionpaginated loads.Model Used
OpenAI Codex GPT-5 with local repository inspection, regression tests, and GitHub CLI publishing.