microsoft / microsoft/agent-framework
Python: [Feature]: a budget-gated escalating compaction composition (summarize before you delete)
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
### Description
## Problem
The stock before-phase default (`ContextWindowCompactionStrategy`) escalates from tool-result collapse straight to **deleting the oldest groups** at 80% of budget. The middle options the library already ships — `SummarizationStrategy` (degrade content gracefully) and `SelectiveToolCallCompactionStrategy` (drop stale tool traffic first) — are not reachable from any composed default, so a long conversation loses history that a cheaper, less destructive stage could have preserved.
`TokenBudgetComposedStrategy` is close but not usable as the harness before-phase:
- its strict fallback loop can exclude **every** group, including system groups (`_compaction.py` ~1189-1209) — an empty projection the provider rejects (`contents are required` / `Messages are required`);
- stages are not individually budget-gated: there is no "stop escalating as soon as the target is met" contract, no per-stage keep-N configuration, and no observability of what each stage did.
## Requested
A composed strategy (or an extension of `TokenBudgetComposedStrategy`) with:
1. **Ordered stages with early-stop**: run stage N only if included tokens still exceed the target after stage N-1; re-annotate/re-tokenize between stages (inserted summaries carry `token_count=None` and silently under-count otherwise).
2. **Per-stage configuration**: keep-N for tool-result/selective stages, target count for summarization, and a separate (higher) threshold for the destructive truncation stage.
3. **A floor**: never produce a projection with zero non-system messages.
4. **Observability**: a log line (or callback) per stage that changed — counts, not content.
5. **Loop-aware after-phase cadence**: harness auto-loops re-invoke the agent per iteration, so `after_run` digestion fires per iteration — at small keep-N a model's fresh parallel tool results are digested to assistant text before its next roundtrip reads them (observed live: the model concluded its tools returned "no results" and looped re-searching). The composition should digest at user-turn boundaries or debounce while the loop will immediately continue.
6. **Bounded summarization input** — now split out as its own upstream bug report, filed as [microsoft/agent-framework#7214](https://github.com/microsoft/agent-framework/issues/7214): `SummarizationStrategy` ships the whole transcript unbounded and silently stops contributing past the provider cap; any composition should design the bound in from the start.
## Evidence it matters (live, harness + AG-UI deployment)
- Escalation preserved the task: with the composed ladder, an over-budget research conversation summarized down (S2: 20→5 messages, 106,949→11,191 est. tokens) and the model still recalled the opening request's verbatim marker afterwards. Stock behavior (delete-oldest) removed the opening request in the same scenario.
- Scale run (906 messages / est. 1.1M tokens, 1M-context model): the ladder kept every run alive (zero run errors) where a single-stage design had nothing between "collapse tool results" and "delete history". The same run — and a second incident on a 262k-cap model (2026-07-17) — surfaced the unbounded-summarizer-input defect, filed separately as [microsoft/agent-framework#7214](https://github.com/microsoft/agent-framework/issues/7214).
## Workaround (application-side, today)
`ats.compaction.EscalatingCompactionStrategy`: composes the three stock strategies plus a first-user-preserving truncation (separate FR, filed as [microsoft/agent-framework#7216](https://github.com/microsoft/agent-framework/issues/7216)), gated on `input_budget = window − output`, early-stopping at 50%, truncating only above 80%, with INFO/DEBUG logging and a chunking + memoizing client in front of the summarizer (bounded map-reduce summarize requests; the before phase otherwise re-summarizes identical content every roundtrip under per-service-call persistence). All of it is composition glue that would be deleted if upstream shipped what's requested above.
### Code Sample
```markdown
```
### Language/SDK
Both
Contributor guide
Assessment
This issue has not been assessed yet.