mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
The proactive handoff gate ran at the start of every tool-loop round. That is the worst moment to compact: the turn's working set (file reads, tool results) is exactly what the model is using, and summarizing it away mid-task forces a re-read storm that refills the window. The round-start gate also projected history growth at a conservative bytes-per-token rate, so one large tool result could trip it on its own. Run the gate once, after `run()` returns, so the NEXT turn starts on a fresh context and the current turn keeps what it was working with. - handoff.rs: `maybe_handoff(&mut attempts)` → `end_of_turn_handoff()`. Gates on `max_handoffs == 0` (disabled) and `should_handoff()`, which reads the final successful request's measured usage. `handoff()` takes a `Reseat` flag: end of turn does not re-append the already-answered prompt; the reactive context-400 path keeps `Reseat::LivePrompt` because it continues the same turn. - agent.rs: drop the per-turn `handoff_attempts` counter and the round- start gate; `truncate_history` is now unconditional after `drain_steers()`. Mid-turn overflow is handled only by the reactive context-400 ladder. - lib.rs: call `end_of_turn_handoff()` after `ctx.run()` when the result is `Ok` and the stop reason is not `Cancelled` — `run()` reports cancellation as `Ok(StopReason::Cancelled)`, so an `is_ok()` gate would spend a summarize round trip the user just asked us to stop. - config.rs / README.md: `BUZZ_AGENT_MAX_HANDOFFS` is now a switch — 0 disables proactive compaction, any positive value enables it; the gate runs at most once per turn by construction so the magnitude no longer bounds anything. Reactive recovery bypasses it as before. - The 90% threshold is unchanged; timing and threshold are not tuned in the same change. Tests (tests/regressions.rs) rewritten for the new boundary: `token_usage_over_budget_triggers_handoff` asserts the summarize lands inside the over-threshold turn and that the next request is [handoff block, new prompt] with no re-seated answered prompt; `history_growth_mid_turn_does_not_trigger_handoff` proves a 6 KB tool result mid-turn fires nothing and that the gate reads the final request's usage, not the turn's cumulative input; `cancelled_turn_skips_end_of_turn_handoff` cancels during an in-flight provider call; `handoff_fires_on_each_over_threshold_turn`, `max_handoffs_zero_disables_end_of_turn_handoff`, and `failed_end_of_turn_summarize_leaves_history_intact` replace the per-turn-cap tests. Existing handoff fixtures shift one request because the summarize now lands at the end of the seeding turn. Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>