Persistent Storage

Storage enables Triks to persist data across sessions and restarts.

The Problem

Without storage, every session starts fresh:

User: Set my preferred theme to dark Agent: Done! Your theme is now dark. [Later, new session] User: What's my preferred theme? Agent: I don't know - I have no memory of previous sessions.

With storage:

User: Set my preferred theme to dark Agent: Done! Your theme is now dark. [Later, new session] User: What's my preferred theme? Agent: Your preferred theme is dark.

How Storage Works

1. Enable in Manifest

{ "capabilities": { "storage": { "enabled": true, "maxSizeBytes": 10485760 } } }
FieldDescription
enabledTurn storage on/off
maxSizeBytesQuota limit (default: 100MB)

2. Access via TrikContext

When storage is enabled, the gateway passes a TrikStorageContext to your trik through TrikContext.storage. How you access it depends on your trik’s mode.

Conversational Mode

In a conversational trik, access storage through the context passed to your agent factory or through tools:

import { wrapAgent } from '@trikhub/sdk'; import { createReactAgent } from '@langchain/langgraph/prebuilt'; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { transferBackTool } from '@trikhub/sdk'; export default wrapAgent(async (context) => { const setPreference = tool( async ({ key, value }) => { await context.storage.set(`pref:${key}`, value); return JSON.stringify({ success: true }); }, { name: 'setPreference', description: 'Store a user preference', schema: z.object({ key: z.string(), value: z.string() }), } ); const getPreference = tool( async ({ key }) => { const value = await context.storage.get(`pref:${key}`); return JSON.stringify({ value: value ?? 'not set' }); }, { name: 'getPreference', description: 'Retrieve a user preference', schema: z.object({ key: z.string() }), } ); return createReactAgent({ llm, tools: [setPreference, getPreference, transferBackTool], }); });

Tool Mode

In a tool-mode trik, access storage through the context passed to each handler:

import { wrapToolHandlers } from '@trikhub/sdk'; export default wrapToolHandlers({ setPreference: async (input, context) => { const { key, value } = input as { key: string; value: string }; await context.storage.set(`pref:${key}`, value); return { status: 'saved', key }; }, getPreference: async (input, context) => { const { key } = input as { key: string }; const value = await context.storage.get(`pref:${key}`); return { key, value: value ?? 'not set' }; }, });

Storage API

All methods are async and return Promises.

get(key: string): Promise<unknown | null>

Retrieve a value by key. Returns null if the key doesn’t exist.

const theme = await context.storage.get('user-theme'); // Returns: "dark" or null

set(key: string, value: unknown, ttl?: number): Promise<void>

Store a value. Optionally set a TTL (time-to-live) in milliseconds.

// Store permanently await context.storage.set('user-theme', 'dark'); // Store for 1 hour await context.storage.set('cache-key', data, 3600000);

delete(key: string): Promise<boolean>

Delete a key. Returns true if the key existed.

const wasDeleted = await context.storage.delete('old-key');

list(prefix?: string): Promise<string[]>

List all keys, optionally filtered by prefix.

const allKeys = await context.storage.list(); // Returns: ["theme", "last-run", "counter"] const prefixedKeys = await context.storage.list('user:'); // Returns: ["user:settings", "user:history"]

getMany(keys: string[]): Promise<Map<string, unknown>>

Get multiple values at once.

const values = await context.storage.getMany(['theme', 'language']); // Returns: Map { "theme" => "dark", "language" => "en" }

setMany(entries: Record<string, unknown>): Promise<void>

Set multiple values at once.

await context.storage.setMany({ theme: 'dark', language: 'en', lastLogin: Date.now() });

Where Data is Stored

Storage persists to a SQLite database on the local filesystem:

~/.trikhub/storage/storage.db

The database uses a single storage table with composite primary key (trik_id, key), where trik_id is the scoped trik name (e.g., @alice/weather). Each Trik has isolated storage - it cannot access other Triks’ data.

Both runtimes use built-in SQLite with no external dependencies:

  • JavaScript gateway: Uses Node.js built-in node:sqlite (requires Node.js 22.5+)
  • Python gateway: Uses Python stdlib sqlite3

Quotas and Limits

LimitDefaultDescription
maxSizeBytes104857600 (100MB)Total storage size per Trik
Key length256 charsMaximum key length
Value size10MBMaximum single value size

When a Trik exceeds its quota, set operations will throw an error.

TTL (Time-To-Live)

Set expiration times for cache-like behavior:

// Cache API response for 5 minutes await context.storage.set('api-cache', response, 300000); // Later: returns null if expired const cached = await context.storage.get('api-cache');

Expired keys are automatically cleaned up on access.

Example: User Preferences Trik

A complete conversational trik that manages user preferences:

import { wrapAgent } from '@trikhub/sdk'; import { createReactAgent } from '@langchain/langgraph/prebuilt'; import { ChatAnthropic } from '@langchain/anthropic'; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { transferBackTool } from '@trikhub/sdk'; export default wrapAgent(async (context) => { const llm = new ChatAnthropic({ apiKey: context.config.get('ANTHROPIC_API_KEY'), model: 'claude-sonnet-4-20250514', }); const setPreferences = tool( async ({ theme, language, notifications }) => { await context.storage.setMany({ theme, language, notifications }); return JSON.stringify({ success: true, message: 'Preferences saved' }); }, { name: 'setPreferences', description: 'Save user preferences', schema: z.object({ theme: z.string(), language: z.string(), notifications: z.boolean(), }), } ); const getPreferences = tool( async () => { const prefs = await context.storage.getMany([ 'theme', 'language', 'notifications' ]); return JSON.stringify({ theme: prefs.get('theme') ?? 'light', language: prefs.get('language') ?? 'en', notifications: prefs.get('notifications') ?? true }); }, { name: 'getPreferences', description: 'Get current user preferences', schema: z.object({}), } ); const resetPreferences = tool( async () => { const keys = await context.storage.list(); for (const key of keys) { await context.storage.delete(key); } return JSON.stringify({ success: true, message: 'Preferences reset' }); }, { name: 'resetPreferences', description: 'Reset all preferences to defaults', schema: z.object({}), } ); return createReactAgent({ llm, tools: [setPreferences, getPreferences, resetPreferences, transferBackTool], }); });

Storage Patterns

Pattern 1: Simple Key-Value Cache

async function getCached( storage: TrikStorageContext, key: string, fetcher: () => Promise<unknown> ) { const cached = await storage.get(`cache:${key}`); if (cached !== null) return cached; const fresh = await fetcher(); await storage.set(`cache:${key}`, fresh, 3600000); // 1 hour TTL return fresh; }

Pattern 2: Counters and Metrics

async function incrementCounter( storage: TrikStorageContext, name: string ) { const current = (await storage.get(`counter:${name}`)) ?? 0; await storage.set(`counter:${name}`, (current as number) + 1); return (current as number) + 1; }

Pattern 3: State Machine

type State = 'idle' | 'processing' | 'complete'; async function transition( storage: TrikStorageContext, newState: State ) { const current = await storage.get('state') as State ?? 'idle'; // Validate transition if (current === 'complete' && newState !== 'idle') { throw new Error('Cannot transition from complete'); } await storage.set('state', newState); await storage.set('stateChangedAt', Date.now()); }

Testing Storage

Use an in-memory storage provider for tests:

import { InMemoryStorageProvider } from '@trikhub/gateway'; const storage = new InMemoryStorageProvider(); const context = storage.forTrik('@test/my-trik'); // Test your storage logic await context.set('key', 'value'); expect(await context.get('key')).toBe('value');

For integration tests, use a temporary directory:

import { SqliteStorageProvider } from '@trikhub/gateway'; import { mkdtemp, rm } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; let tempDir: string; beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), 'storage-test-')); }); afterEach(async () => { await rm(tempDir, { recursive: true }); }); it('persists data', async () => { const storage = new SqliteStorageProvider(tempDir); const context = storage.forTrik('@test/trik'); await context.set('key', 'value'); expect(await context.get('key')).toBe('value'); });

Next: Learn about the Security Model.