Skip to content

Short-Term Memory: Context Window Management

Overview

Short-term memory is the "working memory" of an agent—what's currently in focus. For LLM-based agents, this is essentially the context window they're operating within.

The challenge: Finite context + Infinite possible information = Must choose carefully


What is Short-Term Memory?

Definition

Information agent is currently working with, typically measured in tokens and usually fits in the LLM's context window.

Characteristics

  • Duration: Seconds to minutes (single conversation turn)
  • Capacity: 100K-1M tokens (depending on model)
  • Speed: Instant access (already in context)
  • Scope: Current task only
  • Volatility: Cleared after task completion

Example

Agent working on: "Analyze these 5 research papers"

Short-term memory currently holds:
  ✓ Goal: "Analyze papers"
  ✓ Papers retrieved: [paper1, paper2, paper3, paper4, paper5]
  ✓ Analysis so far: [analysis of papers 1-3]
  ✓ Next action: "Analyze paper 4"
  ✗ Past conversations: [NOT in short-term]
  ✗ General knowledge: [Only relevant parts in context]

The Context Window Problem

The Dilemma

LLM Context Window: 100K tokens
  │
  - System prompt:        5K tokens
  - Current goal:         1K tokens
  - Instruction prompt:   3K tokens
  - Available for data:   91K tokens

But agent needs:
  - Previous turns:       20K tokens
  - Retrieved documents:  30K tokens
  - Reasoning trace:      15K tokens
  - Current observations: 10K tokens
  - Total needed:         75K tokens ✓ Fits!

But what if the conversation is longer?
  Previous turns: 100K tokens
  ✗ Doesn't fit!

Context Window Strategies

Strategy 1: Summarization

Original conversation (100K tokens):
  User: "Tell me about climate change"
  Agent: [Long response about causes, effects, solutions]
  User: "What about the economic impact?"
  Agent: [Another long response]
  User: "Compare solutions"
  ...

Summarized (5K tokens):
  Summary of earlier discussion:
  - Climate change caused by greenhouse gases
  - Economic impacts: $2T annual costs
  - Solutions: Renewable energy, efficiency, carbon capture

Implementation:

class SummarizationStrategy:
    def compress_history(self, history, target_tokens=5000):
        """Summarize old conversation turns"""
        # Keep recent turns fully
        keep_recent = 3  # Last 3 turns
        to_summarize = history[:-keep_recent]

        # Summarize older turns
        summarized = self.llm.summarize(
            to_summarize,
            max_tokens=target_tokens
        )

        return summarized + history[-keep_recent:]

Strategy 2: Selective Retention

Keep:
  ✓ Recent interactions (last 5)
  ✓ Important facts mentioned
  ✓ User preferences stated
  ✓ Task-critical information

Discard:
  ✗ Intermediate reasoning
  ✗ Verbose explanations
  ✗ Repeated information
  ✗ Off-topic comments

Implementation:

class SelectiveRetention:
    def filter_history(self, history):
        """Keep only essential information"""
        essential = []

        for turn in history:
            if self._is_essential(turn):
                essential.append(turn)

        return essential

    def _is_essential(self, turn):
        """Judge if turn should be kept"""
        if turn.is_recent:
            return True  # Keep recent
        if turn.contains_fact:
            return True  # Keep facts
        if turn.affects_task:
            return True  # Keep task-relevant
        if turn.is_preference:
            return True  # Keep user preferences
        return False

Strategy 3: Windowing

Maintain sliding window of recent context:

- Turn 1: ────┐
  - Turn 2:     ├─ [Window 1: turns 1-5]
  - Turn 3:     │
  - Turn 4:     │
- Turn 5: ────┘
- Turn 6:     ┐
  - Turn 7:     ├─ [Window 2: turns 6-10]
  - Turn 8:     │
  - Turn 9:     │
- Turn 10:────┘
- Turn 11:    ┐
  - Turn 12:    ├─ [Window 3: turns 11-15]
...

Implementation:

class WindowingStrategy:
    def __init__(self, window_size=5):
        self.window_size = window_size
        self.history = []

    def get_context(self):
        """Get current window of context"""
        start_idx = max(0, len(self.history) - self.window_size)
        return self.history[start_idx:]

    def add_turn(self, turn):
        """Add new turn to history"""
        self.history.append(turn)

        if len(self.history) > self.window_size:
            # Old turns fall out of window
            # But should be stored in long-term memory
            pass


Short-Term Memory Architecture

- ┌─────────────────────────────────────────────┐
    - LLM Context Window                  │
    - (Short-Term Memory)                   │
  - ┤
│                                             │
    - System Prompt                              │
      - Task description                       │
      - Behavioral guidelines                  │
      - Output format spec                     │
│                                             │
    - Recent Context                             │
      - Last 5 conversation turns              │
      - Relevant retrieved documents           │
      - Current goal/subtask                   │
      - Working state/progress                 │
      - Recent observations/results            │
│                                             │
    - Instruction Prompt                         │
      - "You are currently at step 3/5"        │
      - "The user just said: ..."              │
      - "Respond with JSON: ..."               │
│                                             │
  - ┘

Implementing Short-Term Memory

Basic Implementation

class ShortTermMemory:
    def __init__(self, max_tokens=90000):
        self.max_tokens = max_tokens
        self.current_tokens = 0
        self.items = []

    def add(self, content: str, priority: int = 0):
        """Add content to short-term memory"""
        tokens = count_tokens(content)

        # Check if fits
        if self.current_tokens + tokens > self.max_tokens:
            # Make space by removing low-priority items
            self._make_space(tokens)

        self.items.append({
            "content": content,
            "priority": priority,
            "timestamp": now(),
            "tokens": tokens
        })

        self.current_tokens += tokens

    def _make_space(self, needed_tokens):
        """Remove items to make space"""
        # Sort by priority (keep high-priority)
        self.items.sort(key=lambda x: x['priority'], reverse=True)

        # Remove low-priority items until we have space
        while self.current_tokens + needed_tokens > self.max_tokens:
            removed = self.items.pop()
            self.current_tokens -= removed['tokens']

    def get_context(self):
        """Get formatted context for LLM"""
        return "\n".join([
            item['content'] for item in self.items
        ])

    def clear(self):
        """Clear memory at end of task"""
        self.items = []
        self.current_tokens = 0

# Usage
memory = ShortTermMemory(max_tokens=90000)

memory.add("Goal: Analyze market trends", priority=10)
memory.add("Retrieved data: Q1 results...", priority=8)
memory.add("Current finding: Growth +15%", priority=9)

context = memory.get_context()
# Pass to LLM

Advanced: Prioritization

Priority Scoring

class PriorityScorer:
    def score(self, content, context):
        """Score content importance"""
        score = 0

        # Factor 1: Recency (0-30 points)
        age_seconds = (now() - content.timestamp).total_seconds()
        if age_seconds < 60:
            score += 30
        elif age_seconds < 300:
            score += 20
        elif age_seconds < 3600:
            score += 10

        # Factor 2: Task Relevance (0-40 points)
        if content.mentions_goal:
            score += 40
        elif content.is_observation:
            score += 30
        elif content.is_reasoning:
            score += 15

        # Factor 3: Frequency (0-20 points)
        if content.access_count > 5:
            score += 20
        elif content.access_count > 2:
            score += 10

        # Factor 4: User-provided (0-10 points)
        if content.from_user:
            score += 10

        return score

Common Patterns

Pattern 1: Prompt Recycling

def build_prompt_with_context(memory: ShortTermMemory, task: str):
    """Build prompt including current context"""

    system_prompt = """
    You are a helpful research assistant.
    Complete the task below using the context provided.
    """

    context = memory.get_context()

    user_prompt = f"""
    Context (previous findings):
    {context}

    Current task:
    {task}

    Respond with your findings.
    """

    return system_prompt, user_prompt

Pattern 2: Progressive Enrichment

class ProgressiveContext:
    def __init__(self):
        self.history = []
        self.observations = []

    def execute_step(self, step_num, total_steps):
        """Execute one step, building context"""

        # Add progress to memory
        self.history.append(f"Step {step_num}/{total_steps}")

        # Get relevant context
        context = self.build_context()

        # Execute
        result = self.llm.process(context, step_num)

        # Record result
        self.observations.append(result)

        return result

    def build_context(self):
        """Build context with completed steps"""
        return {
            "steps_completed": self.history,
            "findings_so_far": self.observations,
            "tokens_used": self.estimate_tokens()
        }

Pattern 3: Sliding Window

class SlidingWindowMemory:
    def __init__(self, window_size=5):
        self.window_size = window_size
        self.all_history = []

    def add_turn(self, turn):
        """Add turn to history"""
        self.all_history.append(turn)

    def get_context_window(self):
        """Get recent window for LLM"""
        start = max(0, len(self.all_history) - self.window_size)
        return self.all_history[start:]

    def get_full_history_for_longterm(self):
        """Get full history for storage"""
        return self.all_history

Token Budgeting

Allocating Tokens

Context Window: 100K tokens total

Fixed overhead (30%):
  - System prompt:        5K
  - Instruction prompt:   5K
  - Output format spec:   5K
  - Few-shot examples:    5K
  - Reserved for output:  5K
  - Subtotal:            25K

Available for content (70%):
  - Current goal:         2K (required)
  - Recent context:      30K (recent turns)
  - Retrieved documents: 20K (for task)
  - Working state:        5K (current progress)
  - Buffer:               8K (safety margin)
  - Subtotal:            65K

Implementation:

class TokenBudgeter:
    def __init__(self, context_size=100000):
        self.context_size = context_size

        # Allocate fixed overhead
        self.system_prompt_budget = 5000
        self.instruction_budget = 5000
        self.output_budget = 5000
        self.buffer = 5000

        # Available for content
        self.content_budget = (
            context_size - 
            self.system_prompt_budget -
            self.instruction_budget -
            self.output_budget -
            self.buffer
        )

    def allocate_content(self):
        """Allocate tokens to different content types"""
        return {
            "goal": int(0.05 * self.content_budget),
            "context": int(0.50 * self.content_budget),
            "documents": int(0.35 * self.content_budget),
            "state": int(0.10 * self.content_budget),
        }


Best Practices

1. Explicit Token Counting

# ✅ Good: Track actual tokens
from tiktoken import encoding_for_model

enc = encoding_for_model("gpt-4")
tokens = len(enc.encode(text))

# ❌ Bad: Guess at tokens
estimated = len(text.split()) // 0.75  # Rough estimate

2. Plan for Overflow

# ✅ Good: Handle gracefully
if current_tokens + new_content_tokens > MAX:
    # Compress, summarize, or discard
    make_space(needed_tokens)

# ❌ Bad: Crash on overflow
memory.add(new_content)  # May overflow

3. Preserve Task-Critical Info

# ✅ Good: Essential stays
memory.prioritize([
    goal,              # Keep: defines task
    user_preferences,  # Keep: affects output
    current_state,     # Keep: tracks progress
    # Lower priority: verbose explanations
])

# ❌ Bad: Treat all equal
memory.add(everything)

4. Use Retrieval for Reference

# ✅ Good: Reference, not full content
# In context: "Earlier discussed 5 solutions"
# Retrieve full details only if needed

# ❌ Bad: Always include everything
# Context: [Full text of all 5 solutions]

Debugging Short-Term Memory

def debug_context(memory: ShortTermMemory):
    """Debug what's in current context"""

    print(f"Total tokens used: {memory.current_tokens}")
    print(f"Max capacity: {memory.max_tokens}")
    print(f"Usage: {memory.current_tokens/memory.max_tokens*100:.1f}%")

    print("\nItems in memory (by priority):")
    sorted_items = sorted(
        memory.items,
        key=lambda x: x['priority'],
        reverse=True
    )

    for item in sorted_items:
        print(f"  [{item['priority']:2d}] {item['tokens']:5d} tokens: "
              f"{item['content'][:50]}...")

    print("\nLow-priority candidates for removal:")
    low_priority = [i for i in memory.items if i['priority'] < 3]
    for item in low_priority:
        print(f"  {item['tokens']:5d} tokens: {item['content'][:50]}...")

Key Takeaways

  1. Context window is finite - Must choose what to include
  2. Prioritization critical - Keep task-relevant, discard noise
  3. Strategies exist - Summarization, windowing, selective retention
  4. Token budget - Allocate wisely to system, content, output
  5. Overflow handling - Plan for when content exceeds capacity
  6. Measure everything - Count actual tokens, don't guess

Next Steps


Last Updated: August 9, 2026