Memories
Memories are the core of zkstash. The SDK provides two ways to create memories:
- Extraction: Pass a conversation and let zkstash’s AI extract structured memories.
- Direct Storage: Pass structured data directly (e.g., from agent tool calls).
Creating Memories via Extraction
To extract memories from conversations, use the createMemory method. The zkstash backend will automatically analyze the conversation using your registered schemas.
const response = await client.createMemory({
agentId: "agent-007",
conversation: [
{ role: "user", content: "I am allergic to peanuts." },
{ role: "assistant", content: "Understood, I will remember that." },
],
});
console.log("Created memories:", response.created);Parameters
agentId: The ID of the agent storing the memory.conversation: Array of message objects ({ id?, role, content }).options.subjectId(Optional): Subject ID for multi-tenant isolation.options.threadId(Optional): Thread ID to scope context.options.schemas(Optional): Array of schema names to limit extraction to specific schemas.options.ttl(Optional): Default TTL for all memories (e.g.,"7d").
Incremental Extraction: If you provide unique
ids for your messages, zkStash will automatically skip messages it has already processed. This allows you to simply send the full window of your conversation without tracking the delta manually.
Storing Memories Directly
If you already have structured data (e.g., from agent tool calls), use storeMemories to bypass LLM extraction entirely. This is faster and cheaper.
await client.storeMemories({
agentId: "agent-007",
memories: [
{ kind: "UserProfile", data: { name: "Alice", allergens: ["peanuts"] } },
{ kind: "Preference", data: { category: "food", value: "vegetarian" } },
],
subjectId: "tenant-a", // Optional: Tenant isolation
ttl: "24h" // Optional: Default TTL
});Parameters
The function accepts a single options object with the following properties:
agentId: The ID of the agent storing the memory.memories: Array of memory objects withkind,data, and optionalid,ttl,expiresAt.subjectId(Optional): Subject ID for multi-tenant isolation.threadId(Optional): Thread ID to scope context.ttl(Optional): Default TTL for all memories (e.g.,"1h","24h","7d").expiresAt(Optional): Default expiry timestamp (ms) for all memories.
ID Behavior:
- Schemas with
uniqueOn: ID is auto-generated. New memories automatically supersede existing ones matching the unique fields.- Schemas without
uniqueOn: Omitidto create, includeidto update.
Context Management
zkStash stores context internally and provides it to the extraction process automatically.
The “Always Append” Contract
zkStash is designed to process conversations incrementally. There are two ways to handle this:
1. Manual Delta Tracking
Each call to createMemory contains only new messages since the last call:
// First call: Initial messages
await client.createMemory({
agentId: "agent-007",
conversation: [
{ role: "user", content: "Hi, I'm allergic to peanuts." },
{ role: "assistant", content: "Got it!" },
],
});
// Second call: Only NEW messages
await client.createMemory({
agentId: "agent-007",
conversation: [
{ role: "user", content: "I also prefer dark mode." },
{ role: "assistant", content: "Noted!" },
],
});2. Automatic ID-based Incremental Extraction (Recommended)
If your messages have stable IDs, you can simply send the full context window. zkStash will use the last processed message ID from your thread state to skip already processed messages.
// Send the full window, server handles the delta
await client.createMemory({
agentId: "agent-007",
conversation: [
{ id: "msg_1", role: "user", content: "Hi, I'm allergic to peanuts." },
{ id: "msg_2", role: "assistant", content: "Got it!" },
{ id: "msg_3", role: "user", content: "I also prefer dark mode." },
{ id: "msg_4", role: "assistant", content: "Noted!" },
],
});Idempotency
Sending the exact same conversation twice will result in an idempotent response:
{ "success": true, "created": [], "updated": [] }Searching Memories
You can search for memories using natural language queries. The system uses semantic search to find the most relevant memories.
const results = await client.searchMemories({
query: "dietary restrictions",
filters: {
agentId: "agent-007",
kind: "UserProfile", // Optional: filter by schema type
},
});
console.log("Found memories:", results.memories);Response Format (LLM Mode)
Search results are returned in an LLM-optimized format with semantic structure:
// Each memory in results.memories has this structure:
{
id: "mem_123",
kind: "UserProfile",
quality: {
relevance: 0.89, // Search relevance (0-1)
confidence: 0.95 // Extraction certainty (0-1)
},
data: {
// User-defined schema fields
allergens: ["peanuts"],
dietaryPreference: "vegetarian"
},
context: {
when: "2024-01-15T10:30:00Z", // Event timestamp (if temporal)
mentions: [
{ name: "User", type: "person" }
],
tags: ["health", "preferences"],
isLatest: true // Not superseded by newer memory
},
source: "own" // or "shared:agent-name"
}Filters
agentId: Filter by agent.subjectId: Filter by subject/tenant.threadId: Filter by conversation thread.kind: Filter by schema name (e.g., “UserProfile”).tags: Filter by tags.
Mode
mode: The mode to use for the search. Can be"llm","answer", or"map".llm(default): Returns semantically structured memories optimized for LLM consumption.answer: Returns a concise, grounded answer to the query.map: Returns a memory map with topical clusters.
Scope & Shared Memories
You can search your own memories, shared memories from other agents, or both:
// Search only your own memories
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "own" }
);
// Search shared memories (requires grants)
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "shared" }
);
// Search both (default)
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "all" }
);For more on sharing memories between agents, see Memory Sharing.
Deleting Memories
You can delete memories using the deleteMemory method.
await client.deleteMemory({ id: "memory-007" });Memory Expiration (TTL)
You can set memories to automatically expire using TTL (Time-To-Live). This is useful for session-specific context or time-sensitive information.
Setting TTL on Creation
// Per-memory TTL
await client.storeMemories("agent-007", [
{ kind: "SessionContext", data: { task: "booking" }, ttl: "24h" },
{ kind: "Reminder", data: { text: "Follow up" }, ttl: "1h" },
]);
// Request-level default (applied to LLM extractions and memories without explicit TTL)
await client.createMemory({
agentId: "agent-007",
ttl: "7d", // All extracted memories expire in 7 days
conversation: [
{ role: "user", content: "Book me a flight to Tokyo next week." },
],
});Supported formats: "30s", "15m", "1h", "24h", "7d"
Updating Expiration
// Set expiration on existing memory
await client.updateMemory("memory-id", { expiresAt: Date.now() + 3600000 });
// Remove expiration (make permanent)
await client.updateMemory("memory-id", { expiresAt: null });Batch Operations with TTL
// Set same expiration on multiple memories
await client.batchUpdateMemories({
ids: ["mem_1", "mem_2", "mem_3"],
update: { expiresAt: Date.now() + 86400000 } // All expire in 24h
});Note: TTL expiration applies to all plans. It’s separate from the Free plan’s 7-day system retention policy. See Credits & Payments for details.