From scratch
Ren Okabe8 min read2 views

AI Agent Memory: Long-Term Memory for a Claude Agent in TypeScript (2026)

A runnable 2026 tutorial: build short-term and long-term memory for a Claude agent in TypeScript with message buffers, rolling summaries, and a durable fact store. No vector database required.

Streams of green code on a dark screen, representing an AI agent storing and recalling memory
Streams of green code on a dark screen, representing an AI agent storing and recalling memory
On this page

Quick Answer (July 2026): AI agent memory is how an agent keeps useful information across turns and across sessions instead of starting cold every time. In practice you need two things: short-term memory (the running message list, trimmed to a token budget and summarized when it gets long) and long-term memory (a small store of durable facts you inject into the prompt on each turn). You can build both with the plain Anthropic Messages API and about 60 lines of TypeScript. A vector database is optional, and most agents do not need one on day one.

Every agent tutorial eventually hits the same wall. Your loop works, the tool calls fire, and then the user says "what did I just ask you to do?" and the model has no idea. The context window reset. Nothing was remembered.

This is a memory problem, and it has a boring, reliable solution that does not require a framework. You will build it here step by step, in TypeScript, against the stable @anthropic-ai/sdk.

What "agent memory" actually means

Two different mechanisms get called "memory," and conflating them is why so many agents feel broken.

Anthropic logo Short-term memory is the conversation itself: the array of user and assistant messages you send back to Claude on every request. The model has no hidden state between API calls. If a fact is not in that array (or in the system prompt), it does not exist for this turn.

Long-term memory is information that should survive after the conversation array is trimmed or the session ends: the user's name, their stack, a decision you made three sessions ago. This lives in your own storage and gets injected into the prompt when it is relevant.

Almost every "my agent forgot everything" bug is one of these two being absent. We will add both.

Prerequisites and what you will build

You need Node 20 or newer, an Anthropic API key, and a terminal. By the end you will have a small command-line agent that:

  1. Holds a running conversation (short-term memory) with a token budget.
  2. Summarizes old turns instead of dropping them.
  3. Remembers durable facts across restarts using a tiny on-disk store.

Install the dependencies:

bash
npm init -y
npm install @anthropic-ai/sdk
export ANTHROPIC_API_KEY="sk-ant-..."

If you have not built the underlying loop before, read build your first AI agent from scratch first. This tutorial assumes you already have a working call-model-in-a-loop shape.

Start with the stateless baseline (see the problem)

Here is an agent with no memory at all. Each turn sends only the newest message.

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const MODEL = "claude-sonnet-5";

async function reply(userText: string) {
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 512,
    messages: [{ role: "user", content: userText }],
  });
  return res.content[0].type === "text" ? res.content[0].text : "";
}

console.log(await reply("My name is Priya."));
console.log(await reply("What is my name?"));

Expected output: the second answer is some version of "I do not have that information." The model never saw the first message. That is the whole bug, reproduced in ten lines.

Add short-term memory with a token budget

Short-term memory is just keeping the array. The catch is that the array grows forever, and every token you resend costs money and latency. So keep the array, but cap it.

typescript
type Msg = Anthropic.MessageParam;

const history: Msg[] = [];
const MAX_TURNS = 12; // keep the last N messages verbatim

async function chat(userText: string) {
  history.push({ role: "user", content: userText });

  const windowed = history.slice(-MAX_TURNS);
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 512,
    messages: windowed,
  });

  const text = res.content[0].type === "text" ? res.content[0].text : "";
  history.push({ role: "assistant", content: text });
  return text;
}

Heads up: a fixed message count is a rough proxy for a token budget. Claude's context window is large, but resending 100k tokens on every turn is slow and expensive. Count tokens if you need precision; a turn cap is fine to start. See Anthropic's context windows guide for the exact limits per model in 2026.

Expected output: now "What is my name?" returns "Priya," as long as the naming turn is still inside the last MAX_TURNS messages. Which exposes the next problem: slice off the old turns and the name is gone again.

Summarize old turns instead of dropping them

Do not throw away the turns you evict. Compress them. When the history crosses a threshold, ask a cheap model to fold the oldest messages into a running summary, then keep that summary as a system note.

typescript
let runningSummary = "";

async function summarizeOldTurns() {
  if (history.length <= MAX_TURNS) return;

  const toCompress = history.splice(0, history.length - MAX_TURNS);
  const res = await client.messages.create({
    model: "claude-haiku-4-5-20251001", // cheaper model for the compression step
    max_tokens: 400,
    system:
      "Update the running summary of this conversation. Keep names, decisions, " +
      "and open tasks. Be terse. Return only the updated summary.",
    messages: [
      {
        role: "user",
        content:
          `Current summary:\n${runningSummary || "(none)"}\n\n` +
          `New messages to fold in:\n${JSON.stringify(toCompress)}`,
      },
    ],
  });
  runningSummary = res.content[0].type === "text" ? res.content[0].text : runningSummary;
}

Then pass runningSummary as part of the system prompt on every real turn. The recent messages stay verbatim; everything older survives as a compact note. This is the same buffer-plus-summary pattern the popular memory libraries wrap in more machinery.

Expected output: the agent now remembers facts from 50 turns ago, because they were summarized rather than discarded, while the per-request token cost stays roughly flat.

Add long-term memory that survives a restart

Kill the process and everything above is gone. Long-term memory means writing durable facts to disk and loading them on boot. You do not need a database for this. A JSON file is a completely legitimate memory store for a single-user agent.

typescript
import { readFileSync, writeFileSync, existsSync } from "node:fs";

const FILE = "./memory.json";
type Facts = Record;

function loadFacts(): Facts {
  return existsSync(FILE) ? JSON.parse(readFileSync(FILE, "utf8")) : {};
}
function saveFact(key: string, value: string) {
  const facts = loadFacts();
  facts[key] = value;
  writeFileSync(FILE, JSON.stringify(facts, null, 2));
}

function factsBlock(): string {
  const facts = loadFacts();
  const lines = Object.entries(facts).map(([k, v]) => `- ${k}: ${v}`);
  return lines.length ? `Known facts about the user:\n${lines.join("\n")}` : "";
}

Inject factsBlock() into the system prompt on every turn, alongside runningSummary. To decide what to remember, let the model write to memory with a tool: give it a remember(key, value) tool and call saveFact in the handler. If you have not wired a tool before, the Claude Agent SDK TypeScript quickstart walks through tool definitions end to end.

Expected output: restart the process, ask "what is my stack?", and the agent answers correctly because the fact was loaded from memory.json and placed in the system prompt before the first token.

When you actually need embeddings and a vector database

Here is the counter-take the crowded SERP tends to skip: most agents do not need a vector database. A JSON or SQLite fact store plus a rolling summary covers a huge share of real single-user agents, and it is debuggable because you can open the file and read it.

You graduate to embeddings and semantic retrieval when both of these are true:

  • You have many memories (thousands of notes, documents, or past conversations), and
  • Retrieval must be by meaning, not by exact key ("what did we decide about pricing?" rather than facts["pricing_decision"]).

At that point, reach for a purpose-built system rather than hand-rolling vector search. Two open-source options worth reading, both non-Anthropic:

  • mem0 logo
    mem0 adds a scored, self-editing memory layer with extraction and retrieval built in.
  • Letta logo
    Letta is the successor to the MemGPT research (2023), which framed the LLM as an OS paging memory in and out of a limited context window. That paper is still the clearest mental model for why any of this is necessary.

Adopt one when the file store genuinely stops scaling, not before. Premature vector infrastructure is the most common overbuild in agent projects in 2026.

Verify your install

Run this end-to-end check. It exercises all three memory layers.

bash
node agent.js
# 1. Say: "My name is Priya and I ship with Next.js."
# 2. Send 15 more unrelated messages (to force summarization).
# 3. Ask: "What is my name and my stack?"
# 4. Ctrl-C, restart, ask again: "What is my stack?"

You pass if step 3 answers "Priya, Next.js" (summary survived eviction) and step 4 still answers "Next.js" after a full restart (fact store survived the process). If either fails, print runningSummary and loadFacts() before the API call and confirm both are in the system prompt.

FAQ

What is AI agent memory?
It is how an agent retains useful information across turns and sessions. It has two parts: short-term memory (the running message array you resend to the model) and long-term memory (durable facts stored outside the context window and injected when relevant).

Does an AI agent have memory by default?
No. Large language models are stateless between API calls. Any memory an agent appears to have is something your code adds by resending prior messages or by loading stored facts into the prompt.

Do I need a vector database for agent memory?
Usually not to start. A trimmed message buffer, a rolling summary, and a JSON or SQLite fact store handle most single-user agents. Add embeddings only when you have thousands of memories and need retrieval by meaning rather than by key.

How do I stop the context window from filling up?
Cap the verbatim message history to a token or turn budget, and summarize evicted turns into a compact running note instead of deleting them. Recent turns stay exact; older turns survive as a summary.

What is the difference between short-term and long-term agent memory?
Short-term memory is the conversation array that lives only for the current session and gets trimmed as it grows. Long-term memory is information written to your own storage that survives trimming and restarts, then gets re-injected into the prompt on later turns.

Which model should handle the summarization step?
Use a cheaper, faster model such as Claude Haiku 4.5 for compression, and reserve your primary model for the actual reasoning turns. Summarization is a simple transformation and does not need your most capable model.

References

  1. Anthropic, "Context windows," docs.anthropic.com, 2026.
  2. mem0, open-source memory layer, github.com/mem0ai/mem0, 2026.
  3. Letta (formerly MemGPT), github.com/letta-ai/letta, 2026.
  4. Packer et al., "MemGPT: Towards LLMs as Operating Systems," arXiv:2310.08560, 2023.
Ren Okabe

Written by

Ren Okabe

Ren builds agent infrastructure and writes copy-paste tutorials for engineers shipping LLM tool-use systems.

Frequently asked questions

What is AI agent memory?

It is how an agent retains useful information across turns and sessions. It has two parts: short-term memory (the running message array you resend to the model) and long-term memory (durable facts stored outside the context window and injected when relevant).

Does an AI agent have memory by default?

No. Large language models are stateless between API calls. Any memory an agent appears to have is something your code adds by resending prior messages or by loading stored facts into the prompt.

Do I need a vector database for agent memory?

Usually not to start. A trimmed message buffer, a rolling summary, and a JSON or SQLite fact store handle most single-user agents. Add embeddings only when you have thousands of memories and need retrieval by meaning rather than by key.

How do I stop the context window from filling up?

Cap the verbatim message history to a token or turn budget, and summarize evicted turns into a compact running note instead of deleting them. Recent turns stay exact; older turns survive as a summary.

What is the difference between short-term and long-term agent memory?

Short-term memory is the conversation array that lives only for the current session and gets trimmed as it grows. Long-term memory is information written to your own storage that survives trimming and restarts, then gets re-injected into the prompt on later turns.

Which model should handle the summarization step?

Use a cheaper, faster model such as Claude Haiku 4.5 for compression, and reserve your primary model for the actual reasoning turns. Summarization is a simple transformation and does not need your most capable model.

From scratch

Build your first AI agent from scratch in 30 minutes

An AI agent is just a loop: you call a model, the model asks to run a tool, you run it, you feed the result back, and you repeat until the model is done. In this tutorial you build that loop yourself in plain TypeScript against the Anthropic Messages API, no framework. You will wire up two tools (read a file, run a calculation), let the model orchestrate them, add a turn cap and basic guardrails, then verify the whole thing end to end. The result is a small research agent you fully understand and can extend with your own tools.

6 min read99