
Previous article:
Imagine this conversation with an Agent:
You: How many TypeScript files are in the source code?
Agent: *runs find . -name "*.ts"* 247 files total
You: What about Vue component files?
Agent: *runs find . -name "*.vue"* 89 files total
You: What's the breakdown by directory?
Agent: Uh... what were those numbers again? Let me re-run...The third question should be answerable from the first two Observations. But the Agent has already "forgotten" — the context window filled up with accumulated messages, and the earliest Observations were truncated.
This is the central tension of memory systems: an Agent's capability depends on how much it can "remember," yet the context window is a hard physical limit. GPT-4's 128K tokens sounds generous, but a few file operations and ReAct cycles consume it entirely.
Agent memory systems draw on the layered memory architecture of human cognition — not as metaphor, but as engineering practice:
┌──────────────────────────────────────┐
│ Short-term Memory │
│ Current session conversation │
│ Concatenated directly into prompt │
│ Hard-limited by context window │
│ Strategies: sliding / summary / │
│ compaction │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Working Memory (Scratchpad) │
│ Intermediate results within task │
│ Like scratch paper — read/write │
│ via tool calls │
│ Lifetime: within one task │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Long-term Memory │
│ Persisted across sessions │
│ Vector DB / Knowledge Graph / │
│ Virtual Context │
│ User preferences, history, facts │
└──────────────────────────────────────┘Let's go through each layer in depth.
Short-term memory is the first hurdle every Agent framework must clear. Here's the evolution from "naive" to "smart."
// The simplest approach: keep everything
messages.push({ role: "user", content: query });
messages.push({ role: "assistant", content: reasoning + response });
messages.push({ role: "user", content: `Observation: ${observation}` });Advantage: no information loss. Disadvantage: API throws errors when the conversation gets slightly long.
Keep only the most recent N messages or N tokens; delete the oldest:
function trimByTokenLimit(
messages: Message[],
maxTokens: number,
systemPrompt: Message
): Message[] {
let total = countTokens([systemPrompt, ...messages]);
// Delete from the oldest message pairs
while (total > maxTokens * 0.9 && messages.length > 2) {
total -= countTokens([messages[0], messages[1]]);
messages.splice(0, 2); // Remove oldest user + assistant pair
}
return messages;
}Advantage: simple implementation. Fatal flaw: deleted content is gone forever — if the user says "go back to what we first discussed," the Agent can only apologize.
Instead of deleting history, use the LLM to compress old conversations into a summary:
Original history (~3000 tokens):
User asked about README.md → Agent read and answered
User followed up on package.json → Agent listed deps: openai, dotenv, undici
User asked to compare → Agent found both mention TypeScript
↓ LLM compression ↓
Summary (~80 tokens):
Project is Node.js + TypeScript, deps: openai/dotenv/undici.
Both README.md and package.json confirm use of TypeScript.This summary replaces the original conversation as part of the system message, drastically saving tokens:
async function summarizeAndTrim(messages: Message[]): Promise<Message[]> {
// Keep the most recent 4 messages intact
const recent = messages.splice(-4);
// Compress the rest with LLM
const summary = await llm.chat([
{ role: "system", content: "Compress the following into a concise summary, preserving all key facts:" },
...messages,
]);
return [
{ role: "system", content: `[Conversation Summary]\n${summary}` },
...recent,
];
}This is the core logic behind LangChain's ConversationSummaryMemory and one of the strategies used internally by OpenAI's Assistants API.
Keep the most recent K turns in full; compress everything older into a summary. This is LangChain's ConversationSummaryBufferMemory and the most commonly used approach in production:
[System Prompt]
[Summary: compressed content from turns 1–5]
──────────────────────────────────────────
[Turn 6: full dialogue]
[Turn 7: full dialogue]
[Turn 8: full dialogue] ← most recent, kept completeWhy keep recent turns intact? Summarization inevitably loses detail — tone of voice, exact numbers, code snippets. Recent content is most likely to be referenced; keeping it uncompressed is a worthwhile token investment.
OpenAI's Responses API provides an automated compaction feature. Developers don't need to write their own summarization logic — when context approaches the limit, the API automatically compresses older messages into shorter equivalent representations. It's essentially weighted summarization, fully managed server-side.
Comparing the five approaches:
| Strategy | Information Fidelity | Token Efficiency | Implementation Complexity |
|---|---|---|---|
| Full Retention | Highest | Lowest (quickly exceeds limit) | Lowest |
| Sliding Window | Low (old info lost) | Medium | Low |
| Summarization | Medium (details lost, facts preserved) | High | Medium |
| Buffer + Summary | High (recent full, old summary) | High | Medium |
| Compaction | Medium–High | Highest | Lowest (API-managed) |
Short-term memory handles "remembering the conversation." But when executing complex tasks, an Agent also needs to pass intermediate results between steps within the same task — this memory doesn't span sessions, but lasts longer than a single Observation.
Consider this task:
User: Analyze the project's code structure. Count TS files per directory,
find the directory with the most files, write that directory's
file list to report.txtThe Agent's execution:
Round 1: listFiles → 200 results...
Rounds 2–11: count for each subdirectory → 10 loop rounds...
Round 12: I need to find the directory with the most files...
→ ...what were those count results again? Already truncated!The problem: Rounds 2–11 produced massive Observations (file counts per directory) that filled the context window. By Round 12, when aggregation is needed, the earliest results are gone.
Give the Agent a "notepad" — let it write intermediate results down and read them back when needed:
Round 2:
Action: writeFile
Action Input: {"path": "/tmp/analysis.txt", "content": "src/: 50 files\ncomponents/: 20 files\nutils/: 15 files", "append": true}
Round 12:
Action: readFile
Action Input: {"path": "/tmp/analysis.txt"}
→ Observation: src/: 50 files\ncomponents/: 20 files\nutils/: 15 filesThis "notepad" file is working memory — it's not part of the conversation history, but it can be retrieved via tool calls when needed.
.claude/ or .codex/ directories in the repo root during multi-step tasks, storing intermediate state and todo listsKey insight: working memory isn't something the Agent framework manages for you — it's something you enable by giving the Agent "write file + read file" capabilities and letting it manage its own scratch paper.
Short-term memory lives only within a single conversation. Reopen the terminal tomorrow and the Agent is brand new — it doesn't know you, doesn't remember what you did before. Long-term memory solves this "cross-session amnesia."
This isn't a "can we store it?" problem — disk is cheap. The real challenge is precisely retrieving the most relevant fraction from a vast memory store.
This is the most mainstream engineering approach, used by LangChain, OpenAI Assistants, and countless startups.
Write:
Conversation chunk → Embedding model → vector [0.12, -0.34, 0.78, ...] → store in vector DBRetrieve:
User query → Embedding model → query vector → cosine similarity search → Top-K relevant memories → inject into promptThe advantage of vector search is semantic understanding — "React project" and "frontend framework" are close in vector space even without literal overlap. The limitation is lack of structured reasoning — it can tell you "the user previously used Zustand" but cannot infer "the user dislikes Redux, so don't recommend Redux this time either."
In 2023, UC Berkeley's MemGPT paper proposed a radical idea: let the LLM manage its own memory like an operating system.
The core insight comes from OS virtual memory:
Operating System:
RAM (fast but small) ←→ Disk (slow but large)
"Paging" moves data between them, giving programs the illusion of infinite memory
MemGPT:
Context window (fast but small, ~128K tokens) ←→ External storage (slow but large, unlimited)
The LLM itself decides when to "page," giving conversations the illusion of infinite contextMemGPT splits the context window into two parts:
The LLM manages memory through a set of special "memory management functions":
conversation_search(query) — retrieve relevant memories from external storagearchival_memory_insert(content) — store important information to external storagearchival_memory_search(query) — search the archiveThis essentially delegates the "paging" decision to the LLM itself, rather than using preset truncation rules. When context is nearly full, the LLM can proactively decide: what to "swap out" to external storage, and what to "swap in" from external storage.
MemGPT later evolved into Letta, which further introduced:
In 2023, Stanford's Generative Agents paper built a simulated town with 25 AI agents that wake up, cook breakfast, go to work, and make friends. What powers all this is a carefully designed memory architecture:
Memory Stream
│ Every observation ("I saw John in the kitchen") is recorded in natural language
│ Tagged with timestamp and importance score
│
├──→ Retrieval
│ Based on three dimensions: recency (recent matters more), importance (agent's own rating), relevance (semantic similarity)
│
├──→ Reflection
│ Periodically "synthesizes" from many low-level memories into higher-level insights
│
└──→ Planning
Generates daily plans based on current state and memories; dynamically adjusts during executionThe core innovation isn't retrieval (RAG is everywhere) — it's reflection: letting the agent not just store raw memories, but periodically distill higher-level understanding from many low-level memories.
Example:
Raw memories:
Day 1: User built project with React + TypeScript
Day 5: User rejected Redux, chose Zustand
Day 12: User asked "faster alternative to Jest," chose Vitest
↓ Reflection ↓
Distilled understanding:
User prefers lightweight tools, rejects heavyweight solutions.
Tech stack tendency: React + TypeScript + Zustand (state) + Vitest (testing).
Open-minded but deliberate about new tools.This high-level understanding is far more useful in subsequent conversations than raw conversation fragments — it helps the Agent make decisions aligned with user preferences.
| Approach | Core Mechanism | Strengths | Limitations |
|---|---|---|---|
| RAG + Vector DB | Semantic retrieval | Mature, well-engineered | No reasoning capability |
| MemGPT Virtual Context | LLM self-managed paging | Flexible, human-like | Depends on LLM judgment quality |
| Generative Agents | Memory stream + reflection | Produces high-level insights | Complex, token-intensive |
| Letta Context Repo | Git-versioned memory | Auditable, rollback-able | Still early stage |
In practice, these approaches are often combined — vector retrieval as the first recall layer, reflection as the second distillation layer, and the LLM making the final decision about what to place in context.
With all these approaches laid out, you might wonder: which one should my Agent use?
Answer these three questions first:
Recommended starting point:
Step 1: Full retention + sliding window (ensure it doesn't crash)
↓
Step 2: Add summarization (improve long-conversation quality)
↓
Step 3: Add vector database (cross-session memory)
↓
Step 4 (if needed): Reflection / distillation mechanismMost applications stop at Step 3 and are already excellent. Step 4 is for when your Agent needs to "know you" like a genuine human assistant.
On top of the Agent built in Parts 1–4:
countTokens function (use tiktoken or simple estimate: 1 token ≈ 4 characters)Add saveToScratchpad and readScratchpad tools to your Agent (under the hood, just read/write a temp file). Then give the Agent a task requiring intermediate results across multiple rounds. Observe whether it actively uses these tools.
npx chroma or sqlite-vecObserve how an Agent with long-term memory responds as if it "knows the user."
Keywords: Short-term Memory, Working Memory, Long-term Memory, Context Window, Sliding Window, Summarization, Compaction, RAG, Vector Database, MemGPT, Generative Agents, Memory Stream, Reflection, Context Constitution
The next article is the final chapter of this series: Plan-Exec Mode and Beyond. When tasks become too complex for ReAct's "think-and-act" alone, how do we introduce the "plan first, then execute" two-phase pattern — and what lies ahead for Agent technology: multi-agent collaboration, self-reflection, and long-running task orchestration.
Next article: