Agent Memory Middleware
Persistent memory middleware for AI SDK language models
Agent memory gives your language model persistent, long-term memory across conversations. The model can remember user preferences, project context, past decisions, and learned patterns - all stored in simple markdown files.
Overview
The agent memory system uses a two-tier architecture:
- User-level memory: Personal agent configuration at
~/.deepagents/{agentId}/agent.md - Project-level memory: Shared project context at
[git-root]/.deepagents/agent.md
Memory content is injected into the model's system prompt on every call, and the model can read/update memory files using filesystem tools.
Quick Start
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
// Create memory middleware
const memoryMiddleware = createAgentMemoryMiddleware({
agentId: 'my-coding-assistant',
});
// Create agent with memory
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: memoryMiddleware,
});
// Memory is automatically loaded and injected into system prompt
const result = await agent.generate({
prompt: 'Help me write a TypeScript function',
});Memory File Locations
User-level Memory
Path: ~/.deepagents/{agentId}/agent.md
Purpose: Personal agent configuration, preferences, and cross-project patterns
Auto-created: Yes (directory created automatically if it doesn't exist)
Example content:
# My Coding Assistant
## Personality
I am a helpful TypeScript expert who values code quality and clarity.
## User Preferences
- Prefers 2-space indentation
- Likes comprehensive JSDoc comments
- Wants error handling for all async operations
## Working Style
- Ask clarifying questions before implementing
- Provide examples alongside explanations
- Consider edge cases and security implicationsProject-level Memory
Path: [git-root]/.deepagents/agent.md
Purpose: Project-specific context, conventions, and architecture decisions
Auto-created: Only with user approval (see requestProjectApproval)
Example content:
# Project Context
## Architecture
This is a TypeScript library using Vercel AI SDK v6.
## Conventions
- Use snake_case for tool names
- All exports must have JSDoc comments
- Tests use bun:test framework
## Important Files
- src/agent.ts - Main agent implementation
- src/tools/ - Tool implementations
- docs/ - User documentationAdditional Context Files
Place additional .md files in ~/.deepagents/{agentId}/ for specialized context:
decisions.md- Architecture decision recordsarchitecture.md- System design notesconventions.md- Coding standardspatterns.md- Reusable patterns and recipes
These files are automatically loaded and presented under "Additional Context Files" in the system prompt.
Memory Middleware Options
AgentMemoryOptions
interface AgentMemoryOptions {
/**
* Unique identifier for the agent.
* Used to locate memory at ~/.deepagents/{agentId}/agent.md
*/
agentId: string;
/**
* Optional working directory for project detection.
* Defaults to process.cwd()
*/
workingDirectory?: string;
/**
* Optional callback for project directory approval.
* Called before creating .deepagents/ in project root.
*/
requestProjectApproval?: (projectPath: string) => Promise<boolean>;
}Project Directory Approval
By default, the middleware silently skips project-level memory if .deepagents/ doesn't exist. To request user approval before creating the directory:
const memoryMiddleware = createAgentMemoryMiddleware({
agentId: 'my-agent',
requestProjectApproval: async (projectPath) => {
// Show prompt to user
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await new Promise<string>((resolve) => {
readline.question(
`Create .deepagents/ in ${projectPath}? (y/n) `,
resolve
);
});
readline.close();
return answer.toLowerCase() === 'y';
},
});How Memory Works
Loading Process
- On middleware creation: Memory middleware is created with closure-cached state
- On first model call: Memory files are loaded and cached
- On subsequent calls: Cached memory is reused (files not re-read)
This means:
- ✅ Memory is loaded once per agent instance (performance)
- ❌ File changes during runtime won't be reflected (restart required)
Memory Injection
Memory is injected into the system prompt using AI SDK v6's transformParams hook:
{
specificationVersion: 'v3',
transformParams: async ({ params }) => {
// Load memory (once, cached)
// Enhance system message with memory content
return { ...params, prompt: enhancedPrompt };
},
}When you create a DeepAgent with middleware, the model is wrapped before being passed to ToolLoopAgent. ToolLoopAgent internally converts its instructions parameter to system messages in the params.prompt array, which the middleware then enhances with memory content.
System Prompt Template
The injected memory section includes:
- Memory content (user + project + additional files)
- File paths for each memory source
- Instructions on how to use memory:
- When to read memory files
- When to update memory
- How to organize memory content
- Example use cases
Agent Self-Update
The agent can read and modify its own memory using filesystem tools:
Reading Memory
// Agent uses read_file tool
await agent.generate({
prompt: "What do you know about my preferences?",
});
// Agent reads ~/.deepagents/{agentId}/agent.md
// Responds with memory contentUpdating Memory
// User requests memory update
await agent.generate({
prompt: "Remember that I prefer functional programming style",
});
// Agent workflow:
// 1. read_file(~/.deepagents/{agentId}/agent.md)
// 2. edit_file to add the preference
// 3. Confirm: "I've updated my memory to remember your preference"Best Practices for Memory Updates
The system prompt teaches the agent:
When to update memory:
- User explicitly asks to remember something
- Discover recurring preferences or patterns
- Learn project-specific conventions
When NOT to update memory:
- Temporary task information (use todos instead)
- Information already in codebase
- Obvious facts from documentation
How to organize memory:
- Use markdown headings for structure
- Keep entries concise and relevant
- Remove outdated information
- Group related items together
Integration Examples
Basic Usage
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
const memoryMiddleware = createAgentMemoryMiddleware({
agentId: 'research-agent',
});
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: memoryMiddleware,
});
const result = await agent.generate({
prompt: 'Help me research TypeScript patterns',
});Combining Multiple Middleware
You can combine memory middleware with other middleware:
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
const loggingMiddleware = {
specificationVersion: 'v3',
wrapGenerate: async ({ doGenerate }) => {
console.log('Model called');
return await doGenerate();
},
};
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: [
loggingMiddleware,
createAgentMemoryMiddleware({ agentId: 'my-agent' }),
],
});
const result = await agent.generate({
prompt: 'Hello',
});How It Works
The agent memory middleware integrates seamlessly with createDeepAgent through AI SDK v6's middleware architecture:
- Model Wrapping: When you pass
middlewaretocreateDeepAgent, the model is wrapped usingwrapLanguageModelbefore being passed to ToolLoopAgent - Instructions Conversion: ToolLoopAgent internally converts its
instructionsparameter to a system message in theparams.promptarray - Middleware Interception: The middleware's
transformParamshook intercepts the call and sees the completeparams.promptarray with the system message - Memory Injection: The middleware finds the system message and appends memory content to it
- Model Execution: The enhanced prompt (with memory) is passed to the underlying model
This architecture ensures transparent memory injection without any manual configuration.
Memory File Format
Memory files are plain markdown - no YAML frontmatter or special structure required.
Recommended Structure
# Agent Name
## Personality
[Core identity and role]
## User Preferences
- Preference 1
- Preference 2
## Working Style
[How the agent should approach tasks]
## Learned Patterns
[Discovered patterns from past interactions]
## Important Context
[Project-specific or recurring context]Anti-patterns
❌ Don't store temporary task tracking:
## Current Tasks
- [ ] Implement feature X
- [ ] Fix bug YUse the write_todos tool instead.
❌ Don't duplicate codebase information:
## File Structure
src/
agent.ts
tools/The agent can use ls and glob tools.
❌ Don't store obvious facts:
## Facts
- TypeScript is a typed superset of JavaScript
- React uses JSX syntax✅ Do store preferences and patterns:
## Code Style
- Use functional components over class components
- Prefer async/await over Promise chains
- Extract reusable logic into custom hooksProject Detection
The middleware uses findGitRoot() to locate the project root by walking up the directory tree looking for a .git directory. This means:
- ✅ Works in monorepos (finds root
.git) - ✅ Works in subdirectories
- ❌ Won't find project-level memory outside git repos
If you're not using git, project-level memory won't be loaded (user-level memory still works).
Migration from skillsDir
If you were using skillsDir for skills:
// Old way (deprecated)
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
skillsDir: './skills',
});
// New way (recommended)
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-agent',
// Skills now loaded from:
// - ~/.deepagents/my-agent/skills/
// - .deepagents/skills/ (if in git repo)
});Migration steps:
- Create
~/.deepagents/{agentId}/skills/directory - Move skill directories from old location to new location
- Update agent creation to use
agentId - Remove
skillsDirparameter
Backwards compatibility: The skillsDir parameter still works but shows a deprecation warning.
Troubleshooting
Memory not loading
Check:
- File exists at correct path (
~/.deepagents/{agentId}/agent.md) - File has read permissions
- Working directory is correct (for project memory)
Debug:
const middleware = createAgentMemoryMiddleware({
agentId: 'my-agent',
workingDirectory: process.cwd(), // Explicitly set
});Memory not updating after changes
Cause: Memory is cached on first load
Solution: Restart the agent instance
// Create new agent to reload memory
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-agent',
});Project memory not loading
Check:
- You're in a git repository
.deepagents/agent.mdexists- File has read permissions
Debug:
import { findGitRoot } from 'ai-sdk-deep-agent';
const gitRoot = await findGitRoot();
console.log('Git root:', gitRoot);
// Expected: /path/to/repo
// If null: Not in a git repositoryPermission errors creating directories
Cause: No write permissions in home directory or project root
Solution: Check directory permissions or disable auto-creation:
const memoryMiddleware = createAgentMemoryMiddleware({
agentId: 'my-agent',
requestProjectApproval: async () => false, // Don't create project dir
});API Reference
createAgentMemoryMiddleware(options)
Creates a memory middleware instance for AI SDK v6.
Parameters:
options.agentId: string- Unique identifier for the agent (e.g., "code-architect", "research-agent"). Used to locate agent-specific memory at~/.deepagents/{agentId}/agent.mdoptions.workingDirectory?: string- Optional working directory for project-level memory detection. Defaults toprocess.cwd()options.userDeepagentsDir?: string- Optional custom path for user-level.deepagentsdirectory. Defaults toos.homedir() + '/.deepagents'options.requestProjectApproval?: (projectPath: string) => Promise<boolean>- Optional callback to request user approval for creating project-level.deepagents/directory
Returns: LanguageModelMiddleware - Middleware instance compatible with AI SDK v6 (specification version: 'v3')
Example:
const middleware = createAgentMemoryMiddleware({
agentId: 'my-agent',
workingDirectory: '/path/to/project',
userDeepagentsDir: '/custom/path/.deepagents',
requestProjectApproval: async (path) => {
console.log(`Create .deepagents/ in ${path}?`);
return true; // User approves
},
});findGitRoot(startPath?)
Finds the git repository root by walking up the directory tree.
Parameters:
startPath?: string- Starting directory (defaults toprocess.cwd())
Returns: Promise<string | null> - Absolute path to git root, or null if not found
Example:
import { findGitRoot } from 'ai-sdk-deep-agent';
const gitRoot = await findGitRoot();
if (gitRoot) {
console.log('Project root:', gitRoot);
} else {
console.log('Not in a git repository');
}Related Documentation
- Middleware Architecture - How middleware works in DeepAgent
- Skills System - Loading and using skills
- Examples - Working code examples
- API Reference - Full API documentation
FAQ
Q: Does the memory middleware work with DeepAgent?
A: Yes! Simply pass the middleware to the middleware parameter:
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
const memoryMiddleware = createAgentMemoryMiddleware({ agentId: 'my-agent' });
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: memoryMiddleware,
});The middleware automatically injects memory into the agent's system prompt on every call. See the How It Works section for technical details.
Q: Can I use multiple agentIds in the same application?
A: Yes! You can create multiple agents with different memory configurations:
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
const researchAgent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: createAgentMemoryMiddleware({ agentId: 'research-agent' }),
});
const codingAgent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: createAgentMemoryMiddleware({ agentId: 'coding-agent' }),
});Each agent will have its own independent memory at ~/.deepagents/{agentId}/agent.md.
Q: Can I manually edit memory files?
A: Yes! Memory files are plain markdown. Edit them with any text editor. Changes take effect on next agent restart (middleware caches memory on first load).
Q: Does memory persist across agent restarts?
A: Yes! Memory files are stored on disk and persist across restarts, unlike checkpointer which is in-memory by default.
Q: Can I use memory without skills?
A: Yes! Memory and skills are independent features. The agentId parameter controls skills loading, while middleware controls memory:
import { createDeepAgent, createAgentMemoryMiddleware } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
// Memory only (no agentId, so no skills loaded)
const memoryOnlyAgent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: createAgentMemoryMiddleware({ agentId: 'my-agent' }),
});
// Skills only (agentId without middleware)
const skillsOnlyAgent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-agent', // Loads skills from ~/.deepagents/my-agent/skills/
});
// Both memory and skills
const fullAgent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-agent', // Loads skills
middleware: createAgentMemoryMiddleware({ agentId: 'my-agent' }), // Loads memory
});Q: Is memory secure?
A: Memory files are stored in your home directory with default file permissions. Do not store sensitive credentials or secrets in memory files. Use environment variables for secrets.
Q: Can I use a custom directory instead of ~/.deepagents/?
A: Yes! Use the userDeepagentsDir option:
const middleware = createAgentMemoryMiddleware({
agentId: 'my-agent',
userDeepagentsDir: '/custom/path/.deepagents',
// Memory will be loaded from: /custom/path/.deepagents/my-agent/agent.md
});This is useful for testing or custom deployment environments. Alternatively, you can symlink ~/.deepagents/ to your preferred location.