Short-Term Memory with Message Buffers

Duration: 15 min

Short-Term Memory with Message Buffers

Duration: 15 min

Overview

Short-term memory bridges the gap between the immediate context window and persistent long-term storage. It holds recent conversation history, intermediate reasoning steps, and temporary state that the agent may need within a session. This module covers buffer strategies - sliding windows, token-limited buffers, and summary buffers - and shows how to implement each with practical Python code.

Choosing the right buffer strategy determines whether your agent remembers what happened 5 messages ago or loses track of multi-step tasks.

Key Concepts & Foundations

  • What: Short-term memory stores recent interactions and intermediate state, typically lasting one session
  • Why: Agents need to remember what was said earlier in a conversation without loading everything into the context window
  • How: Buffer implementations control what stays, what goes, and how evicted content is preserved

Buffer Strategies

| Strategy | How It Works | Best For | |----------|-------------|----------| | Sliding Window | Keep last N messages | Simple chatbots | | Token Buffer | Keep messages until token limit hit | API cost control | | Summary Buffer | Summarize old messages, keep recent | Long conversations | | Entity Buffer | Track entities mentioned, discard details | Customer service |

When Short-Term Memory Fails

  • Lost instructions - User gave a preference 20 messages ago, buffer dropped it
  • Broken multi-step tasks - Agent forgets step 2 results when executing step 5
  • Repetitive questions - Agent asks for clarification it already received

Hands-On Implementation

from collections import deque
from dataclasses import dataclass, field
from datetime import datetime, timedelta

@dataclass class BufferMessage: role: str content: str timestamp: datetime = field(default_factory=datetime.now) tokens: int = 0

def __post_init__(self): self.tokens = len(self.content) // 4

class SlidingWindowBuffer: """Keep the last N messages."""

def __init__(self, max_messages: int = 20): self.max_messages = max_messages self.buffer: deque[BufferMessage] = deque(maxlen=max_messages)

def add(self, role: str, content: str): self.buffer.append(BufferMessage(role=role, content=content))

def get_messages(self) -> list[dict]: return [{"role": m.role, "content": m.content} for m in self.buffer]

def __len__(self): return len(self.buffer)

class TokenLimitedBuffer: """Keep messages until a token budget is exhausted."""

def __init__(self, max_tokens: int = 4000): self.max_tokens = max_tokens self.messages: list[BufferMessage] = []

@property def total_tokens(self) -> int: return sum(m.tokens for m in self.messages)

def add(self, role: str, content: str): msg = BufferMessage(role=role, content=content) self.messages.append(msg) # Evict oldest messages until under budget while self.total_tokens > self.max_tokens and len(self.messages) > 1: self.messages.pop(0)

def get_messages(self) -> list[dict]: return [{"role": m.role, "content": m.content} for m in self.messages]

class SummaryBuffer: """Summarize old messages, keep recent ones verbatim."""

def __init__(self, max_recent: int = 6, summarize_fn=None): self.max_recent = max_recent self.recent: list[BufferMessage] = [] self.summary: str = "" self.summarize_fn = summarize_fn or self._default_summarize

def _default_summarize(self, messages: list[BufferMessage]) -> str: """Simple extractive summary (replace with LLM call in production).""" points = [] for m in messages: # Take first sentence of each message first_sentence = m.content.split(".")[0] points.append(f"- [{m.role}] {first_sentence}") return "Previous conversation summary:\n" + "\n".join(points)

def add(self, role: str, content: str): self.recent.append(BufferMessage(role=role, content=content)) if len(self.recent) > self.max_recent: # Move oldest messages into summary to_summarize = self.recent[: len(self.recent) - self.max_recent] self.recent = self.recent[len(self.recent) - self.max_recent:] self.summary = self.summarize_fn( [BufferMessage("system", self.summary)] + to_summarize if self.summary else to_summarize )

def get_messages(self) -> list[dict]: messages = [] if self.summary: messages.append({"role": "system", "content": self.summary}) messages.extend({"role": m.role, "content": m.content} for m in self.recent) return messages

Demo: Compare buffer strategies

print("=== Sliding Window Buffer ===") sw = SlidingWindowBuffer(max_messages=3) for i in range(5): sw.add("user", f"Message {i+1}: Tell me about topic {i+1}") print(f"Buffer holds {len(sw)} messages (dropped oldest 2)") for m in sw.get_messages(): print(f" {m['content'][:50]}")

print("\n=== Token-Limited Buffer ===") tb = TokenLimitedBuffer(max_tokens=100) tb.add("user", "Short message") tb.add("assistant", "This is a much longer response " * 10) tb.add("user", "Follow-up question about the topic") print(f"Buffer: {tb.total_tokens} tokens, {len(tb.messages)} messages")

print("\n=== Summary Buffer ===") sb = SummaryBuffer(max_recent=3) sb.add("user", "My name is Alex. I am learning Python.") sb.add("assistant", "Welcome Alex! I will help you learn Python.") sb.add("user", "I prefer short code examples with comments.") sb.add("assistant", "Got it - concise code with inline comments.") sb.add("user", "Now teach me about decorators.") sb.add("assistant", "A decorator wraps a function to extend its behavior.") print(f"Summary: {sb.summary}") print(f"Recent messages: {len(sb.recent)}")

Advanced Techniques

Production short-term memory systems often combine multiple strategies:

1. Hybrid Buffers - Use sliding window for recent turns + summary for older context 2. Priority Tagging - Mark certain messages as "sticky" so they survive eviction 3. Session Segmentation - Detect topic changes and summarize completed topics 4. Time-Based Decay - Weight messages by recency; old messages evict before recent ones even at same priority 5. Buffer Checkpointing - Save buffer state so conversations can resume after disconnection

Quiz

Q1: What is the main advantage of a summary buffer over a sliding window?

  • A) It uses less code
  • B) It preserves the gist of old messages instead of losing them entirely ✓
  • C) It is faster to query
  • D) It never drops any information

Q2: When would a token-limited buffer be preferred over a message-count buffer?

  • A) When all messages are the same length
  • B) When you need to control API costs based on actual token usage ✓
  • C) When the conversation is very short
  • D) When you do not care about context quality

Q3: What is the primary risk of a pure sliding window buffer?

  • A) It uses too much memory
  • B) It may drop important early instructions or user preferences ✓
  • C) It is too slow
  • D) It cannot handle multiple users