Skip to content

Mid-turn compaction no longer drops this turn's tool results from the next request (salvage #91234) - #120175

Merged
teknium1 merged 3 commits into
mainfrom
fix/turn-idx-reanchor-mid-turn-compaction
Sep 23, 2026
Merged

teknium1 merged 3 commits into
mainfrom
fix/turn-idx-reanchor-mid-turn-compaction

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

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.

  • Post-tool compaction (salvaged from fix(agent): re-anchor after post-tool compression #91234 by @Diaspar4u, authorship kept): compress_after_tool_results re-anchors current_turn_user_idx with _reanchor right after a real compaction and hands it back through the post-tool and tool-round verdicts. _run_phase copies it into the loop state, and _persist_user_message_idx updates at the same moment.
  • Every other mid-turn site (ours): prepare_iteration checks 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_compression returns continue with 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: tests/agent/test_mid_turn_compaction_turn_boundary.py holds 2 invariants: fix(agent): re-anchor after post-tool compression #91234's post-tool test (moved into this file) and a real AIAgent loop test where the pre-API gate compacts after a tool round.

Root cause: build_api_messages splits the replay prefix at current_turn_user_idx (canonicalize_replay_history(messages[:idx])). Mid-turn compaction shrinks messages from the front, so a stale index lands inside this turn's tool rows. Canonicalization drops the assistant tool_call whose result fell past the split, and the sanitizer then drops the orphaned result.

Live repro (real AIAgent + on-disk SessionDB + real read_file tools against the loopback fake provider; tests/e2e/core/compaction/):

  • Before (origin/main 07646a7f727): test_compaction_auto.py::test_good_summarizer...[11] fails with turn 1 request 2: request history diverges from state.db. sent=[… U('U11x1…'), A[call_fake_5], T:call_fake_5] vs db=[… 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.
  • After: all 34 tests in tests/e2e/core/compaction/ pass (4 files, including the kill -9 and summary-fault matrices).
invariant test origin/main #91234 only this PR
post-tool compaction re-anchors the boundary red (TypeError, no kwarg) green green
pre-API compaction mid-turn keeps this turn's tool pair on the wire red (request ends at the user row, call_1 pair dropped) red green

That middle column is why the prepare_iteration check is layered on top of the salvage.

Validation

  • scripts/run_tests.sh tests/agent/: 9219 passed, 1 failed, then fixed. The failure was test_proactive_prune_loop_wiring.py calling compress_after_tool_results without the new required current_turn_user_idx kwarg; the call now passes it and no assertion changed. test_run_agent.py hit the 300 s per-file cap under host load and passed on rerun (267 tests), along with test_moa_loop_mode.py, which failed once and passed on retry and is unrelated to this change. After rebasing onto 33a30fdd815: the touched test files are green.
  • ruff check on 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.
  • The E2E suite that found this lands separately in the core E2E-suite PR and is not part of this diff.

Credit / Supersedes

Infographic

Tool results survive compaction

Diaspar4u and others added 3 commits September 23, 2026 05:14
…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).
@teknium1 teknium1 added the ci-reviewed applied to manually approve dangerous changes label Sep 23, 2026
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 23, 2026
@teknium1
teknium1 merged commit fd94981 into main Sep 23, 2026
37 checks passed
@teknium1
teknium1 deleted the fix/turn-idx-reanchor-mid-turn-compaction branch September 23, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions ci-reviewed applied to manually approve dangerous changes comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants