Skip to content
Changelog

Keep long agent sessions inside the context window with compaction

October 8, 20263 min read

With Agent(compaction=True), Agno waits for the provider to reject a request as too long, summarizes the older turns with the agent's own model, and retries with that summary plus up to the five most recent runs.

Picture a support agent that stays in one session all week. Every run replays the history, and one day the request no longer fits the model. Pass a Compaction to fold at a size you pick, before the provider ever says no:

from agno.agent import Agent
from agno.compaction import Compaction
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
 
agent = Agent(
    model=OpenAIResponses(id="gpt-5.6-luna"),
    db=SqliteDb(db_file="tmp/agent.db"),
    session_id="trip",
    add_history_to_context=True,
    compaction=Compaction(compact_at_tokens=100_000, uncompacted_runs=2),
)
 
agent.print_response("I am planning a trip to Japan in April. My budget is 4000 dollars.")
 
# Fold now, at a point you choose, without waiting for the threshold
result = agent.compact(session_id="trip")
print(result.status.value, result.message)

At the start of each run Agno counts the request, with a local tiktoken estimate by default. Once it reaches compact_at_tokens, everything older than the kept runs becomes one summary. agent.compact() and agent.acompact() fold on demand and return a CompactionResult, whose status is compacted or names why the fold declined, such as nothing_to_fold or already_compacted.

SettingDefaultWhat it does
compact_at_tokens150_000Fold when the request reaches this size; None folds only on demand
uncompacted_runs5Recent runs sent word for word
uncompacted_tokensNoneSize the kept tail in tokens; can't be set with uncompacted_runs
modelThe agent's modelWrites the summary, so a cheaper model can do it
instructionsNoneExtra guidance for the summarizer, like "Keep every ticket id"
compacted_token_budgetNoneLength target for the summary, cut at a line break when it runs over
store_compacted_messagesTrueKeep the folded messages in the database
search_compacted_messagesFollows store_compacted_messagesGive the agent a tool to search those messages
on_context_overflowFalse, True with compaction=TrueFold and retry when the provider rejects a request as too long

What gets stored where

Compaction never edits the session. Each fold writes one row to a new agno_compactions table with the summary, the folded messages rendered as markdown in archived_messages, the id of the first message it kept, and the token counts on both sides. Agno rebuilds every request from the stored runs plus the newest row, so the summary never lands in the session as a message. On the next fold the summarizer reads the previous summary together with the newer turns and writes a single updated one.

Diagram of one compaction fold for a six-run session with uncompacted_runs set to 2. On the left, the session still stores all six runs, two messages each. In the middle, one agno_compactions row holds the summary of runs 1 to 4, those runs verbatim in archived_messages, and a first_kept_message_id pointing at run 5. On the right, the model payload is the summary, runs 5 and 6, and the new question. A note says deleting the row makes the next request use the stored runs again.

We ran a six-run session on SQLite with an offline stand-in model and a summarizer that returns fixed text, since no API key was available. After agent.compact() the next question went out like this (real output, trimmed):

agent.compact(): compacted | Compacted 8 messages (85 -> 44 tokens).
agno_compactions columns: compaction_id, session_id, run_id, user_id,
  first_kept_message_id, summary, archived_messages, messages_compacted,
  tokens_before, tokens_after, created_at
request after the fold: 6 messages
  user       Summary of earlier conversation (compacted): ...
  user       Do I need a rail pass?
  assistant  Noted.
  user       Which airport should I fly into?
  assistant  Noted.
  user       Remind me what my budget was.
tools sent: search_compacted_history
messages stored in the session: 14

A run that folds on its own carries the record on run.compaction and streams CompactionStarted and CompactionCompleted events. AgentOS exposes the manual fold as POST /agents/{agent_id}/sessions/{session_id}/compact.

Undo a fold

The row is the only thing that changes what the model sees, so deleting it undoes the fold. db.delete_compactions_for_session("trip") returned 1 in the run above, and the following request carried 15 messages, every stored run plus the new question.

Search the folded turns

search_compacted_messages follows store_compacted_messages, so it is on by default. After the first fold archives something, the agent gets search_compacted_history(pattern, context_lines=2), scoped to its own session. The summary message tells the model to search before it answers any question that needs an exact value from the folded turns, like an identifier, a figure or an error message. The pattern is literal, case-insensitive text with | between alternatives, and one search returns at most 8,000 characters. A search for the booking reference from the first turn found it in the archive (real output):

1: ## user
2:
3: I am planning a trip to Japan in April. My budget is 4000 dollars. Booking ref JP-7731.
4:
5: ## assistant

Set search_compacted_messages=False to keep the archive for your own debugging without the tool, or store_compacted_messages=False to keep only the summary. Compaction runs on SqliteDb and PostgresDb; on any other database Agno logs a warning and turns it off.

See the cookbook, and learn more about chat history in the documentation.

Frequently asked questions

Set compaction=True on the Agent to fold older turns when the provider says a request is too long, or pass Compaction(compact_at_tokens=...) to fold at a size you choose. The agent needs a SqliteDb or PostgresDb to store the folds.

No. The session keeps every message exactly as it was saved. A fold adds a row to the agno_compactions table, and Agno derives each model request from the stored runs plus that row, so calling delete_compactions_for_session(session_id) on the database brings the full history back.

Yes. By default the folded messages are archived and the agent gets a search_compacted_history tool that finds literal text in them, such as an order id or an error message. Set search_compacted_messages=False to remove the tool and keep the archive for yourself.

SqliteDb and PostgresDb. Any other database cannot store compaction records, so Agno logs a warning and turns compaction off for that agent.

Shipped around the same time