Skip to main content

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:

  1. Markdown template - Structured text with sections
  2. JSON schema - Validated structured data using Zod
  3. Free-form - Unstructured text

Scope options:

  • conversation (default) - Context per conversation thread
  • user - 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:

  1. Adds instructions to system prompt - Includes current working memory content and usage guidelines
  2. Exposes tools automatically:
    • get_working_memory() - Retrieve current content
    • update_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

  1. Use JSON schemas for structured data - Ensures type safety and validation
  2. Use Markdown templates for narrative context - Better for summaries, notes, observations
  3. Default to append mode - Safer than replace; preserves existing data
  4. User scope for preferences - Name, timezone, communication style
  5. Conversation scope for session data - Goals, tasks, project details
  6. Keep it compact - Working memory supplements message history, not replaces it

Learn More

Table of Contents