511633be90
_maybe_inject_run_budget_wrapup() appends its wrap-up notice to the newest role:"tool" message in place, with no _DB_PERSISTED_MARKER check. Its sibling, _maybe_inject_iteration_budget_warning(), got exactly this guard added in the same recent saga (turn_iteration_prep.py), with the comment "an older turn may already be cached." The reachability is structural, not an edge case: _maybe_inject_run_budget_wrapup is only ever called from prepare_iteration(), at the START of the next iteration -- strictly after tool_executor.py's _flush_session_db_after_tool_progress has already flushed and marked the previous iteration's tool row persisted. So every successful injection was mutating an already-persisted row: the wire request for that turn carried the notice, but the durable transcript never did, diverging replay from the live bytes and invalidating the provider's prompt-cache prefix from that row onward. Fix: - Add the same _DB_PERSISTED_MARKER guard to _maybe_inject_run_budget_wrapup, scoped to the specific tool row the reversed scan lands on (not just messages[-1], since this function -- unlike its sibling -- scans backward for the newest tool row rather than only checking the tail). - Wire _maybe_inject_run_budget_wrapup into _flush_session_db_after_tool_progress (pre-flush), mirroring exactly how _maybe_inject_iteration_budget_warning is wired in both places. Without this, the guard alone would make the notice stop firing in the common case, since prepare_iteration's call site almost always hits an already-persisted row -- the pre-flush call site is what actually lets it land in durable bytes. Verified empirically: read the real call graph (tool_executor.py's three _flush_session_db_after_tool_progress call sites cover every tool-completion path) to confirm the guard's premise, then added an end-to-end test using a real AIAgent + SessionDB that flushes and checks the persisted row for the notice text. Mutation-verified: reverting the two production files drops exactly the 2 new/updated assertions (28 pass, 2 fail); reapplying restores green (30 passed). Also ran the sibling iteration-budget-warning and /steer suites (71 passed) to check for interaction regressions -- none.