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 Type | Best For |
|---|---|
| Key-Value | Fast lookups, caching, configuration, rate limits |
| Vector | Semantic search, embeddings, RAG |
| Object (S3) | Files, images, documents, media |
| Database | Structured data, complex queries, transactions |
| Durable Streams | Large exports, audit logs, real-time data |
KV vs Built-in State
Use built-in state (ctx.state, ctx.thread.state, ctx.session.state) for data tied to active requests and conversations. Use KV when you need custom TTL, persistent data across sessions, or shared state across agents.
Access Patterns
| Context | Access | Details |
|---|---|---|
| Agents | ctx.kv | See examples below |
| Routes | c.var.kv | See Using in Routes |
| Standalone | createAgentContext() | See Standalone Usage |
| External backends | HTTP routes | SDK Utilities for External Apps |
| Frontend | Via routes | React Hooks |
Same API Everywhere
The KV API is identical in all contexts. ctx.kv.get() and c.var.kv.get() work the same way. See Accessing Services for the full reference.
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 optional TTL (minimum 60 seconds)
await ctx.kv.set('cache', 'api-response', responseData, {
ttl: 3600, // expires in 1 hour
contentType: 'application/json',
});
// Store without TTL (persists indefinitely)
await ctx.kv.set('config', 'feature-flags', {
darkMode: true,
betaFeatures: false,
});
return { success: true };
},
});Retrieving Data
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 });
return { data: result.data };
}
ctx.logger.info('Cache miss');
return { data: null };
},
});Deleting Data
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 { 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
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:
const agent = createAgent('NamespaceManager', {
handler: async (ctx, input) => {
// Create a new namespace
await ctx.kv.createNamespace('tenant-123');
// Delete a namespace (removes all keys)
await ctx.kv.deleteNamespace('old-cache');
return { success: true };
},
});Destructive Operation
deleteNamespace() permanently removes the namespace and all its keys. This operation cannot be undone.
TTL Strategy
Keys persist indefinitely by default. Use TTL for temporary data:
| Data Type | Suggested TTL |
|---|---|
| API cache | 5-60 minutes (300-3600s) |
| Session data | 24-48 hours (86400-172800s) |
| Rate limit counters | Until period reset |
| Feature flags | No TTL (persistent) |
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;External Backend Access
Need to access KV from a Next.js backend or other external service? Create authenticated routes that expose storage operations, then call them via HTTP. See SDK Utilities for External Apps.
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}:prefsinstead ofu123 - Set appropriate TTLs: Prevent storage bloat with expiring cache entries
- Handle missing keys: Always check
result.existsbefore accessing data - Keep values small: KV is optimized for small-to-medium values; use Object Storage for large files
Next Steps
- Vector Storage: Semantic search and embeddings
- Object Storage (S3): File and media storage
- Database: Relational data with queries and transactions
- Durable Streams: Large data exports
- State Management: Built-in request/thread/session state
Need Help?
Join our Community for assistance or just to hang with other humans building agents.
Send us an email at hi@agentuity.com if you'd like to get in touch.
Please Follow us on
If you haven't already, please Signup for your free account now and start building your first agent!