TTemporalStore.AI GitHub

Data model

Long Sequence Feature: ordered history at scale.

Store long, ordered behavior histories per entity and serve them with tail reads, time windows, and filters — the input rankers, agents, and investigation tools need without scanning a warehouse at request time.

What it is

The last N events for an entity, in order, ready to read.

A Long Sequence Feature is a per-entity, event-time-ordered log the engine understands as a first-class object. Each entity — a user, device, merchant, or agent session — owns its own sequence, and the store keeps the recent tail hot.

Reads come in three shapes: a tail read for the most recent N events, a windowed slice over a time range or count, and either with filters on type, source, or dimension. Older events stay recoverable but out of the hot path.

Because the store keeps the recent tail warm and reconstructs full history from WAL plus snapshots, a tail read stays sub-millisecond even when the entity has millions of events behind it.

One entity's sequence (recent → old)
Taillast N, hot cache
Windowrange / count slice
HistoryWAL + snapshots

Sharded across millions of sparse entity keys; each key keeps its own ordered tail warm.

Why TemporalStore

Why the temporal engine is the perfect fit for ordered history.

Recent-first history is deceptively hard to serve well. The three usual answers each break in a different place. A warehouse query ("last 200 events for this user, newest first") is a full-table scan that lands seconds later — fine for a report, fatal on a request path that has a few milliseconds to spare. A per-feature stream job keeps a materialized "last N" up to date, but you pay for it forever: a Flink or Spark topology, its state store, its checkpoints, and a new job every time a ranker wants a different N or a different filter. A Redis list is fast until you multiply it by millions of sparse entity keys — now it is memory you can't afford, with no windowed slices, no filters, and no history once it evicts.

TemporalStore collapses all three into one read because the sequence is the storage layout. Events are append-structured: a new event is a cheap ordered write to the WAL and the hot memory tier, never a read-modify-write, so there is no write amplification as a sequence grows into the millions. The most recent tail stays warm in cache, so a tail read is a bounded slice off the end — sub-millisecond regardless of how much cold history sits behind it.

The windowing and filtering run as model-aware executors at the shard, next to the data, not in a client that has to drag every event across the network first. Asking for "the last 50 view events in the past 24h" narrows to a bounded read and evaluates the where predicate in place; the request path never sees the other 40,000 events. Because each entity owns its own sequence, the model shards cleanly across millions of sparse keys — a hot entity and a cold one cost the same per read.

And because durability is the append log itself, history is replay-friendly: after a failover the shard reconstructs full ordered state from WAL plus snapshots, so an investigation tool can still ask "what did this entity do, in order, before the incident" with no separate archive to reconcile. One append-structured object replaces the stream job, the warehouse round trip, and the cache — new read shapes are a query, not a new pipeline.

What you'd otherwise run
Warehousescan per request
Stream jobone per feature
Redis listno window / filter
↓ collapse into one object ↓
TemporalStore sequence
Append-structured, shard-localtail hot · window + filter in place · replay from WAL

No write amplification on append; bounded reads regardless of history depth.

In practice

Append events, then read a recent slice.

Append an event to an entity's sequence
ts.append(
  table="user_events",
  entity="user_42",
  ts_ms=event.ts_ms,
  attrs={"kind": "view", "item": "sku_812"},
)
Read the recent slice a ranker needs
seq = ts.sequence(
  table="user_events",
  entity="user_42",
  last=50,
  since="24h",
  where={"kind": "view"},
)

How to use it

An end-to-end flow, from write to a ranked request.

The full loop is four moves: append events as they happen, read the tail or a windowed slice at request time, filter to the signal a model needs, and hand the last-N to a ranker. Each read is bounded, so the shape you ask for is the cost you pay.

1 · Append

Write the event

Each behavior is one ordered write to the entity's sequence — WAL plus hot tier, no read-modify-write.

2 · Tail read

Grab the recent end

The last N events off the warm tail, newest first, in sub-millisecond bounded reads.

3 · Window + filter

Narrow to signal

A time or count window with a where predicate evaluated at the shard, before anything crosses the wire.

4 · Rank

Feed the model

Pass the ordered last-N straight into a ranker or an agent context pack.

1 · Append events as they happen
ts.append(
  table="user_events",
  entity="user_42",
  ts_ms=event.ts_ms,          # event time, not wall-clock arrival
  attrs={"kind": "view", "item": "sku_812", "surface": "home"},
)

Writes are keyed by entity, so each user, device, or session lands in its own sequence — the store shards on that key across millions of entities. Always pass the real ts_ms event time; order is by event time, so late-arriving events land in the correct position rather than the tail.

2 · Tail read — the most recent N, newest first
recent = ts.sequence(
  table="user_events",
  entity="user_42",
  last=50,                    # bounded: reads off the hot tail
)

A bare last=N is the cheapest read the model offers — it never touches cold history. Keep N to what the consumer actually uses; the tail cache is sized for recent-first access, so a last-50 read costs the same whether the entity has 500 events or 5 million behind it.

3 · Windowed + filtered slice — recent views only
views = ts.sequence(
  table="user_events",
  entity="user_42",
  since="24h",                # time window; or use last=N for a count window
  where={"kind": "view"},     # predicate runs at the shard, not the client
  limit=200,                  # hard cap on rows returned
)

Combine since (or last) with where to pull just the signal a feature needs. The filter is applied in place, so a sparse event type does not force a scan of everything in the window. limit bounds the result set; for deep history, page with a before cursor from the oldest returned event rather than raising limit without bound.

4 · Feed a ranker with the last-N on the request path
def rank_candidates(user_id, candidates):
    history = ts.sequence(
        table="user_events",
        entity=user_id,
        last=200,
        where={"kind": "view"},
    )
    return ranker.score(candidates, recent_history=history)  # ordered, fresh

The ranker receives ordered, request-time-fresh behavior with no feature pipeline in between. Gotchas: keep last/limit tight on the hot path, prefer count windows for rankers (stable input size) and time windows for investigations, and page rather than widen a single read. After a failover the same reads work unchanged — the shard replays ordered state from WAL plus snapshots.

When to use it

When you need recent-first history, not a warehouse scan.

Reach for a sequence whenever a request depends on what this entity did lately, in order. It replaces per-feature stream jobs and request-time warehouse queries with a single bounded, replay-friendly read.

Use caseSequence readWhy it fits
Ranking & recommendationLast 200 interactions per userFeeds a ranker the ordered behavior signal without a feature pipeline.
Agent tool historyOrdered tool calls per sessionBacks Context Management with the exact action trail behind a decision.
Investigation trailsWindowed slice with filtersReconstructs what happened, in order, after failover or migration.
Shopping & content journeysTail read since last sessionServes the recent path without scanning cold history.

Related models

Combine sequences with the rest of the store.

FlagshipContext ManagementAgent memory built on sequences. AggregatesAggregated FeatureRollups over the same events. Counters & setsControl StateCaps and distinct sets alongside history.