← All posts

Per-key TTL: agent memory that forgets

OriginChainDB Team · May 5, 2026
ttl agent-memory ephemeral design tutorial

TL;DR - Every OriginChainDB write can carry a ttl in seconds. The key stops appearing in reads the moment it expires, and the substrate compacts it away on its own schedule - so tool-call traces, session caches and idempotency keys clean themselves up with no sweeper job and no cron. TTL is per-key, not per-shape, and the maximum is 365 days.

The problem with permanent agent memory

If you store every tool call your agent makes - and you should, for retrieval and reproducibility - you accumulate millions of records per active user. Most of them are useless after 24 hours. The reasoning trace from a failed attempt three weeks ago has zero retrieval value today.

The naive approach is to write everything and run a daily DELETE job. This works at small scale and breaks at large scale: the DELETE has to scan, lock, and free space, and on a heavily-loaded store this becomes operationally annoying. You end up running it during off-peak hours, watching for it to take 3x longer than expected, and resigning yourself to “the cleaner is broken again.”

The right answer is per-record expiry that the substrate enforces transparently.

How TTL works in OriginChainDB

Every write can carry a ttl (time-to-live) in seconds. The key becomes invisible to reads after the TTL expires; the substrate compacts it away on its own schedule.

// Tool-call trace, expires in 1 hour
await oc.put({
 shape: "tool-call",
 id: callId,
 set: { tool: "search", args, result, latency_ms },
 ttl: 3600,
});

// User session cache, expires in 30 min
await oc.put({
 shape: "session-cache",
 id: sessionId,
 set: { state },
 ttl: 1800,
});

// Embedding refresh marker, expires in 24h
await oc.put({
 shape: "stale-marker",
 id: entityId,
 set: { triggered_at: now },
 ttl: 86400,
});

After the TTL, reads return “not found.” No application code needs to check expiry; no cron job runs. The compaction is the substrate’s responsibility.

What this lets you build

Tool-call memory with automatic forgetting. Store every tool call your agent makes with a 24-hour TTL. The retrieval pipeline searches over the recent window only, automatically. No “forgetting” logic in the application.

Session-scoped state. Push intermediate reasoning state with a session-length TTL. When the session ends, it cleans itself up. No “did the user explicitly logout” detection needed.

Replay protection. Idempotency keys for incoming requests get a 5-minute TTL. The same request retried within that window deduplicates; outside it, the substrate doesn’t keep state forever.

Cache layers. OriginChainDB isn’t a cache, but TTL’d shapes work like one for AI-feature output. Store an LLM-generated response with a 1-hour TTL keyed on the input hash; the next identical input gets the cached answer for free.

Embedding staleness markers. When an entity changes, write a stale-marker shape with a long TTL. A background job sweeps stale markers and re-embeds. If the entity hasn’t changed during the TTL window, the marker auto-expires and you don’t re-embed needlessly.

TTL semantics, precisely

What TTL doesn’t do

It’s not a scheduling primitive. Don’t use TTL to “trigger” a background job after some interval - there’s no callback. If you need that, write a marker and run a sweeper.

It’s not a billing-period primitive. TTLs are seconds-from-write, not “expires at end of month.” If you need calendar-aligned expiry, write a marker shape with a precise expires_at timestamp and check it in application code.

It’s not a delete-on-event primitive. TTL is purely time-based. To delete on a non-time event (user closes account, etc.), call DELETE explicitly.

Performance characteristics

The TTL machinery adds zero overhead to writes - it’s a few extra bytes per record. Reads check the expiry timestamp inline; this costs about as much as a single attribute access (negligible).

The cost is on the compaction path: the substrate has to scan and reclaim expired keys eventually. For workloads that write millions of TTL’d records per day, this is real work - but it runs in the background, throttled to avoid impacting foreground throughput. We tune it conservatively by default.

FAQ

What is per-key TTL?

Per-key TTL is a database feature where each individual record can carry its own expiry timestamp. After the TTL, the record becomes invisible to reads, and the substrate compacts it away in the background. It’s the standard pattern for ephemeral data - caches, sessions, idempotency keys.

How does this differ from Redis TTL?

Redis is purely in-memory; OriginChainDB TTL is on durable storage with the same durability semantics as any other write. You get TTL without giving up persistence guarantees.

Can I extend a TTL?

Yes - the next write to the key resets the expiry to “write-time + TTL.” A common pattern for session refresh.

What’s the maximum TTL?

Bounded by the substrate at 365 days. If you want longer expiry than that, store an explicit timestamp and DELETE on a sweeper.

Does TTL apply to vector shapes?

Yes. A vector with TTL becomes invisible to ANN search after expiry. Useful for ephemeral embeddings of session state.


← All posts Subscribe to RSS →