Skip to content

feat(agent): compact on a token ceiling and rewrite the handoff prompt - #3147

Open
atishpatel wants to merge 1 commit into
mainfrom
feat/agent-handoff-thresholds-and-prompt
Open

feat(agent): compact on a token ceiling and rewrite the handoff prompt#3147
atishpatel wants to merge 1 commit into
mainfrom
feat/agent-handoff-thresholds-and-prompt

Conversation

@atishpatel

Copy link
Copy Markdown
Contributor

Summary

Tweaks buzz-agent's context handoff (self-compaction): when it fires, and what the summary it produces is asked to contain.

When it fires. The gate was a hard-coded 90% of the context window. That's a fine default at 200K and a bad one at 1M — a single compaction near the top of a large window has to summarize ~900K tokens of history in one call, which costs more than most whole sessions and throws away detail the agent was still using. Two changes:

  • BUZZ_AGENT_HANDOFF_PERCENT (default 90) makes the fraction configurable instead of hard-coded.
  • BUZZ_AGENT_HANDOFF_AT_TOKENS (default 272000) adds an absolute ceiling, and handoff fires at whichever binds first. 272K is OpenAI's input boundary above which the higher long-context rates apply, so compacting just under it also keeps every request in the cheaper pricing tier. 0 disables the ceiling.

A ceiling rather than an override because the two knobs protect different things: the percentage protects the window, the ceiling protects the summarization call, whose cost scales with how much history it has to read. One pair of defaults covers both 200K and 1M without per-model config. Staying well inside the window is also just better for the agent: cheaper per request, and models degrade at recall and instruction-following as the window fills, so a compacted 200K of relevant context tends to beat 900K of raw history.

What the summary contains. The old prompt asked for "concise but thorough" free prose over five topics. The next turn's failure mode isn't a missing overview, it's a specific missing fact — a path, a command that worked, a dead end already ruled out — and "concise" quietly permits dropping exactly those. The prompt now specifies a fixed set of sections (TASK, CONSTRAINTS, DONE, DECISIONS, FACTS, REMAINING, NEXT STEP) that must all be present, tells the summarizer its output is the only thing the agent will still know, and asks for specifics over description. It also treats the session history as data rather than instructions, so text in the transcript can't redirect the summarizer.

Supporting changes:

  • Summary output budget 819232000 tokens, since the section contract asks it to carry more. Clamped to half the context window so a small-window deployment can't reserve more output than the window holds.
  • BUZZ_AGENT_MAX_HANDOFFS 1080. This is a backstop against a pathological compaction loop, not a session length limit — reaching it doesn't stop the agent, it silently degrades it to dropping its oldest turns, which loses strictly more than compacting again would. A long autonomous run can legitimately compact dozens of times, and a cap low enough to hit in normal operation makes the degraded path the common one.
  • Threshold math switched to multiply-then-divide, so a percentage that isn't a multiple of 10 doesn't lose precision.

Related issue

None found — no existing issue or PR covers the handoff threshold or summary prompt.

Testing

No UI surface; this is agent-internal.

cargo test -p buzz-agent (296 unit tests), cargo clippy -p buzz-agent --all-targets -- -D warnings, and cargo fmt --check all pass.

New unit tests cover the threshold policy and the prompt contract:

  • The default ceiling is inert at a 200K window (still 180K) — existing deployments are unchanged by shipping a 272K default.
  • The ceiling binds at a 1M window (272K, not 900K).
  • BUZZ_AGENT_HANDOFF_AT_TOKENS=0 hands control back to the percentage.
  • The output reservation still wins over both knobs, so no configuration can produce a request that cannot fit its own response.
  • Percentages that aren't multiples of 10 are exact.
  • The output-token clamp binds on small windows and is positive on degenerate ones.
  • Every section heading starts its own line — the instruction block is one wrapped string literal, and reflowing it would silently merge a heading into the previous sentence, at which point the model stops treating it as a section to fill.
  • The system prompt states the same token limit that is actually requested.

Manual verification of the new defaults against a live provider is the main thing not covered here — the tests pin the policy math and the prompt's shape, not summary quality.

Follow-ups deferred

  • Nothing per-provider yet: BUZZ_AGENT_HANDOFF_AT_TOKENS is one global value, so a deployment mixing providers with different long-context boundaries has to pick one or disable the ceiling.
  • No metric on summary quality or on how often the max_handoffs fallback is reached; if the degraded path is being hit in practice we'd currently only see it in logs.

The handoff gate fired at a hard-coded 90% of the context window, which is
a reasonable default at 200k and a bad one at 1M: a single compaction near
the top of a large window has to summarize ~900k tokens of history in one
call. Make the fraction configurable and add an absolute ceiling
(default 272k, OpenAI's long-context pricing boundary) so handoff fires at
whichever binds first. The ceiling is inert at a 200k window, so existing
deployments are unchanged.

Rewrite the summary prompt around a fixed section contract so the next
turn inherits the specifics it would otherwise have to rediscover, and
raise the summary output budget from 8k to 32k tokens now that it has more
to carry. Raise the handoff cap to 80, since it is a backstop against a
compaction loop rather than a session length limit, and hitting it
degrades the agent to dropping its oldest turns.

Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@atishpatel
atishpatel requested a review from a team as a code owner July 27, 2026 16:46

@tlongwell-block tlongwell-block left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just lowering the max context here probably works in most cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants