An agent that works for a long time on one task never exceeds the model’s context window. Before every request the history is measured against the window of the model actually called, and compacted in tiers, cheapest first, when it nears the limit. Without any configuration, Agent(llm=llm) already does this.

How the history is measured

  • The window comes from the model: an explicit context_window, the server’s listing, the built-in table, or a fallback. See Models.
  • The count is anchored on the provider’s own report: after each reply, the agent records the input and output tokens the provider billed, which cover the system prompt, the tool schemas and any files. Only the messages added since are estimated locally (tiktoken by default), so a tokenizer mismatch cannot drift.
ContextUsage also tells the limit (limit_tokens), whether the window is known (resolved) and whether the count is anchored on the provider (anchored). on_context_usage is called with it before every request:

When compaction happens

Compaction triggers when the history reaches max_fraction of the window (0.85 for the agent’s default buffer), and stops once it is back under a target, 70 % of that limit by default (about 60 % of the window). That gap means compaction runs in batches, and between two batches the prefix sent to the model stays identical, so the provider’s prompt cache stays valid.

The tiers

The tiers are applied in this order, re-measuring after each, until the history fits. Whatever happens:
  • a tool call and its result always move together;
  • the first user message, the task of the current run, and any message marked with pin() (gwenflow.memory.compaction) survive every tier;
  • a summarizer that fails never fails the run: the sliding window takes over for that batch.
The originals of what a compaction changed are kept in agent.history.compactions[-1].removed, and each compaction emits a compact_messages span with the tiers applied and the counts before and after (never the content).

The summarizer

The summary tier calls a model. By default it is a copy of the agent’s llm; give a cheaper one with summarizer:
The summary’s tokens are counted in the run that triggered it. The summarizer reads a transcript where every tool result is capped, and updates the previous summary rather than starting over.

Compacting by hand

Large tool outputs

A single grep or file read can fill a small window. max_tool_output_tokens caps one tool result: a longer one is set aside, the model gets its beginning and a reference, and a read_tool_output(ref, offset, length) tool to page through the rest.

Warning the model

With warn_near_limits=True, the system prompt of a request carries a notice when the run nears a limit, so the model concludes instead of seeing its history rewritten or being cut off:
  • URGENT when the history reaches warning_threshold (0.7) of its budget, or the run warning_threshold of max_turns;
  • CRITICAL when a compaction is imminent, or critical_remaining_turns (3) turns are left.
The notice is request-scoped: nothing is written in the history.

A custom buffer

Pass your own buffer as history to tune everything: default_strategies(summarizer=..., deduplicate={...}, clamp={...}, clear={...}, summarize={...}, sliding={...}) builds the default tiers with per-tier options; without a summarizer it skips the summary tier.

A coding agent on a 32K window

Recommended configuration for a coding agent on a model served by vLLM with a 32K window:
hf_tokenizer(model, **kwargs) counts with the Hugging Face tokenizer of the served model (kwargs go to AutoTokenizer.from_pretrained: trust_remote_code, token…); tiktoken under-counts for Mistral, Qwen or Gemma models.