Skip to Content

Memories

Memories are the core of zkstash. The SDK provides two ways to create memories:

  1. Extraction: Pass a conversation and let zkstash’s AI extract structured memories.
  2. 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 with kind, data, and optional id, 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: Omit id to create, include id to 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!" }, ], });

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.

Last updated on