Skip to main content

Flash Memory Store

What it solves

For long-term agent memory (user preferences, learned facts, historical decisions) the access pattern is power-law:
  • ~1% of keys are read constantly (current user’s preferences, recent decisions).
  • ~99% are read rarely (historical facts, archived sessions).
Keeping everything in memory wastes RAM. Storing everything on disk wastes latency. FlashMemoryStore is a tiered StorageDriver:
  • Hot tier: in-process Map with LFU eviction. Fast.
  • Cold tier: LMDB on disk. Cheap, ACID, ~50,000 ops/sec on SSDs.
Inspired by Redis Iris / Flex which uses the same pattern at petabyte scale.

Architecture

On a hot miss, the cold value is promoted to hot. On hot-cache overflow, the least-frequently-used entry is evicted (it’s still in cold, so no data loss).

Quick start

Configuration

path

When omitted, falls back to pure in-memory operation. Useful for tests and serverless environments where you can’t write to disk. When set, lmdb (optional peer dep) is loaded lazily. If the package isn’t installed, a warning is printed and the store also falls back to in-memory:

mapSize

LMDB’s underlying memory-mapped file size cap. Once the file grows past mapSize, writes fail with MDB_MAP_FULL. Set it generously — LMDB only consumes actual disk space, not the full mapped size. 2 GiB default is fine for most agent memory; raise to 100+ GiB for large knowledge corpuses.

hotCacheSize

Bounds the hot tier. Too low → constant promotion churn. Too high → wastes RAM. Rule of thumb: set to 10× the expected working set (concurrent active users × per-user keys).

Eviction policy (LFU)

The hot tier evicts the entry with the lowest hit count, with ties broken by oldest last access. This means:
  • Recently written + never read → evicted soon.
  • Old but constantly read → kept.
  • Bursty access pattern → bursts survive while the burst is hot.
The hit count is monotonically increasing — there’s no decay. For decay-based LFU (FreshLFU), wrap this in your own decorator.

API (StorageDriver)

FlashMemoryStore implements the full StorageDriver interface, so it’s a drop-in replacement anywhere a StorageDriver is accepted:

get(namespace, key)

Lookup order: hot map → cold LMDB. A cold hit is promoted to hot, evicting the least-used entry if hot is full.

set(namespace, key, value)

Writes to BOTH tiers immediately. The hot copy makes the next read instant; the cold copy survives process restart. If path is omitted (in-memory mode), only the hot map is written.

delete(namespace, key)

Removes from both tiers. Idempotent.

list(namespace, prefix?)

Returns all entries whose key (within namespace) starts with prefix. Implementation:
  1. Range-scan the cold LMDB starting at <namespace>\u0000<prefix>.
  2. Add any hot-only entries (e.g. set in memory before a cold flush).
Returns Array<{ key, value }> where key is the inner key (no namespace prefix). Performance: Cold range scans are O(log N) seek + O(K) scan. Don’t list over millions of keys per request — paginate with prefixes.

close()

Closes the LMDB env and clears the hot map. Always call this at process shutdown to flush LMDB cleanly.

Use as agent memory backend

Combine with ScopedStorage

For multi-tenant deployments, wrap it:
Each tenant’s keys are namespaced; the hot cache is still shared, so hot entries from heavy tenants stay hot.

Performance characteristics

Benchmarks on M2 Mac, 5K entries, JSON values ~1KB each: Disk usage is roughly 1.2× the sum of serialized values (LMDB B+tree overhead).

When to use

  • Persistent agent memory across process restarts
  • Long-running multi-day agent sessions
  • High-volume workloads where the default InMemoryStorage won’t survive
  • Edge / Raspberry Pi deployments (LMDB has tiny RAM overhead)

When NOT to use

  • Multi-process deployments — LMDB allows multiple readers but only one writer process at a time. Use Postgres / Mongo / DynamoDB instead.
  • Serverless functions — the LMDB file isn’t shared across cold starts; use a network driver.
  • You only need an in-memory cache — Map is simpler. Use this if you want the persistence too.

See also