Working Memory
Working memory stores compact context across conversation turns. Unlike full message history, it tracks key facts, preferences, or goals that persist throughout interactions.
Configuration
Three formats supported:
- Markdown template - Structured text with sections
- JSON schema - Validated structured data using Zod
- Free-form - Unstructured text
Scope options:
conversation
(default) - Context per conversation threaduser
- Context shared across all user conversations
Markdown Template
import { Agent, Memory } from "@voltagent/core";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
import { openai } from "@ai-sdk/openai";
const memory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
workingMemory: {
enabled: true,
scope: "conversation", // default
template: `
# User Profile
- Name:
- Role:
- Timezone:
# Current Goals
-
# Preferences
-
`,
},
});
const agent = new Agent({
name: "Assistant",
model: openai("gpt-4o-mini"),
memory,
});
JSON Schema
import { z } from "zod";
const memory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
workingMemory: {
enabled: true,
scope: "user", // persist across conversations
schema: z.object({
profile: z
.object({
name: z.string().optional(),
role: z.string().optional(),
timezone: z.string().optional(),
})
.optional(),
preferences: z.array(z.string()).optional(),
goals: z.array(z.string()).optional(),
}),
},
});
Free-Form
const memory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
workingMemory: {
enabled: true, // no template or schema
},
});
Agent Integration
When working memory is enabled, the agent:
- Adds instructions to system prompt - Includes current working memory content and usage guidelines
- Exposes tools automatically:
get_working_memory()
- Retrieve current contentupdate_working_memory(content)
- Update content (validated against schema if configured)clear_working_memory()
- Clear content
The agent manages working memory proactively based on conversation flow.
Update Modes
Two modes available:
Append Mode (Default)
Safely merges new information with existing content:
// Existing working memory:
// { profile: { name: "Alice" }, goals: ["Learn TypeScript"] }
// Agent calls update_working_memory with:
// { goals: ["Build an API"] }
// Result (merged):
// { profile: { name: "Alice" }, goals: ["Learn TypeScript", "Build an API"] }
For JSON schemas:
- Objects: Deep merge
- Arrays: Deduplicated concatenation
- Primitives: Overwrite
For Markdown:
- New content appended with separator
Replace Mode
Overwrites all existing content:
await memory.updateWorkingMemory({
conversationId: "thread-123",
userId: "user-456",
content: { profile: { name: "Bob" } }, // existing data lost
options: { mode: "replace" },
});
Use replace mode only when intentionally resetting all context.
Programmatic API
Direct memory access without agent tools:
// Get current working memory
const content = await memory.getWorkingMemory({
conversationId: "thread-123",
userId: "user-456",
});
console.log(content); // string (JSON or Markdown)
// Update (default: append mode)
await memory.updateWorkingMemory({
conversationId: "thread-123",
userId: "user-456",
content: { goals: ["Complete onboarding"] }, // object or string
});
// Update (replace mode)
await memory.updateWorkingMemory({
conversationId: "thread-123",
userId: "user-456",
content: "Fresh context",
options: { mode: "replace" },
});
// Clear
await memory.clearWorkingMemory({
conversationId: "thread-123",
userId: "user-456",
});
// Introspect configuration
const format = memory.getWorkingMemoryFormat(); // "markdown" | "json" | null
const template = memory.getWorkingMemoryTemplate(); // string | null
const schema = memory.getWorkingMemorySchema(); // ZodObject | null
Storage Implementation
Working memory is stored differently per scope:
Conversation scope:
- Stored in
conversations.metadata.workingMemory
field - Isolated per conversation thread
User scope:
- Stored in
${tablePrefix}_users.metadata.workingMemory
field (adapter-specific) - Shared across all user conversations
All official adapters (LibSQL, Postgres, Supabase, Managed Memory) support both scopes.
Example: User-Scoped Preferences
import { Agent, Memory } from "@voltagent/core";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const userPreferencesSchema = z.object({
name: z.string().optional(),
timezone: z.string().optional(),
communicationStyle: z.enum(["formal", "casual"]).optional(),
interests: z.array(z.string()).optional(),
});
const memory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
workingMemory: {
enabled: true,
scope: "user", // persist across all conversations
schema: userPreferencesSchema,
},
});
const agent = new Agent({
name: "Personal Assistant",
instructions: "Adapt responses based on user preferences stored in working memory.",
model: openai("gpt-4o-mini"),
memory,
});
// First conversation
await agent.generateText("I prefer casual communication and I'm into AI and music.", {
userId: "user-123",
conversationId: "conv-1",
});
// Different conversation - agent remembers user preferences
await agent.generateText("What should I learn next?", {
userId: "user-123",
conversationId: "conv-2", // different thread
});
Example: Conversation-Scoped Goals
const projectMemory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
workingMemory: {
enabled: true,
scope: "conversation", // isolated per project
template: `
# Project Context
- Name:
- Deadline:
- Tech Stack:
# Current Sprint
- Goals:
- Blockers:
`,
},
});
const agent = new Agent({
name: "Project Manager",
instructions: "Track project context and help with sprint planning.",
model: openai("gpt-4o-mini"),
memory: projectMemory,
});
// Each project gets its own working memory
await agent.generateText("Let's plan the e-commerce project using Next.js.", {
userId: "user-123",
conversationId: "project-ecommerce",
});
await agent.generateText("For the analytics dashboard, we'll use React and D3.", {
userId: "user-123",
conversationId: "project-analytics",
});
Best Practices
- Use JSON schemas for structured data - Ensures type safety and validation
- Use Markdown templates for narrative context - Better for summaries, notes, observations
- Default to append mode - Safer than replace; preserves existing data
- User scope for preferences - Name, timezone, communication style
- Conversation scope for session data - Goals, tasks, project details
- Keep it compact - Working memory supplements message history, not replaces it
Learn More
- Semantic Search - Retrieve relevant past messages
- Managed Memory - Zero-setup working memory storage
- LibSQL / Turso - Self-hosted working memory storage