Long-Term Memory for Agents
The persistence problem
Agent memory has two timescales:
Session memory (seconds to minutes): conversation context, intermediate states. Fine if lost.
Long-term memory (hours to years): user preferences, learned behavior, decisions. Must survive restarts, crashes, and agent updates.
Most teams conflate them and use the same storage. This is a mistake.
Storage tiers
Tier 1: Session (in-memory, non-durable)
Use for: Conversation context, intermediate reasoning, temporary state.
const sessionMemory = new Map<string, Memory>();
// or
const sessionMemory = new Brain({ storage: "memory" }); // auto-cleared on exitDurability: None. Lost on restart. Latency: < 1ms. Cost: Free (RAM). Cardinality: 100–1K facts per session.
Tier 2: Mid-term (local persistent, single-node)
Use for: Recent decisions, conversation summaries, learned preferences (days).
const midTermMemory = new Brain({
endpoint: "http://localhost:9090", // self-hosted node, durable to disk (arena + WAL + redb)
ttl: 7 * 24 * 60 * 60, // 7 days
});Durability: Survives restart (disk-backed). Lost if disk fails. Latency: 5–50ms (local disk I/O). Cost: Disk space (~1MB per 100K facts). Cardinality: 1K–100K facts.
Tier 3: Long-term (managed persistent, replicated)
Use for: User profiles, historical facts, policies (months/years).
const longTermMemory = new Brain({
apiKey: process.env.BRAIN_API_KEY,
region: "us-east-1",
replication: "multi-region", // survives data center failure
});Durability: Survives any single failure (replicated, backed up). SLA-guaranteed. Latency: 50–200ms P99 (network + DB). Cost: $0.001 per 1K vectors/month (managed). Cardinality: 100K–10M facts per user.
Multi-tier architecture
Most production agents use all three:
// Session: conversation in this request
const session = new Brain({ storage: "memory" });
// Mid-term: recent decisions + learned preferences (7 days)
const midTerm = new Brain({ endpoint: "http://localhost:9090", ttl: 7 * 24 * 60 * 60 });
// Long-term: user profile + historical facts (permanent)
const longTerm = new Brain({
apiKey: process.env.BRAIN_API_KEY,
region: "us-east-1",
});
// On each turn:
async function agentLoop(input) {
// Read from all tiers
const sessionContext = await session.retrieve(input);
const recentDecisions = await midTerm.retrieve(input);
const userProfile = await longTerm.retrieve(input);
// Combine
const fullContext = [sessionContext, recentDecisions, userProfile];
// Decide and store
const decision = await llm.complete(fullContext);
// Write to appropriate tiers
await session.store({ content: decision, type: "decision" });
if (important) {
await midTerm.store({ content: decision, type: "decision" });
await longTerm.store({ content: decision, type: "insight" });
}
}Eviction policies
Memory grows. You need a policy for what to forget.
LRU (Least Recently Used)
Forget what you haven't accessed in N days.
Best for: User-scoped memory (chat history, preferences). Tradeoff: Might forget important but rarely-accessed facts.
{
eviction_policy: "lru",
window_days: 30,
// Keep facts accessed in last 30 days
}TTL (Time To Live)
Forget specific memories after a fixed time.
Best for: Transient facts (conversation state, temporary preferences). Tradeoff: No automatic cleanup for long-lived memories.
{
type: "conversation_turn",
ttl: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day
}Priority-based
Forget low-confidence or low-impact memories first.
Best for: Mixed-importance memory (facts + events + preferences). Tradeoff: Requires predicting what's important.
{
eviction_order: "confidence_desc", // Forget low-confidence first
// or
eviction_order: "type", // Forget events before facts
}Merge + compress
Instead of evicting, compress old memories.
Best for: Long conversations (summarize past turns). Tradeoff: Lossy; summary might miss nuance.
// After 7 days, summarize conversation
const oldTurns = await memory.query({
type: "conversation_turn",
timestamp: { before: 7 days ago },
});
const summary = await llm.summarize(oldTurns);
await memory.store({
type: "conversation_summary",
period: "week_1",
content: summary,
});
// Delete old turns
await memory.delete({ type: "conversation_turn", timestamp: { before: 7 days ago } });Durability guarantees
Write-ahead logging (WAL)
Every write is logged before it's applied. Survives crashes.
How: Write to log, then to memory. On restart, replay log.
Agent: "Remember: user is in SF"
→ Write to WAL: {memory_id: 123, content: "user in SF"}
→ Confirm write
→ Apply to memory store
→ Delete from WALWhat Brain does: the self-hosted server (local; arena + WAL + redb) and managed Cloud both use group-commit WAL fsync.
Replication
Every write is replicated to N replicas before confirming.
How: Write to primary, replicate to 2+ secondaries, confirm when N replicas ack.
Agent: "Remember: user is in SF"
→ Write to primary
→ Replicate to secondary-1 (ack)
→ Replicate to secondary-2 (ack)
→ Confirm to agent
→ If primary fails, secondaries elect new primaryBrain Cloud: 3-way replication within region, async replication across regions.
Backups
Regular snapshots of memory state, kept for disaster recovery.
Frequency: Hourly for active agents, daily for inactive. Retention: 30 days minimum (longer for compliance).
Brain Cloud: Automatic daily backups, configurable retention.
Retrieval patterns for long-term memory
Pattern 1: User profile (first-access warm-up)
On agent startup, load the user's profile facts (most important facts about them).
async function loadUserProfile(userId: string) {
const profile = await longTermMemory.retrieve({
query: "What is the user's profile?",
filter: { user_id: userId, type: "fact", category: "profile" },
topK: 20,
});
return profile; // Fast lookup, cached
}Pattern 2: Decision history (context for reasoning)
Before deciding, retrieve past decisions on similar topics.
async function getDecisionHistory(topic: string, userId: string) {
const history = await midTermMemory.retrieve({
query: `What did we decide about ${topic}?`,
filter: { user_id: userId, type: "decision" },
topK: 5,
});
return history;
}Pattern 3: Temporal context (recent events first)
Get recent events, then fall back to older facts.
async function getFullContext(userId: string, query: string) {
// Recent (last 3 days)
const recent = await midTermMemory.retrieve({
query,
filter: { user_id: userId, timestamp: { after: 3 days ago } },
topK: 10,
});
// Historical (if recent is sparse)
if (recent.length < 5) {
const historical = await longTermMemory.retrieve({
query,
filter: { user_id: userId },
topK: 10 - recent.length,
});
return [...recent, ...historical];
}
return recent;
}Pattern 4: Graph traversal (relationships)
For entity-based memory, traverse relationships.
async function getCompanyContext(companyName: string) {
// Find company entity
const company = await longTermMemory.retrieve({
query: companyName,
filter: { entity_type: "company" },
topK: 1,
});
if (!company) return null;
// Find employees (traverse relationship)
const employees = await longTermMemory.retrieve({
query: "employees of " + companyName,
filter: {
relation_type: "works_at",
object_id: company.entity_id,
},
});
return { company, employees };
}Cost optimization
Long-term memory can get expensive. Optimize:
1. Compression
Summarize old memories. Trade precision for cost.
// Daily: compress events from past 7 days into 1 summary
const oldEvents = await memory.query({
type: "event",
timestamp: { before: "7 days ago" },
});
const compressed = await llm.compress(oldEvents);
await memory.store(compressed); // 1 memory instead of 1002. Sampling
Keep every Nth memory, not all.
// Keep 1 in 10 conversation turns
const sampling_rate = 0.1;
if (Math.random() < sampling_rate) {
await longTermMemory.store(turn);
}3. Selective indexing
Don't index low-value fields.
// Index only high-cardinality, frequently-queried fields
{
index: ["user_id", "type", "timestamp"],
store_only: ["content", "metadata"],
}4. Tiering by value
Cold data goes to cheaper storage.
// Recent (7 days): fast, expensive
// Archive (> 7 days): slow, cheap
if (timestamp.getTime() < Date.now() - 7 * 24 * 60 * 60 * 1000) {
await archiveStorage.store(memory); // S3, cheaper
} else {
await hotStorage.store(memory); // SSD, faster
}Checklist: Before shipping long-term memory
- You have a multi-tier strategy (session/mid-term/long-term)
- Durability is guaranteed (replication or backup)
- Eviction policy is defined and tested
- You can reconstruct recent context from logs if needed
- Cost is predictable and monitored
- Retrieval latency is acceptable (< 200ms P99 for long-term)
- You have a backup restoration plan
- Data is encrypted at rest and in transit
Further reading
- Memory Architectures — System design
- Schema Design Patterns — Durable schemas
- Cheat Sheet — Deployment patterns