← Blog

Claude API Agent Memory Implementation Guide

2026-10-18 · 5 min read · SubToAPI Team

Claude doesn't remember anything between API calls. Every request is stateless — the model only "knows" what's in the messages array you send it. If you want an agent that recalls what a user told it yesterday, tracks facts across a long project, or avoids repeating questions it already asked, you have to build that memory yourself. This guide walks through the actual implementation patterns: what to store, where to store it, how to retrieve it, and how to fit it into Claude's context window without blowing your token budget.

There's no single "memory API" to call. Memory for an LLM agent is an engineering problem that sits outside the model: you capture information, persist it, retrieve the relevant subset, and inject it back into the prompt. The rest of this article covers each of those steps with concrete code.

Why Claude needs external memory

Claude's context window holds everything relevant to the current conversation — system prompt, chat history, tool results — but it resets the moment you start a new request thread or exceed the window. Three distinct memory problems show up in practice:

Each needs a different storage and retrieval strategy, so don't try to solve them with one generic "memory table."

Pattern 1: Rolling summary for session memory

The simplest approach: when the conversation gets long, ask Claude to summarize everything so far, store that summary, and replace the old messages with it.

async function summarizeIfNeeded(messages, client) {
  const estimatedTokens = messages.reduce((sum, m) => sum + m.content.length / 4, 0);
  if (estimatedTokens < 6000) return messages;

  const summaryResponse = await client.post('/v1/messages', {
    model: 'claude-sonnet-4',
    max_tokens: 500,
    messages: [
      ...messages,
      { role: 'user', content: 'Summarize this conversation so far in under 300 words, keeping all facts, decisions, and open questions.' }
    ]
  });

  const summary = summaryResponse.data.content[0].text;
  return [
    { role: 'user', content: `Conversation summary so far: ${summary}` },
    ...messages.slice(-4) // keep last couple of exchanges verbatim
  ];
}

This keeps token costs predictable and avoids truncating context mid-thought. Run it as a check before every request once the transcript crosses a threshold.

Pattern 2: Structured facts for user memory

Free-text summaries drift and lose precision. For durable facts about a user — their name, stack, preferences, past decisions — store structured key-value or JSON records in a real database, not inside the prompt history.

// Extract facts worth remembering after a conversation turn
async function extractFacts(userMessage, assistantReply, client) {
  const res = await client.post('/v1/messages', {
    model: 'claude-sonnet-4',
    max_tokens: 300,
    messages: [{
      role: 'user',
      content: `Extract any durable facts about the user from this exchange as JSON (empty object if none):\nUser: ${userMessage}\nAssistant: ${assistantReply}`
    }]
  });
  return JSON.parse(res.data.content[0].text);
}

// Then merge into your user record
await db.users.update(userId, { $merge: extractedFacts });

On the next session, load that user record and inject it as a short system-prompt addendum:

const facts = await db.users.get(userId);
const systemPrompt = `You are a helpful assistant. Known facts about this user: ${JSON.stringify(facts)}`;

This keeps memory precise, queryable, and cheap to inject — a few hundred tokens instead of a full transcript.

Pattern 3: Vector retrieval for large memory stores

Once a user has months of history or you're running an agent across hundreds of documents, structured key-value storage stops scaling. Embed each memory chunk, store the vectors, and retrieve only what's relevant to the current query before calling Claude.

const relevant = await vectorStore.query(currentUserMessage, { topK: 5 });
const memoryContext = relevant.map(r => r.text).join('\n---\n');

const messages = [
  { role: 'user', content: `Relevant past context:\n${memoryContext}\n\nCurrent question: ${currentUserMessage}` }
];

This is retrieval-augmented generation applied to agent memory rather than documents. The embedding model doesn't have to be Claude — use any embedding provider and keep Claude focused on generation.

Pattern 4: Task state for multi-step agents

Agents that use tools need memory of their own actions: what they called, what came back, what's still pending. The cleanest implementation is an explicit state object you pass through the loop, separate from the conversation itself.

let taskState = { completedSteps: [], pendingSteps: ['fetch_data', 'validate', 'write_report'] };

while (taskState.pendingSteps.length > 0) {
  const step = taskState.pendingSteps[0];
  const response = await client.post('/v1/messages', {
    model: 'claude-sonnet-4',
    max_tokens: 1024,
    tools: [...],
    messages: [{ role: 'user', content: `Current step: ${step}. Completed so far: ${JSON.stringify(taskState.completedSteps)}` }]
  });
  // handle tool_use blocks, update taskState accordingly
  taskState.completedSteps.push(step);
  taskState.pendingSteps.shift();
}

Persist taskState to a database or queue so a crashed process can resume instead of restarting from scratch — this matters a lot for long-running agents.

Where SubToAPI fits

None of this memory logic lives inside the API layer — it's your responsibility regardless of how you call Claude. But if you're building this on top of raw Anthropic access, you still need to handle streaming responses while memory extraction runs asynchronously, track token usage per user to cost out your memory pipeline, and manage API keys across a team building the same agent. SubToAPI gives you that operational layer: application-scoped keys (sub_live_...), streaming and tool use support, and per-key usage metadata so you can see exactly how much of your token budget memory summarization is consuming versus the actual conversation. Check the quickstart or the Messages API docs to wire it in, and see pricing if you're evaluating it for a team — plans start at Solo for solo builders and scale up to Scale for larger teams, with a free trial at signup.

Putting it together

A production agent typically layers all four patterns: rolling summaries for session continuity, structured facts for user-level personalization, vector retrieval for large historical stores, and explicit task state for multi-step execution. Start with whichever one matches your actual bottleneck — most teams only need the rolling summary and structured facts patterns before their memory problem is solved.

questions

Does Claude have a built-in memory feature? No. The Claude API is stateless per request. Any persistence across requests or sessions has to be implemented in your application using the patterns above.

What's the cheapest way to add memory to a simple chatbot? Structured fact extraction (Pattern 2) is usually enough — store a small JSON record per user and inject it into the system prompt. It costs far fewer tokens than re-sending full transcripts.

How do I prevent memory from blowing up my context window? Summarize aggressively, cap stored facts to what's actually useful, and use retrieval (Pattern 3) instead of injecting everything — only pull in the memory chunks relevant to the current query.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →