Mid-turn compaction no longer drops this turn's tool results from the next request (salvage #91234) - #120175
Merged
Conversation
…r mid-turn compaction Mid-turn compaction rebuilds `messages` without always handing back a new current_turn_user_idx: the pre-API pressure gate (run_preflight_compression) returns "continue" with the pre-compaction index, and any future compaction site would do the same. The request builder splits the replay prefix at that index and canonicalizes messages[:idx]. With a stale index the split lands inside the current turn's tool rows: canonicalization drops the assistant tool_call whose result fell past the split, the sanitizer then drops the orphaned result, and the model silently loses the turn's earlier tool output even though state.db (and the in-memory history) still hold it. persisted != sent, and a resumed session sends a different history than the live one. prepare_iteration now validates the index before every request (it must land on this turn's user row, verbatim or via its user-originated view) and re-anchors otherwise, so every mid-turn compaction path is covered in one place. The post-tool gate's own re-anchor (previous commit) keeps _persist_user_message_idx aligned immediately; this check is a no-op there. Found by the compaction E2E suite (tests/e2e/core/compaction, persisted-prefix invariant): a seeded tool-heavy session that crosses the trigger after a tool round.
… on the wire Drives the real AIAgent loop: a tool round, then the pre-API gate compacts two historical rows away before the next request. That request must still end with this turn's user row, the assistant tool_call and its result. Red on origin/main and with only the post-tool re-anchor; green with the prepare_iteration check. Folds the post-tool re-anchor invariant into the same file (renamed to cover both gates). Passes the now-required current_turn_user_idx to the existing post-tool prune-wiring test call (signature change from the salvaged commit; no assertion changed).
This was referenced Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mid-turn compaction no longer drops the current turn's earlier tool calls and results from the next request, so what the model receives matches state.db again.
compress_after_tool_resultsre-anchorscurrent_turn_user_idxwith_reanchorright after a real compaction and hands it back through the post-tool and tool-round verdicts._run_phasecopies it into the loop state, and_persist_user_message_idxupdates at the same moment.prepare_iterationchecks the index before every request. It must land on this turn's user row, either verbatim or through its user-originated view. If it doesn't, it re-anchors. This covers the pre-API pressure gate (run_preflight_compressionreturnscontinuewith the pre-compaction index) and any compaction site added later. Where fix(agent): re-anchor after post-tool compression #91234 already re-anchored, the check does nothing.tests/agent/test_mid_turn_compaction_turn_boundary.pyholds 2 invariants: fix(agent): re-anchor after post-tool compression #91234's post-tool test (moved into this file) and a realAIAgentloop test where the pre-API gate compacts after a tool round.Root cause:
build_api_messagessplits the replay prefix atcurrent_turn_user_idx(canonicalize_replay_history(messages[:idx])). Mid-turn compaction shrinksmessagesfrom the front, so a stale index lands inside this turn's tool rows. Canonicalization drops the assistanttool_callwhose result fell past the split, and the sanitizer then drops the orphaned result.Live repro (real
AIAgent+ on-diskSessionDB+ realread_filetools against the loopback fake provider;tests/e2e/core/compaction/):07646a7f727):test_compaction_auto.py::test_good_summarizer...[11]fails withturn 1 request 2: request history diverges from state.db.sent=[… U('U11x1…'), A[call_fake_5], T:call_fake_5]vsdb=[… U('U11x1…'), A[call_fake_4], T:call_fake_4, A[call_fake_5], T:call_fake_5]: the call_fake_4 pair is missing from the wire.tests/e2e/core/compaction/pass (4 files, including the kill -9 and summary-fault matrices).call_1pair dropped)That middle column is why the
prepare_iterationcheck is layered on top of the salvage.Validation
scripts/run_tests.sh tests/agent/: 9219 passed, 1 failed, then fixed. The failure wastest_proactive_prune_loop_wiring.pycallingcompress_after_tool_resultswithout the new requiredcurrent_turn_user_idxkwarg; the call now passes it and no assertion changed.test_run_agent.pyhit the 300 s per-file cap under host load and passed on rerun (267 tests), along withtest_moa_loop_mode.py, which failed once and passed on retry and is unrelated to this change. After rebasing onto33a30fdd815: the touched test files are green.ruff checkon the touched files: clean.scripts/check_no_tmp_literals.py agent tests/agent: clean.git diff --check: clean.scripts/audit_pr_attribution.py --fix: all mapped.Credit / Supersedes
prepare_iterationcheck covers the per-iteration pre-API gate its thread asked about.Infographic