new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

AI SDK · Cookbook · all subjects

agents/memory

43 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Memory types: core, archival, and recall

Memory comes in three types, each serving a different purpose. Core memory is information included in every turn, injected directly into the system prompt so the model always has it without needing a tool call—examples include the user's name or instructions for accessing other memories. Archival memory is a notes folder or file where the model stores detailed knowledge, functioning like a notebook for facts, summaries, and observations that the model reads and writes on demand through the memory tool. Recall memory is the conversations themselves—persisting full turn-by-turn history allows the model to search previous interactions and surface relevant context from past discussions.

Memory storage as filesystem abstraction

Memory should be stored as files organized in a hierarchical, filesystem-like structure. The backing store implementation does not matter—it could be a real sandboxed filesystem, an in-memory virtual filesystem, or a shim over a database like Postgres. What matters is the concept: files organized in a hierarchical directory structure with an interface that can manipulate, search, read, and edit those files. This approach offers persistence across process restarts and conversations, fast read/write performance even at scale, alignment with language model training data (LLMs understand files and paths), and the ability to create deep and organized memory banks by grouping memories by topic, time, or type.

Two approaches for model memory tool interaction

Two patterns exist for giving a language model access to memory files. The Structured Actions Tool approach defines explicit actions the model can take (view, create, update, search) and has the model generate structured input that you handle yourself. This is safe by design since you control every operation that runs, but it requires more upfront implementation and limits the model to only the actions you have built. The Bash-Backed Tool approach backs the memory tool with bash, allowing models to compose flexible shell commands (cat, grep, pipe operations, sed in-place edits) to craft queries and access what they need. This is more powerful and flexible, but requires careful work to build an approval system that prevents prompt injection and blocks dangerous commands.

Memory directory structure for agent persistence

The memory layout uses a .memory directory with three files: core.md for core memory injected every turn, notes.md for archival memory with timestamped notes, and conversations.jsonl for recall memory containing full turn history in JSONL format (one JSON object per line).

Inject core memory via prepareCall hook

Use the prepareCall hook in ToolLoopAgent to read core memory fresh before every LLM call and inject it into the system prompt. Because prepareCall runs before each generate call in the tool loop, the system prompt always reflects the latest state of core.md. If the model updates core memory during a conversation, the next loop iteration sees the change immediately.

Structured Actions Memory Tool schema

The structured actions memory tool uses a Zod schema with these fields: command (required, enum of 'view', 'create', 'update', 'search'), path (optional string for memory path under /memories like /memories/core.md or /memories/notes.md, required for view/create/update), content (optional string for text to write in create or update commands), mode (optional enum of 'append' or 'overwrite' for write operations, defaults to overwrite), and query (optional string with search keywords, prefer short focused terms of 1-4 words). The tool routes every request through a runMemoryCommand handler that executes only safe, explicit operations on known .memory paths.

Bash-backed memory tool with just-bash

The bash-backed memory tool uses just-bash (a JavaScript-based bash interpreter with AST parser) to allow flexible shell commands for memory operations. ReadWriteFs reads and writes directly to the real filesystem rooted at process.cwd(), so paths inside bash (like /.memory/core.md) map directly to disk at <project-root>/.memory/core.md. The safety pipeline has two layers: an AST-based command guard rejects unapproved commands before they reach the interpreter, and just-bash itself is a JavaScript implementation (does not spawn a real shell process), so the filesystem is real but the command execution environment is controlled.

Bash-backed memory tool usage examples

Bash-backed memory tool examples: cat /.memory/core.md (view file), echo "- User prefers concise answers" >> /.memory/core.md (append to core memory), perl -pi -e 's/concise answers/detailed answers/g' /.memory/core.md (in-place edit), grep -n "project" /.memory/notes.md (search notes), echo "2026-02-16: started a Rust CLI" >> /.memory/notes.md (timestamped note), grep -niE "pricing|budget" /.memory/conversations.jsonl (search conversations), tail -n 40 /.memory/conversations.jsonl | jq -c '.role + ": " + .content' (format recent conversation).

ToolLoopAgent memory setup pattern

Wire memory into an agent using ToolLoopAgent with prepareCall. Create const memoryAgent = new ToolLoopAgent({ model: 'anthropic/claude-haiku-4.5', tools: { memory: memoryTool }, prepareCall: async settings => { const coreMemory = await readCoreMemory(); return { ...settings, instructions: `Today's date is ${today}. Core memory: ${coreMemory}. You can save and recall important information using the memory tool.` }; } });. The prepareCall hook reads core.md fresh before each LLM call and injects it into the system prompt, ensuring the model always sees the latest state.

Memory agent run loop pattern

To run a memory agent: (1) Record the user message with appendConversation({ role: 'user', content: prompt, timestamp: new Date().toISOString() }). (2) Run the agent with const result = await memoryAgent.generate({ prompt }), which loops automatically on tool calls. (3) Record the assistant response with appendConversation({ role: 'assistant', content: result.text, timestamp: new Date().toISOString() }). When the model needs to store or recall information, it calls the memory tool, the ToolLoopAgent executes it and feeds the result back, continuing until the model produces a final text response.

Bootstrap memory filesystem on startup

On startup, ensure the memory directory and its files exist with reasonable defaults using ensureMemoryFilesystem(). This is a one-time setup: create the .memory directory if missing, seed each file with starter content if it does not already exist, and add .memory to .gitignore to keep it local and private. Use Node.js filesystem APIs like mkdir({ recursive: true }) to create the directory and writeFile() to seed default content only if the file does not exist.

Filesystem bootstrap implementation with defaults

const MEMORY_DIR = '.memory'; const MEMORY_ROOT = resolve(process.cwd(), MEMORY_DIR); const CORE_MEMORY_PATH = join(MEMORY_ROOT, 'core.md'); const NOTES_PATH = join(MEMORY_ROOT, 'notes.md'); const CONVERSATIONS_PATH = join(MEMORY_ROOT, 'conversations.jsonl'); const DEFAULT_CORE_MEMORY = `# Core Memory\n- Keep this short.\n- Put stable user facts here.\n`; const DEFAULT_NOTES = `# Notes\nUse this file for detailed memories and timestamped notes.\n`; async function ensureFile(path: string, content: string): Promise<void> { try { await access(path); } catch { await writeFile(path, content, 'utf8'); } } async function ensureMemoryFilesystem(): Promise<void> { await mkdir(MEMORY_ROOT, { recursive: true }); await ensureFile(CORE_MEMORY_PATH, DEFAULT_CORE_MEMORY); await ensureFile(NOTES_PATH, DEFAULT_NOTES); await ensureFile(CONVERSATIONS_PATH, ''); }

Helper functions for core memory and conversation logging

Implement readCoreMemory() to fetch the contents of core.md for system prompt injection: async function readCoreMemory(): Promise<string> { try { return await readFile(CORE_MEMORY_PATH, 'utf8'); } catch { return ''; } }. Implement appendConversation() to append JSONL entries: async function appendConversation(entry: { role: 'user' | 'assistant'; content: string; timestamp: string }): Promise<void> { await appendFile(CONVERSATIONS_PATH, `${JSON.stringify(entry)}\n`, 'utf8'); }. Conversations are stored as JSONL (one JSON object per line), which makes them straightforward to grep for keywords and pipe through jq for formatting.

Structured actions handler implementation

The runMemoryCommand function maps each action to a filesystem operation. For 'view': read the file and return its content. For 'create' or 'update': write or append content to the file. For 'search': read all specified files (or all memory files if no path given), split by newline, search each line for any of the query terms (case-insensitive substring match), and return matching lines in format 'filename:lineNumber:line'. Paths are resolved relative to MEMORY_ROOT via resolveMemoryPath(), which strips various path formats (/memories/, /.memory/, etc) and validates that only known MEMORY_FILES are accessed (core.md, notes.md, conversations.jsonl).

Command guard AST-based validation for bash tool

The AST-based command guard parses the bash command using just-bash parse() and walks every node (including pipelines, subshells, loops, conditionals) via collectCommandNames(). It rejects any command not in the approvedCommands set. If a command name is dynamically constructed (e.g., via variable expansion), extractLiteralWord() returns null and the guard skips the allowlist check for that command. Since just-bash is a JavaScript-based interpreter (not a real shell), dynamically constructed commands that bypass the allowlist fail to resolve to real binaries. The approved commands for memory operations are: cat, echo, grep, jq, ls, mkdir, perl, sed, tail.

Dependencies for custom memory tool implementation

Install with 'pnpm add ai just-bash zod'. The AI SDK provides ToolLoopAgent and tool. Zod provides tool input schemas. just-bash provides the JavaScript-based bash interpreter, AST parsing for command validation, and ReadWriteFs filesystem abstraction. If using only Route A (structured actions), just-bash is optional.

Implementing an agent with Llama 3.1 using stopWhen

To build an agent that executes multiple tool calls in sequence, use generateText with the stopWhen parameter and isStepCount function. This allows the model to make multiple decisions and tool calls in a single interaction. Example: stopWhen: isStepCount(5) stops the agent after 5 step iterations. The agent can use tools multiple times while reasoning through a problem step by step.

Agent Context Compaction guide

The AI SDK provides a guide on how to compact agent context by mutating message state between steps using prepareStep.

Access token usage in prepareStep for context management

Implement prepareStep callback that receives { steps, context }. Access the last step's usage via steps.at(-1)?.usage?.inputTokens or fall back to context.lastInputTokens from the previous request. This allows implementing context compaction strategies when approaching token limits.

prepareStep receives steps with usage data

The prepareStep callback receives an object with steps array containing all previous steps from the current run, where each step has usage property with inputTokens. On the first step of a new request, steps is empty.

Runtime context in ToolLoopAgent

Use runtimeContext as the agent's shared runtime state. It flows through the agent loop and is available in prepareStep, lifecycle callbacks, and final results. Pass runtimeContext to agent.generate() call with { runtimeContext: { key: value } }.

Three approaches to agent memory

Memory can be added to agents in three ways: Provider-Defined Tools (low effort, medium flexibility, yes provider lock-in), Memory Providers (low effort, low flexibility, depends on provider), and Custom Tool (high effort, high flexibility, no provider lock-in).

Anthropic Memory Tool structure

The Anthropic Memory Tool (memory_20250818) gives Claude structured commands for managing a /memories directory. It receives structured commands (view, create, str_replace, insert, delete, rename), each with a path scoped to /memories. The execute function receives an action containing command, path, and other fields depending on the command, and should return the result as a string. Your execute function maps these to a storage backend (filesystem, database, or other persistence layer).

Anthropic Memory Tool example

import { anthropic } from '@ai-sdk/anthropic'; import { ToolLoopAgent } from 'ai'; const memory = anthropic.tools.memory_20250818({ execute: async action => { // `action` contains `command`, `path`, and other fields // depending on the command (view, create, str_replace, // insert, delete, rename). // Implement your storage backend here. // Return the result as a string. }, }); const agent = new ToolLoopAgent({ model: 'anthropic/claude-haiku-4.5', tools: { memory }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Letta memory provider setup

Letta provides agents with persistent long-term memory. Create an agent on Letta's platform (cloud or self-hosted), configure memory there, then use the AI SDK provider. Letta's agent runtime handles memory management including core memory, archival memory, and recall. Install with: pnpm add @letta-ai/vercel-ai-sdk-provider

Letta with ToolLoopAgent example

import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: lettaCloud(), providerOptions: { letta: { agent: { id: 'your-agent-id' }, }, }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Letta built-in memory tools

Letta provides built-in memory tools that can be used alongside custom tools. Tools include core_memory_append, memory_insert, and memory_replace. Access them via lettaCloud.tool('tool-name') and pass them in the tools object when creating a ToolLoopAgent.

Mem0 memory provider setup

Mem0 adds a memory layer on top of any supported LLM provider. It automatically extracts memories from conversations, stores them, and retrieves relevant ones for future prompts. Works with OpenAI, Anthropic, Google, Groq, and Cohere. Install with: pnpm add @mem0/vercel-ai-provider

Mem0 with ToolLoopAgent example

import { createMem0 } from '@mem0/vercel-ai-provider'; import { ToolLoopAgent } from 'ai'; const mem0 = createMem0({ provider: 'openai', mem0ApiKey: process.env.MEM0_API_KEY, apiKey: process.env.OPENAI_API_KEY, }); const agent = new ToolLoopAgent({ model: mem0('gpt-4.1', { user_id: 'user-123' }), }); const { text } = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Mem0 explicit memory management

Mem0 provides functions for explicit memory management: addMemories(messages, { user_id: 'user-123' }) to add memories and retrieveMemories(prompt, { user_id: 'user-123' }) to retrieve relevant memories for a prompt.

Supermemory persistent memory platform

Supermemory is a long-term memory platform that adds persistent, self-growing memory to AI applications. It provides tools that handle saving and retrieving memories automatically through semantic search. Works with any AI SDK provider. Install with: pnpm add @supermemory/tools

Supermemory tools example

import { supermemoryTools } from '@supermemory/tools/ai-sdk'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: __MODEL__, tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!), }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Supermemory memory operations

Supermemory tools give the model addMemory and searchMemories operations that handle storage and retrieval automatically.

Hindsight persistent memory tools

Hindsight provides agents with persistent memory through five tools: retain, recall, reflect, getMentalModel, and getDocument. Can be self-hosted with Docker or used as a cloud service. Install with: pnpm add @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client

Hindsight setup example

import { HindsightClient } from '@vectorize-io/hindsight-client'; import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; import { ToolLoopAgent } from 'ai'; import { openai } from '@ai-sdk/openai'; const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL }); const agent = new ToolLoopAgent({ model: __MODEL__, tools: createHindsightTools({ client, bankId: 'user-123' }), instructions: 'You are a helpful assistant with long-term memory.', }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Hindsight bankId for multi-user apps

The bankId parameter identifies the memory store and is typically a user ID. In multi-user apps, call createHindsightTools inside the request handler so each request gets the right bank.

MongoDB persistent memory with AI SDK

The @mongodb-developer/vercel-ai-memory package provides MongoDB Atlas-backed persistent memory with five structured tiers: Session, Semantic, Procedural, Episodic, and Scratchpad. Retrieval uses Atlas Vector Search with any AI SDK embedding model, with automatic index creation and per-type retention policies. Targets AI SDK v6 and requires ai ^6.0.0, mongodb ^6.0.0, and zod ^3.0.0. Install with: pnpm add @mongodb-developer/vercel-ai-memory

MongoDB memory setup example

import { createMongoDBMemory } from '@mongodb-developer/vercel-ai-memory'; import { openai } from '@ai-sdk/openai'; import { ToolLoopAgent, isLoopFinished } from 'ai'; // Create the memory instance once at module/server level const mongodbMemory = createMongoDBMemory({ uri: process.env.MONGODB_URI!, embedder: openai.embedding('text-embedding-3-small'), }); // Scope to a user and session per request const agent = new ToolLoopAgent({ model: openai('gpt-4.1'), tools: mongodbMemory({ userId: 'alice', sessionId: 'sess-001' }), stopWhen: isLoopFinished(), }); const result = await agent.generate({ prompt: 'My name is Alice and I love hiking. Remember that.', });

isLoopFinished() for memory persistence

isLoopFinished() lets the agent keep running until the tool loop naturally finishes, which is useful when memory tools need to read and write before the final response.

MongoDB memory modes: tool-driven vs hook-driven

Session memory supports two modes: tool-driven (the LLM decides when to read/write — good for prototypes) and hook-driven (the runtime persists every turn via prepareCall and onEnd hooks — recommended for production). The other memory tiers (semantic, procedural, episodic, scratchpad) are always LLM-controlled and selective by design.

Custom memory tool patterns

Two common patterns for custom memory tools: Structured actions where you define explicit operations (view, create, update, search) and handle structured input yourself (safe by design since you control every operation), and Bash-backed where you give the model a sandboxed bash environment to compose shell commands (cat, grep, sed, echo) for flexible memory access (more powerful but requires command validation for safety).

Memory providers tradeoff

Memory providers are a good fit when you want memory without building storage infrastructure. The tradeoff is that the provider controls memory behavior, so you have less visibility into what gets stored and how it is retrieved. You also take on a dependency on an external service.

Agent memory persistence across runs

Without memory, every conversation starts fresh. With memory, an agent builds context over time, recalls previous interactions, and adapts to the user.

Give your agent this brain