SmartMemory
SmartMemory provides four types of memory for AI agents: working memory for active sessions, episodic memory for completed sessions, semantic memory for knowledge storage, and procedural memory for skills and templates. This system lets AI agents remember conversations, learn from experience, and build up knowledge over time.
The memory system works through isolated sessions that contain conversation context and can be organized into timelines. When sessions complete, they move to episodic memory where they become searchable history. Semantic memory stores documents and facts that persist across all sessions, while procedural memory holds reusable templates and procedures.
Prerequisites
- Raindrop framework installed in your project
- Understanding of AI agent architecture and memory systems
- Familiarity with TypeScript and async/await patterns
Configuration
Define SmartMemory in your application manifest:
application "demo-smartmemory" { smartmemory "memory" {}}
Basic Usage
Access SmartMemory through environment variables:
// Access the SmartMemory instanceconst smartMemory = env.MEMORY;
// Start a new working memory sessionconst { sessionId, workingMemory } = await smartMemory.startWorkingMemorySession();
// Store a memoryconst memoryId = await workingMemory.putMemory({ content: "User prefers morning meetings", timeline: "preferences"});
// Search memoriesconst results = await workingMemory.searchMemory({ terms: "meeting schedule"});
Core Interfaces
MemoryEntry
Represents a stored memory with metadata:
interface MemoryEntry { id: string; // Unique memory identifier in: string; // Session ID containing this memory timeline: string; // Timeline organization by: string; // Creator identifier dueTo: string; // Causal relationship content: string; // The actual memory content at: Date; // Creation timestamp key?: string; // Optional metadata key agent?: string; // Optional agent identifier}
NewMemoryEntry
Input for creating new memories:
interface NewMemoryEntry { content: string; // Required: memory content timeline?: string; // Optional: defaults to "*defaultTimeline" key?: string; // Optional: metadata key for filtering agent?: string; // Optional: agent identifier sessionId?: string; // Optional: explicit session override at?: Date; // Optional: custom timestamp}
WorkingMemoryQuery
Query interface for exact memory retrieval:
interface WorkingMemoryQuery { timeline?: string; // Filter by timeline key?: string; // Filter by metadata key nMostRecent?: number; // Limit results to N most recent startTime?: Date; // Time range start endTime?: Date; // Time range end}
WorkingMemorySearchQuery
Search interface for semantic memory search:
interface WorkingMemorySearchQuery { timeline?: string; // Optional timeline filter terms: string; // Required search terms nMostRecent?: number; // Optional result limit startTime?: Date; // Optional time range start endTime?: Date; // Optional time range end}
ProcedureEntry
Represents stored procedures and templates:
interface ProcedureEntry { key: string; // Unique procedure identifier value: string; // Procedure content or template createdAt: Date; // Creation timestamp updatedAt: Date; // Last modification timestamp}
Working Memory
Working memory handles active conversation sessions with timeline organization and search capabilities.
startWorkingMemorySession()
Creates a new isolated memory session:
Returns: Promise<{ sessionId: string, workingMemory: SmartWorkingMemory }>
const { sessionId, workingMemory } = await env.MEMORY.startWorkingMemorySession();console.log(`Started session: ${sessionId}`);
getWorkingMemorySession()
Retrieves an existing session by ID:
Parameters:
sessionId: string
- The session identifier
Returns: Promise<SmartWorkingMemory>
const workingMemory = await env.MEMORY.getWorkingMemorySession("session-123");
putMemory()
Stores a new memory entry in the session:
Parameters:
entry: NewMemoryEntry
- The memory to store
Returns: Promise<string>
- The memory entry ID
const memoryId = await workingMemory.putMemory({ content: "User wants daily standup at 9 AM", timeline: "meetings", key: "schedule", agent: "assistant"});
getMemory()
Retrieves memories using exact filters:
Parameters:
query: WorkingMemoryQuery
- Query filters
Returns: Promise<MemoryEntry[] | null>
- Matching memories
const memories = await workingMemory.getMemory({ timeline: "meetings", key: "schedule", nMostRecent: 5});
searchMemory()
Performs semantic search across memory content:
Parameters:
query: WorkingMemorySearchQuery
- Search terms and filters
Returns: Promise<MemoryEntry[] | null>
- Ranked results
const results = await workingMemory.searchMemory({ terms: "morning meeting schedule", timeline: "meetings", nMostRecent: 10});
deleteMemory()
Removes a memory entry by ID:
Parameters:
entryId: string
- The memory ID to delete
Returns: Promise<void>
await workingMemory.deleteMemory("memory-456");
endSession()
Ends the session with optional flush to episodic memory. When flush
is true, the operation happens asynchronously in the background:
Parameters:
flush: boolean
- Whether to save to episodic storage
Returns: Promise<void>
// End session and save to episodic memory (background processing)await workingMemory.endSession(true);console.log("Session ended, flushing to episodic memory in background");
// End session without savingawait workingMemory.endSession(false);
summarizeMemory()
Generates AI summary of memory entries:
Parameters:
memories: MemoryEntry[]
- Memories to summarizesystemPrompt?: string
- Optional custom prompt
Returns: Promise<{ summary: string, entries: Record<string, MemoryEntry[]>, metadata: object }>
const summary = await workingMemory.summarizeMemory(memories, "Summarize the key decisions and action items from these memories.");console.log(summary.summary);console.log(`Summarized ${summary.metadata.entryCount} entries`);
Episodic Memory
Episodic memory stores completed sessions as searchable history that can be restored.
searchEpisodicMemory()
Search across historical sessions:
Parameters:
terms: string
- Search termsoptions?: object
- Optional filters
Returns: Promise<{ results: Array<object>, pagination: object }>
const results = await env.MEMORY.searchEpisodicMemory("project planning", { nMostRecent: 10, startTime: new Date('2024-01-01'), endTime: new Date('2024-12-31')});
results.results.forEach(session => { console.log(`Session ${session.sessionId}: ${session.summary}`); console.log(`${session.entryCount} entries, ${session.duration}ms duration`);});
rehydrateSession()
Restore a previous session from episodic memory. This operation is asynchronous and may take time for large sessions:
Parameters:
sessionId: string
- Session to restoresummaryOnly?: boolean
- Restore only summary or full session
Returns: Promise<{ sessionId: string, workingMemory: SmartWorkingMemory, success: boolean, message: string, entriesRestored?: number }>
const restored = await env.MEMORY.rehydrateSession("session-789", false);if (restored.success) { console.log(`Restored ${restored.entriesRestored} entries`); // Continue working with restored.workingMemory} else { console.error(`Rehydration failed: ${restored.message}`);}
getRehydrationStatus()
Monitor the progress of an ongoing rehydration operation:
Returns: Promise<{ status: 'initiated' | 'processing' | 'completed' | 'failed', sessionId?: string, entriesRestored?: number, error?: string, initiatedAt?: string, completedAt?: string } | null>
// Start rehydrationconst restored = await env.MEMORY.rehydrateSession("session-789", false);
// Check status periodically for large sessionsconst checkStatus = async () => { const status = await workingMemory.getRehydrationStatus(); if (status) { console.log(`Status: ${status.status}`);
if (status.status === 'completed') { console.log(`Successfully restored ${status.entriesRestored} entries`); } else if (status.status === 'failed') { console.error(`Rehydration failed: ${status.error}`); } else if (status.status === 'processing') { console.log('Still processing...'); setTimeout(checkStatus, 1000); // Check again in 1 second } }};
if (restored.success) { await checkStatus();}
Search Behavior
SmartMemory uses vector-based semantic search powered by AI embeddings for intelligent content discovery.
Vector Search
Working memory search uses AI embeddings to understand semantic meaning:
// These searches will find semantically similar contentawait workingMemory.searchMemory({ terms: "user authentication" });// ^ Finds memories about "login security", "access control", etc.
await workingMemory.searchMemory({ terms: "morning meeting" });// ^ Finds memories about "daily standup", "9 AM call", etc.
Automatic Fallback
When vector search fails, the system automatically falls back to text-based search:
// Search works even if AI embeddings are unavailableconst results = await workingMemory.searchMemory({ terms: "project deadline"});
// Results come from either:// 1. Vector search (semantic understanding) - preferred// 2. Text search (keyword matching) - fallback
Performance Characteristics
- Vector Search: Fast semantic matching across thousands of memories
- Text Search: Reliable keyword matching when vector search unavailable
- Session Isolation: Each session’s search is completely independent
- Timeline Filtering: Search within specific timelines for better performance
Semantic Memory
Semantic memory stores documents and knowledge that persist across all sessions.
putSemanticMemory()
Store a document in semantic memory:
Parameters:
document: Record<string, unknown>
- Document to store
Returns: Promise<{ success: boolean, objectId?: string, error?: string }>
const result = await workingMemory.putSemanticMemory({ title: "Project Requirements", content: "The system should handle 1000 concurrent users...", type: "requirements", tags: ["project", "specs", "performance"]});
if (result.success) { console.log(`Stored document: ${result.objectId}`);} else { console.error(`Failed to store document: ${result.error}`);}
getSemanticMemory()
Retrieve a specific document by ID:
Parameters:
objectId: string
- Document identifier
Returns: Promise<{ success: boolean, document?: Record<string, unknown>, error?: string }>
const result = await workingMemory.getSemanticMemory("doc-123");if (result.success && result.document) { console.log(`Title: ${result.document.title}`); console.log(`Content: ${result.document.content}`);}
searchSemanticMemory()
Search across semantic memory documents:
Parameters:
needle: string
- Search query
Returns: Promise<{ success: boolean, documentSearchResponse?: object, error?: string }>
const results = await workingMemory.searchSemanticMemory("user authentication");if (results.success && results.documentSearchResponse) { results.documentSearchResponse.results.forEach(match => { console.log(`Score: ${match.score}, Text: ${match.text}`); });} else { console.error(`Search failed: ${results.error}`);}
deleteSemanticMemory()
Remove a document from semantic memory:
Parameters:
objectId: string
- Document to delete
Returns: Promise<{ success: boolean, error?: string }>
const result = await workingMemory.deleteSemanticMemory("doc-456");if (result.success) { console.log("Document deleted successfully");}
Procedural Memory
Procedural memory stores reusable procedures, templates, and skills.
getProceduralMemory()
Get access to procedural memory:
Parameters:
id?: string
- Optional procedural memory identifier
Returns: Promise<SmartProceduralMemory>
const proceduralMemory = await env.MEMORY.getProceduralMemory();
putProcedure()
Store a procedure or template:
Parameters:
key: string
- Unique procedure identifiervalue: string
- Procedure content
Returns: Promise<void>
await proceduralMemory.putProcedure("email-template", "Subject: Update on {{project}}\n\nHi {{name}},\n\nI wanted to update you on...");
await proceduralMemory.putProcedure("code-review-checklist", "1. Check for security vulnerabilities\n2. Verify test coverage\n3. Review performance implications");
getProcedure()
Retrieve a procedure by key:
Parameters:
key: string
- Procedure identifier
Returns: Promise<string | null>
const template = await proceduralMemory.getProcedure("email-template");if (template) { const personalizedEmail = template .replace("{{project}}", "Website Redesign") .replace("{{name}}", "Sarah");}
listProcedures()
Get all stored procedures:
Returns: Promise<ProcedureEntry[]>
const allProcedures = await proceduralMemory.listProcedures();allProcedures.forEach(proc => { console.log(`${proc.key}: ${proc.value.substring(0, 50)}...`); console.log(`Created: ${proc.createdAt}, Updated: ${proc.updatedAt}`);});
searchProcedures()
Search procedures by content:
Parameters:
query: ProceduralMemorySearchQuery
- Search parameters
Returns: Promise<ProcedureEntry[] | null>
const results = await proceduralMemory.searchProcedures({ terms: "email template", nMostRecent: 5, searchKeys: true, searchValues: true});
deleteProcedure()
Remove a procedure:
Parameters:
key: string
- Procedure to delete
Returns: Promise<boolean>
const deleted = await proceduralMemory.deleteProcedure("old-template");if (deleted) { console.log("Procedure deleted successfully");}
Complete Example
Here’s how to use all memory types together with proper async handling and error checking:
try { // Start a working memory session const { sessionId, workingMemory } = await env.MEMORY.startWorkingMemorySession(); console.log(`Started session: ${sessionId}`);
// Store some conversation memories await workingMemory.putMemory({ content: "User is working on a React project with authentication needs", timeline: "project-context", key: "requirements" });
await workingMemory.putMemory({ content: "Discussed using JWT tokens for session management", timeline: "technical-decisions", key: "auth-strategy" });
// Search working memory (uses vector search with text fallback) const authMemories = await workingMemory.searchMemory({ terms: "authentication JWT tokens", nMostRecent: 5 });
if (authMemories) { console.log(`Found ${authMemories.length} related memories`); }
// Store knowledge in semantic memory with error handling const semanticResult = await workingMemory.putSemanticMemory({ title: "JWT Best Practices", content: "Always use HTTPS, set appropriate expiration times, validate tokens on every request...", type: "security-guide", topic: "authentication" });
if (semanticResult.success) { console.log(`Stored knowledge document: ${semanticResult.objectId}`); } else { console.error(`Failed to store knowledge: ${semanticResult.error}`); }
// Store reusable templates in procedural memory const proceduralMemory = await env.MEMORY.getProceduralMemory(); await proceduralMemory.putProcedure("jwt-middleware-template", `const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1];
if (!token) { return res.sendStatus(401); }
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); });}`);
// End session and flush to episodic memory (happens in background) await workingMemory.endSession(true); console.log("Session ended, flushing to episodic memory in background");
// Later, search episodic memory for similar conversations const pastSessions = await env.MEMORY.searchEpisodicMemory("React authentication project"); if (pastSessions.results.length > 0) { console.log(`Found ${pastSessions.results.length} similar past sessions`);
// Rehydrate a previous session with status monitoring const restored = await env.MEMORY.rehydrateSession(pastSessions.results[0].sessionId); if (restored.success) { console.log("Rehydration initiated, checking status...");
// Monitor rehydration progress for large sessions const checkStatus = async () => { const status = await workingMemory.getRehydrationStatus(); if (status?.status === 'completed') { console.log(`Successfully restored ${status.entriesRestored} memories`); } else if (status?.status === 'failed') { console.error(`Rehydration failed: ${status.error}`); } else if (status?.status === 'processing') { setTimeout(checkStatus, 1000); // Check again in 1 second } };
await checkStatus(); } else { console.error(`Rehydration failed: ${restored.message}`); } }
} catch (error) { console.error("SmartMemory operation failed:", error);}
This example demonstrates:
- Working Memory: Active conversation storage and semantic search
- Semantic Memory: Persistent knowledge with error handling
- Procedural Memory: Reusable code templates
- Episodic Memory: Searchable session history
- Async Operations: Background flushing and rehydration with status monitoring
- Error Handling: Proper checking of success/failure states