Skip to main content
The KV Store is a persistent key-value database built into the platform. Use it to share state across executions, sessions and agents — shopping carts, feature flags, counters, idempotency keys — without standing up your own Redis or database.
  • Tenant-scoped — all agents in your workspace read and write the same store
  • Namespaces — created implicitly on first write, no setup required
  • TTL — optional per-key expiration in seconds
  • Pattern search — find keys with glob patterns like cart:*:items
  • Any JSON value — objects, arrays, strings, numbers, booleans (up to 256 KB per entry)

KV Store vs Memory

The KV Store holds business state; Memory holds conversation context. A value you set() comes back exactly as stored, for as long as you need it — while Memory content is trimmed and summarized as the conversation grows.
NeedUse
Cart contents, feature flags, counters, preferences, idempotency keysKV Store
Message history, conversation summaries, session status, follow-upsMemory
If you were persisting data by stuffing it into sessions or message history, the KV Store replaces that workaround. See Abandoned Cart Recovery for both working together — cart in KV, dialogue in Memory.

Quick Start

import { KV } from '@runflow-ai/sdk';

// Namespace dedicated to a domain (created automatically on first set)
const carts = KV.namespace('carts');

// Set a value with a 1-hour TTL
await carts.set('cart:5511999:items', { items: ['sku-1', 'sku-2'] }, { ttl: 3600 });

// Read it back (null when missing or expired)
const cart = await carts.get('cart:5511999:items');

// Find keys by pattern
const keys = await carts.keys('cart:*:items');

// Delete when done
await carts.delete('cart:5511999:items');
Also available as a dedicated entry point: import { KV } from '@runflow-ai/sdk/kv'.

Static Shortcuts

For one-off reads and writes, the static methods use the default namespace:
import { KV } from '@runflow-ai/sdk';

await KV.set('flag:new-checkout', true);
const enabled = await KV.get('flag:new-checkout');

await KV.has('flag:new-checkout');   // true
await KV.delete('flag:new-checkout');

Namespaces

Namespaces isolate keys by domain. They are implicit: a namespace exists as soon as its first key is written, and disappears when its last key is deleted.
const carts = KV.namespace('carts');
const flags = KV.namespace('feature-flags');

// Same key, different namespaces — no collision
await carts.set('config', { maxItems: 50 });
await flags.set('config', { rollout: 0.25 });

// List every namespace in the tenant with key counts
const all = await KV.namespaces();
// [{ namespace: 'carts', keys: 12 }, { namespace: 'feature-flags', keys: 3 }]

// Delete every key in a namespace
const removed = await carts.clear(); // returns number of deleted keys
Namespace names accept letters, digits, ., _ and - (max 128 characters, must start with a letter or digit).

TTL and Expiration

Pass ttl (in seconds) to make an entry expire automatically. Expired keys behave exactly like missing keys — get() returns null, has() returns false, and listings skip them.
// Expires in 30 minutes
await carts.set('cart:5511999:items', cart, { ttl: 1800 });

// No ttl = persistent
await carts.set('cart:5511999:address', address);
Setting a key without ttl clears any previous TTL — the entry becomes persistent. To keep a key expiring, pass ttl on every write.
To inspect the expiration of a key, use getEntry():
const entry = await carts.getEntry('cart:5511999:items');
// { key, value, expiresAt: '2026-07-12T18:30:00.000Z', createdAt, updatedAt }
// null when missing or expired
keys() and getAll() accept a glob pattern: * matches any sequence of characters, ? matches exactly one.
const carts = KV.namespace('carts');

// All item lists, any customer
await carts.keys('cart:*:items');
// ['cart:5511888:items', 'cart:5511999:items']

// Keys and values together
const entries = await carts.getAll('cart:5511999:*');
// [{ key: 'cart:5511999:items', value: {...}, expiresAt, ... }, ...]

// No pattern = everything in the namespace
const everything = await carts.getAll();
Structure your keys with a consistent separator (entity:id:field) so patterns stay predictable — cart:*:items, user:*:profile, order:2026-07-*.

Pagination

Listings return up to 100 items by default (max 1000). For large namespaces, use listKeys() / listEntries(), which also return the total count:
const page = await carts.listEntries({ pattern: 'cart:*', limit: 50, offset: 100 });
// { items: KvEntry[], total: 1240 }

const keyPage = await carts.listKeys({ limit: 200 });
// { keys: [{ key, expiresAt, updatedAt }], total: 1240 }

Common Patterns

Cart / conversation state with TTL

const carts = KV.namespace('carts');

// Inside a tool: persist partial state that survives the session
await carts.set(`cart:${phone}:items`, items, { ttl: 24 * 3600 });

// Next conversation, even days later on a new session:
const items = await carts.get(`cart:${phone}:items`) ?? [];

Feature flags

const flags = KV.namespace('feature-flags');

await flags.set('new-payment-flow', { enabled: true, rollout: 0.5 });

const flag = await flags.get('new-payment-flow');
if (flag?.enabled) {
  // ...
}

Idempotency / deduplication

const processed = KV.namespace('processed-events');

if (await processed.has(`webhook:${eventId}`)) {
  return { skipped: true }; // already handled
}
await processed.set(`webhook:${eventId}`, { at: new Date().toISOString() }, { ttl: 86400 });
See Abandoned Cart Recovery for a complete working project combining these patterns — KV cart state, TTL as cleanup policy, idempotent reminders, and Memory-powered follow-ups.

Managing Data in the Dashboard

Every namespace is browsable in the platform under KV Store in the sidebar:
  • Browse namespaces with live key counts
  • Filter keys with the same glob patterns (cart:*:items)
  • Inspect full JSON values and TTLs
  • Delete individual keys or clear a whole namespace

Local Development

Outside the platform, the SDK follows the same convention as Memory: with RUNFLOW_ENV=development (or RUNFLOW_LOCAL_MEMORY=true) values are stored as JSON files under .runflow/kv/ in your project — no API required.
# Force a specific provider (optional)
RUNFLOW_KV_PROVIDER=file   # local JSON files
RUNFLOW_KV_PROVIDER=api    # Runflow API
You can also pass a provider explicitly — useful in tests:
import { KV, FileKvProvider } from '@runflow-ai/sdk';

const kv = KV.namespace('test', { provider: new FileKvProvider('/tmp/kv-test') });
To back the KV store with your own storage, implement the KvProvider interface (same pattern as custom memory providers).

Limits and Validation

ConstraintValue
Value size256 KB per entry (JSON-serialized)
Key length1–512 characters, no control characters
NamespaceLetters, digits, ., _, - — max 128 chars
TTLInteger ≥ 1 (seconds); omit for persistent
List page sizeDefault 100, max 1000
null / undefined valuesRejected — use delete() to remove a key

Method Reference

MethodReturnsDescription
get(key)T | nullValue, or null when missing/expired
getEntry(key)KvEntry | nullValue plus expiresAt, createdAt, updatedAt
set(key, value, { ttl? }){ key, namespace, expiresAt }Create or overwrite a key
has(key)booleanWhether the key exists and is not expired
delete(key)booleantrue if the key existed
keys(pattern?)string[]Key names, optionally filtered by glob
getAll(pattern?)KvEntry[]Entries with values, optionally filtered
listKeys(options?){ keys, total }Paginated key metadata
listEntries(options?){ items, total }Paginated entries
clear()numberDelete all keys in the namespace
listNamespaces()KvNamespaceSummary[]All namespaces with key counts
All methods are also available as statics on KV (operating on the default namespace), plus KV.namespace(name) to get a scoped instance and KV.namespaces() to list namespaces.

REST API

Everything above is also exposed as authenticated REST endpoints under /api/v1/runtime/v1/kv — see the Runtime REST API reference if you’re integrating without the SDK.

Next Steps

Memory

Conversation history — use KV for state, Memory for dialogue

Tools

Read and write KV state from custom tools

Abandoned Cart Recovery

Complete project: KV cart state + Memory follow-ups

Schedule

Combine KV state with scheduled follow-ups