Short-Term Memory: Managing the Conversation Buffer
How token budgets, protected messages, and head-tail eviction manage conversation context.
Short-term memory is the bounded message buffer sent on each model call. When it fills, deleting an arbitrary prefix can remove the system prompt, task definition, or unmatched tool messages. Eviction therefore needs explicit invariants, not a retry handler for context_length_exceeded.
The buffer contract
Short-term memory is the bounded message list that the harness sends to the model on each turn. It survives across turns in one session and disappears across sessions by default. A token, message, or application budget sets its size, so the harness must evict content before the next request exceeds that bound. Because this list is the prompt the model sees, eviction also determines which evidence remains available for reasoning.
The database may retain the complete conversation log. Short-term memory is the bounded working set that the harness reconstructs from that log for the next call. The log may grow; the working set must fit the prompt budget.
What belongs in the buffer
Every turn, the harness reconstructs a message array of the shape every modern Chat-Completions-style API accepts:
- System prompt(s). One or more, but always at index 0. Sets behavior, injects pinned semantic facts (from the semantic memory tier), declares tools, declares output schemas.
- Task anchor. The first user message of the session. The one that defines what the agent is trying to accomplish. Treated as protected in any non-trivial harness.
- Retrieved context. RAG hits, memory-store retrievals, tool definitions injected just-in-time. Lives near the system prompt or just before the latest user turn, depending on the JIT-vs-AOT context engineering policy.
- Conversation tail. The most recent N user/assistant turns and any tool-call/tool-result pairs they generated.
- The current user turn. Always at the end. Always protected.
Eviction operates on the middle: the conversation between the task anchor and the recent tail. Within that middle, the harness chooses what to drop. Below are the policies that actually ship.
Policy 1: Hard sliding window
Keep only the last K messages. Drop the oldest. Linear, simple, and the default in nearly every framework’s getting-started tutorial.
| |
When this is right. Single-purpose assistants with tight token budgets where the next turn is overwhelmingly the most important context. Customer-support bots that handle one issue per session. Single-prompt chat UIs.
When this is wrong. Any session where the original user message matters past turn K. Coding agents (the task description matters at turn 200). Multi-step research agents (the original question matters even when the current step is debugging the JSON parser). The fix is the next policy.
Policy 2: Head-tail (task-anchored sliding window)
Keep the system prompt, keep the first user message, keep the last K messages. Drop the middle.
| |
When this is right. Any agent whose system prompt is the single most important context (almost all of them) and whose task description is the second most important context (most of them). This is the policy LangGraph’s trim_messages with include_system=True and strategy="last" defaults to.
The subtle bug. Hard cuts at the boundary between “first message” and “last K messages” can break message-pair integrity; you can end up with a tool result for a tool call that’s no longer in the buffer. Every production buffer manager must respect the tool-call/tool-result pairing invariant: if you drop a tool call, drop its result; if you drop a result, drop its call. Otherwise the model sees orphan messages and behavior gets weird.
Policy 3: Token-budget eviction
Same head-tail shape, but measured in tokens instead of messages. Count tokens for each message; keep adding from the tail backwards until you hit your budget, then add the head.
| |
Why this beats message-count. Messages have wildly variable sizes. A tool result returning 50KB of JSON is one message but eats more tokens than 200 short chat turns. Message-count eviction silently lets one fat tool result push the system prompt out of token budget; token-count eviction sees the size and evicts the fat result. This is the policy every production-grade harness ends up at.
How to count. Use the official tokenizer where available. Anthropic exposes a free count_tokens endpoint that accepts the exact message payload and returns the exact token count Anthropic will charge for. OpenAI ships tiktoken. Approximating with len(text) // 4 is fine for budget estimates but unsafe for hard cutoffs; overshoot the limit by 100 tokens because your estimate was off and the call fails. The cost of one tokenizer round-trip is dwarfed by the cost of one failed request.
Policy 4: Salience-based eviction
Instead of evicting the middle uniformly, drop messages tagged as low-importance first. Importance can come from a write-time classifier (“this is a routine clarification”) or from a learned salience score (lifted from the episodic-memory literature; the Generative Agents importance score is the canonical formulation, and the episode segmentation and salience scoring article works the anchored 1-10 prompt and its failure modes in detail). The classifier returns a number per message; the eviction pass drops the lowest-scored messages first until budget fits.
When this pays off. Long-running agents (research, coding, multi-day workflows) where some intermediates are critical and others are scratch. Pays the cost of a per-message importance call to get back token budget at the eviction step.
When this hurts. Short sessions where the classifier cost exceeds the value. A salience pass that calls the model is 100ms+ per message; for a 5-turn customer-support session you’ve spent more than the budget you saved.
Policy 5: Summarization-and-replace (preemptive compaction)
When token usage crosses a watermark (say 60% of the model’s context window), pause, summarize the oldest M messages into a single “summary” message, and replace them. The buffer keeps the system prompt + summary + recent tail. Repeat on every watermark crossing.
| |
This is a summarization policy; the deep version belongs in the context-compression article in this Memory subtree. For short-term memory the relevant point: summarization is the more aggressive sibling of plain truncation. It preserves more semantic content per token but burns model calls to do so, and the summary itself is a lossy compression that you cannot undo. Run it when you have an actual reason to compress (long sessions, tight model budget); skip it when you don’t (short sessions where simple truncation works).
The frontier frameworks have all converged on this pattern as the production default. The OpenAI Agents SDK ships OpenAIResponsesCompactionSession as a decorator over any session backend, with a should_trigger_compaction hook that fires on a budget threshold. Anthropic exposes both server-side context editing with a trigger parameter (the model handles eviction transparently with the clear_tool_uses strategy) and an SDK-side compaction feature that summarizes the buffer when token usage hits the trigger. LangGraph composes the same idea with trim_messages plus a summarize_node you add to your graph. Different APIs, same algorithm.
Reserve headroom before eviction
The single most violated rule in real harnesses: never fill the context window to its stated limit, because the model has to output tokens too. A Claude Opus 4.7 1M-token window does not mean you can pack 1M input tokens; it means input + output must total ≤ 1M. If you pack 999K of input, the model has 1K to respond with, and any response longer than that gets truncated mid-token, often in the middle of a tool-call JSON, which breaks downstream parsing.
The defensible formula:
| |
For a 200K-token Claude Sonnet 4.6 window with max_tokens=4096, a sane setup is:
max_reply_tokens= 4096 (matchesmax_tokens)safety_headroom= 2048 (~1% buffer for estimation drift)tool_result_headroom= 8192 (if this turn might call tools)- →
input_budget= 200,000 - 4,096 - 2,048 - 8,192 = 185,664 tokens
When the buffer crosses input_budget, evict or compact. When it crosses the hard limit, the call fails. The watermark is the place to act; the limit is the place to panic.
Account for lost-in-the-middle behavior
A 2023 paper from Nelson Liu, Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni, and Percy Liang; Lost in the Middle: How Language Models Use Long Contexts; quantified what most engineers had a gut feel for: model accuracy on retrieval-from-context tasks follows a U-shaped curve over the position of the relevant content. Performance is highest when the relevant content is at the beginning (primacy bias) or end (recency bias) of the prompt, and degrades by 20–30%+ when the relevant content is in the middle. The follow-up work Found in the Middle attributes the curve to positional-encoding decay in RoPE-based architectures, which is most modern LLMs.
Two design implications for short-term memory.
Putting the recently evicted summary in the middle matches the model’s attention pattern. The head and tail receive more attention, so the compressed middle does not occupy a privileged slot.
Second, the more you stuff into the middle, the less reliably the model reads it. Some teams “compress” by leaving lots of low-importance middle material in and trusting the model to filter. The model does not reliably filter. If you wouldn’t bet the agent’s behavior on a particular middle message being read, evict it. The middle is not a free zone; it’s an attention-degraded zone.
Code: a token-budget head-tail buffer in Python
The smallest production-shaped buffer manager. Uses the Anthropic SDK for both the model call and (importantly) the official count_tokens endpoint so the budget math matches what the model actually charges. Install: pip install anthropic.
| |
the system prompt is the API’s system parameter, not message index 0; both Anthropic and OpenAI separate it out, so eviction logic never has to worry about accidentally dropping it. count_tokens is the authoritative source of truth; using a local estimator (len(text) / 4) is fine for development but a recipe for production context_length_exceeded errors. The task anchor is the first user message and is added last, not first; if it doesn’t fit, it falls back to a short summary line rather than evicting recent turns; “preserve the anchor” must not silently become “preserve nothing else.” Tool-use and tool-result pairs travel atomically; never break a pair, the model crashes hard on orphan tool messages. This is the same invariant Anthropic’s server-side clear_tool_uses strategy maintains.
The code is deliberately framework-light. A real LangGraph deployment would lean on trim_messages; an Agents SDK deployment would use OpenAIResponsesCompactionSession and let the runner handle the watermark. The principles are the same: a budget, a watermark, head-tail preservation, atomic pairs, authoritative token counting.
Further reading
- Lost in the Middle: How Language Models Use Long Contexts (Liu et al., 2023): the paper that quantified the U-shaped attention curve. Read §3 (multi-document QA results) and §4 (key-value retrieval results) for the numbers that should inform every “drop the middle” eviction decision. The follow-up Found in the Middle (2024) attributes the curve to RoPE positional encoding decay.
- Anthropic: Context editing. The official docs for server-side eviction, including the
clear_tool_uses_20250919strategy that handles tool-pair atomicity automatically and thetrigger/keep/clear_at_leastknobs. The cleanest mental model of how a server-side compaction actually works in production. - OpenAI Cookbook: Short-Term Memory Management with Sessions. A walkthrough of the Agents SDK’s session model, including
OpenAIResponsesCompactionSessionand theshould_trigger_compactionhook. The Cookbook is the most current entry point because it tracks SDK changes faster than the long-form docs. - LangGraph: Add memory. The LangGraph view of short-term memory: checkpointers as the persistence layer,
trim_messagesas the eviction policy, summarize-node as the compaction layer. The pattern-language is opinionated but the patterns are good.