Files
buzz/crates
Meli 25d5a0ca66 buzz-agent: move proactive context handoff to end of turn
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>
2026-08-22 18:10:25 -04:00
..