Key-Value Storage — Agentuity Documentation

Key-Value Storage

Fast key-based storage for caching, session data, and configuration

Key-value ("KV") storage provides fast data access for agents. Use it for caching, configuration, rate limiting, and data that needs quick lookups.

When to Use Key-Value Storage

Storage TypeBest For
Key-ValueFast lookups, caching, configuration, rate limits
VectorSemantic search, embeddings, RAG
Object (S3)Files, images, documents, media
DatabaseStructured data, complex queries, transactions
Durable StreamsLarge exports, audit logs, real-time data

Access Patterns

ContextAccessDetails
Agentsctx.kvSee examples below
Routesc.var.kvSee Using in Routes
StandalonecreateAgentContext()See Standalone Usage
External backendsHTTP routesSDK Utilities for External Apps
FrontendVia routesReact Hooks

Basic Operations

Access key-value storage through ctx.kv in agents or c.var.kv in routes. Namespaces are created when you write the first key.

Storing Data

import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('CacheManager', {
  handler: async (ctx) => {
    const responseData = { status: 'ok', cachedAt: new Date().toISOString() };
 
    // Store with custom TTL
    await ctx.kv.set('cache', 'api-response', responseData, {
      ttl: 3600,  // expires in 1 hour
      contentType: 'application/json',
    });
 
    // Store with default TTL (7 days)
    await ctx.kv.set('cache', 'user-prefs', { theme: 'dark' });
 
    // Store with no expiration (persists indefinitely)
    await ctx.kv.set('config', 'feature-flags', {
      darkMode: true,
      betaFeatures: false,
    }, {
      ttl: null,  // never expires (0 also works)
    });
 
    return { success: true };
  },
});

Retrieving Data

import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('CacheRetriever', {
  handler: async (ctx) => {
    const result = await ctx.kv.get('cache', 'api-response');
 
    if (result.exists) {
      ctx.logger.info('Cache hit', {
        contentType: result.contentType,
        expiresAt: result.expiresAt,  // ISO timestamp when key expires (if TTL set)
      });
      return { data: result.data };
    }
 
    ctx.logger.info('Cache miss');
    return { data: null };
  },
});

Deleting Data

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const agent = createAgent('SessionCleaner', {
  schema: {
    input: s.object({ sessionId: s.string() }),
    output: s.object({ deleted: s.boolean() }),
  },
  handler: async (ctx, input) => {
    await ctx.kv.delete('sessions', input.sessionId);
    return { deleted: true };
  },
});

Type Safety

Use generics for type-safe data access:

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const UserPreferencesSchema = s.object({
  theme: s.enum(['light', 'dark']),
  language: s.string(),
  notifications: s.boolean(),
});
 
type UserPreferences = s.infer<typeof UserPreferencesSchema>;
 
const agent = createAgent('PreferenceLoader', {
  schema: {
    input: s.object({ userId: s.string() }),
    output: s.object({ theme: s.enum(['light', 'dark']) }),
  },
  handler: async (ctx, input) => {
    const result = await ctx.kv.get<UserPreferences>('prefs', input.userId);
 
    if (result.exists) {
      // TypeScript knows the shape of result.data
      const theme = result.data.theme;  // Type: 'light' | 'dark'
      return { theme };
    }
 
    return { theme: 'light' };  // default
  },
});

Additional Methods

import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('StorageExplorer', {
  handler: async (ctx) => {
    // Search keys by keyword (returns keys with metadata)
    const matches = await ctx.kv.search('cache', 'user-');
 
    // List all keys in a namespace
    const keys = await ctx.kv.getKeys('cache');
 
    // List all namespaces
    const namespaces = await ctx.kv.getNamespaces();
 
    // Get statistics for a namespace
    const stats = await ctx.kv.getStats('cache');
 
    // Get statistics for all namespaces
    const allStats = await ctx.kv.getAllStats();
 
    return { keys, namespaces, stats, allStats };
  },
});

Namespace Management

Create and delete namespaces programmatically:

import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('NamespaceManager', {
  handler: async (ctx) => {
    // Create a namespace with default TTL for all keys
    await ctx.kv.createNamespace('cache', {
      defaultTTLSeconds: 3600,  // all keys expire in 1 hour by default
    });
 
    // Create a namespace with no expiration
    await ctx.kv.createNamespace('config', {
      defaultTTLSeconds: 0,  // keys never expire
    });
 
    // Delete a namespace (removes all keys)
    await ctx.kv.deleteNamespace('old-cache');
 
    return { success: true };
  },
});

TTL semantics:

ValueBehavior
undefinedInherits the namespace default TTL (7 days for auto-created namespaces)
null or 0Keys never expire
>= 60Custom TTL in seconds (minimum 60 seconds, maximum 365 days)

TTL Strategy

Keys expire after 7 days by default unless a namespace-level or per-key TTL is set. Use TTL for temporary data:

Data TypeSuggested TTL
API cache5-60 minutes (300-3600s)
Session data24-48 hours (86400-172800s)
Rate limit countersUntil period reset
Feature flagsNo TTL (persistent)
import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
interface UserSession {
  readonly userId: string;
  readonly email: string;
  readonly loginAt: string;
  readonly preferences: { readonly theme: string };
}
 
const agent = createAgent('SessionManager', {
  schema: {
    input: s.object({
      token: s.string(),
      userId: s.string(),
      email: s.string(),
    }),
    output: s.object({
      session: s.object({
        userId: s.string(),
        email: s.string(),
        loginAt: s.string(),
        preferences: s.object({ theme: s.string() }),
      }),
    }),
  },
  handler: async (ctx, input) => {
    const sessionKey = `session:${input.token}`;
 
    // Check for existing session
    const existing = await ctx.kv.get<UserSession>('sessions', sessionKey);
    if (existing.exists) {
      return { session: existing.data };
    }
 
    // Create new session with 24-hour TTL
    const session: UserSession = {
      userId: input.userId,
      email: input.email,
      loginAt: new Date().toISOString(),
      preferences: { theme: 'light' },
    };
 
    await ctx.kv.set('sessions', sessionKey, session, {
      ttl: 86400,  // 24 hours
    });
 
    return { session };
  },
});

Using in Routes

Routes have the same KV access via c.var.kv:

import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
 
const router = new Hono<Env>();
 
router.get('/session/:id', async (c) => {
  const sessionId = c.req.param('id');
  const result = await c.var.kv.get<{ userId: string; email: string }>('sessions', sessionId);
 
  if (!result.exists) {
    return c.json({ error: 'Session not found' }, 404);
  }
 
  return c.json({ session: result.data });
});
 
export default router;

Standalone Usage

Use KV storage outside of agent handlers with createAgentContext():

import { createApp, createAgentContext } from '@agentuity/runtime';
 
const app = await createApp();
export default app;
 
async function ensurePreferences(userId: string): Promise<void> {
  const ctx = createAgentContext();
 
  await ctx.invoke(async () => {
    // Check user preferences
    const prefs = await ctx.kv.get('prefs', userId);
 
    if (!prefs.exists) {
      await ctx.kv.set('prefs', userId, {
        theme: 'dark',
        notifications: true,
      });
    }
  });
}

See Running Agents Without HTTP for more patterns including Discord bots, CLI tools, and queue workers.

Best Practices

  • Use descriptive keys: user:{userId}:prefs instead of u123
  • Set appropriate TTLs: Prevent storage bloat with expiring cache entries
  • Handle missing keys: Always check result.exists before accessing data
  • Keep values small: KV is optimized for small-to-medium values; use Object Storage for large files

Next Steps