Describe the bug
Auto-compaction is only evaluated at a user-turn boundary, so tool output accumulated inside a turn is never re-examined. A turn that begins under the threshold can run all the way to the provider's context limit without a single check.
| path |
site |
gate |
| legacy |
crates/goose/src/agents/agent.rs:2049 |
check_if_compaction_needed runs once, before the reply stream |
| state machine |
crates/goose/src/agents/state_machine/ops_compaction.rs:235 |
not_applicable() unless last_effective_role == EffectiveRole::User |
This was noted as a secondary observation in #11049 and deferred. It is not theoretical — it is reproducible in a live session, and it survives a correct context limit.
Observed
A session with a correctly resolved context_limit of 49,152 and GOOSE_AUTO_COMPACT_THRESHOLD=0.35 (trigger ≈ 17,203). Auto-compaction fired six times at turn boundaries and worked. Then one tool-heavy turn, from usage_ledger:
16:09:29 17,513 in is_compaction=1 -> compacted to 6,912
16:29:29 17,189 in past the 17,203 trigger, nothing fires
16:32:50 24,560 in
16:42:15 40,783 in
16:45:14 49,120 in + 32 out = 49,152 at the server's hard ceiling
Thirty-plus consecutive provider calls in a single turn, no compaction check in any of them. Note the last row's 32 output tokens: at that point the model has almost no room left to answer, and the next request is rejected.
To Reproduce
- Configure any provider whose context limit is small enough to reach in one turn.
- Set
GOOSE_AUTO_COMPACT_THRESHOLD well below 1.0.
- Ask for work that drives a long sequence of tool calls without returning to the user.
- Watch
usage_ledger: tokens climb past threshold * context_limit and keep going; is_compaction stays 0 until the turn ends or the provider rejects the request.
Expected behavior
The threshold is re-evaluated during a turn, once tool responses have landed, so a long tool loop compacts instead of walking into the context limit.
The safe point matters: compacting between a tool request and its response would hide the request from the model and orphan it. EffectiveRole::Tool — a user-role message carrying tool responses — is the natural boundary, and Assistant must stay excluded.
Please provide the following information
- OS & Arch: macOS 15 (Darwin 25.5.0) arm64
- Interface: Desktop (ACP) and CLI
- Version: 1.45.0
- Provider & Model: custom OpenAI-compatible (llama.cpp-derived,
--ctx 49152)
Additional context
Related but distinct:
AGENTS.md requires both agent paths to change together, which is achievable in one edit per path. I have a working branch and will attach it as an RFC PR; it needs a test that exercises a mid-turn compaction end to end before it should be considered mergeable.
Describe the bug
Auto-compaction is only evaluated at a user-turn boundary, so tool output accumulated inside a turn is never re-examined. A turn that begins under the threshold can run all the way to the provider's context limit without a single check.
crates/goose/src/agents/agent.rs:2049check_if_compaction_neededruns once, before the reply streamcrates/goose/src/agents/state_machine/ops_compaction.rs:235not_applicable()unlesslast_effective_role == EffectiveRole::UserThis was noted as a secondary observation in #11049 and deferred. It is not theoretical — it is reproducible in a live session, and it survives a correct context limit.
Observed
A session with a correctly resolved
context_limitof 49,152 andGOOSE_AUTO_COMPACT_THRESHOLD=0.35(trigger ≈ 17,203). Auto-compaction fired six times at turn boundaries and worked. Then one tool-heavy turn, fromusage_ledger:Thirty-plus consecutive provider calls in a single turn, no compaction check in any of them. Note the last row's 32 output tokens: at that point the model has almost no room left to answer, and the next request is rejected.
To Reproduce
GOOSE_AUTO_COMPACT_THRESHOLDwell below 1.0.usage_ledger: tokens climb pastthreshold * context_limitand keep going;is_compactionstays 0 until the turn ends or the provider rejects the request.Expected behavior
The threshold is re-evaluated during a turn, once tool responses have landed, so a long tool loop compacts instead of walking into the context limit.
The safe point matters: compacting between a tool request and its response would hide the request from the model and orphan it.
EffectiveRole::Tool— a user-role message carrying tool responses — is the natural boundary, andAssistantmust stay excluded.Please provide the following information
--ctx 49152)Additional context
Related but distinct:
AGENTS.mdrequires both agent paths to change together, which is achievable in one edit per path. I have a working branch and will attach it as an RFC PR; it needs a test that exercises a mid-turn compaction end to end before it should be considered mergeable.