Key-Value Storage — Agentuity Documentation

Key-Value Storage

Fast, ephemeral 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. Buckets are auto-created on first use.

Storing Data

import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('CacheManager', {
  handler: async (ctx, input) => {
    // Store with custom TTL (minimum 60 seconds, maximum 90 days)
    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, input) => {
    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';
 
const agent = createAgent('SessionCleaner', {
  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 { type } from 'arktype';
 
const UserPreferences = type({
  theme: "'light' | 'dark'",
  language: 'string',
  notifications: 'boolean',
});
 
type UserPreferences = typeof UserPreferences.infer;
 
const agent = createAgent('PreferenceLoader', {
  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, input) => {
    // 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, input) => {
    // 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
undefinedKeys expire after 7 days (default)
null or 0Keys never expire
>= 60Custom TTL in seconds (minimum 60 seconds, maximum 90 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';
 
interface UserSession {
  userId: string;
  email: string;
  loginAt: string;
  preferences: { theme: string };
}
 
const agent = createAgent('SessionManager', {
  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 { createRouter } from '@agentuity/runtime';
 
const router = createRouter();
 
router.get('/session/:id', async (c) => {
  const sessionId = c.req.param('id');
  const result = await c.var.kv.get('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';
 
await createApp();
 
// Discord bot example
client.on('messageCreate', async (message) => {
  const ctx = createAgentContext();
 
  await ctx.invoke(async () => {
    // Check user preferences
    const prefs = await ctx.kv.get('prefs', message.author.id);
 
    if (!prefs.exists) {
      await ctx.kv.set('prefs', message.author.id, {
        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