🧩 Agentic Scaling how consumer AI scales · 1 → 1B

Memory — how an assistant remembers a person, and a billion of them

A person tells their assistant something once — an allergy, a habit, a project they are anxious about — and expects it to still be true next week. That expectation is the whole product. This chapter is how a system keeps that promise for one person, then for a billion, without one person's memory ever touching another's.

The thesis, said this chapter's way: memory is not a longer transcript. It is two stores with two access patterns, a hard boundary per person, a write path that never slows the answer, and a read path that ranks by relevance and recency. Scale is then a storage question, not a redesign.

📖 Story: why "just keep the chat history" fails

The first version of every assistant remembers by replaying the conversation. By the second week the transcript no longer fits the context window, "I have a severe peanut allergy" is buried under three hundred lines about typography, and every turn costs more than the last because the whole history is re-sent as tokens. Summarizing the history quietly drops the allergy. The fix that survives is to treat memory as data, not text: exact facts looked up by key, episodes retrieved by meaning, both written on purpose after each turn rather than dragged along inside the prompt.

Why are there two kinds of memory?

Because two kinds of question are being asked of the store, and they want opposite guarantees.

Profile memory Episodic memory
Holds identity and standing facts: name, city, allergy, preferred tone things the person said or did, one row per episode
Fetched by keyed lookup — a primary-key read, always returned vector search — top-K by meaning, ranked
Guarantee exact, complete, deterministic approximate, best-effort, budgeted
Changes rarely, and through explicit edits every turn, through write-back
Failure if mixed an allergy loses a similarity contest to a hiking story a dozen hiking stories bury the single new fact

The rule that falls out: identity is never left to similarity. A fact that must be true on every turn is stored where it is returned on every turn; anything merely likely relevant is stored where relevance is computed. Personalization shows how the two fuse into one context pack; this chapter is about the stores behind them.

🪤 Misconception — "one big vector index for everything the user ever said; let the ranker sort it out." Similarity finds the five most related episodes well. It cannot make sure the allergy is present. Rankers have no notion of must.

Where does one person's memory end?

At the memory namespace. Every memory row carries its owner's identifier, and every retrieval is filtered by that identifier before it ranks anything. A worker serving Maya cannot phrase a query that returns Raj's rows: the store applies the filter; the caller is not trusted to.

This is the most important line in the whole memory design, for three reasons:

Shared knowledge — how-tos, facts about the world, product documentation — lives in a separate corpus with no user filter, because it belongs to nobody. A personal fact can therefore never leak into shared results, and since scores from two corpora are not comparable, the pack budgets each separately rather than merging them into one sorted list.

flowchart LR
    W[Stateless worker] -- "user id → namespace filter" --> M[(Maya's namespace)]
    W -. never .-> R[(Raj's namespace)]
    W -- "no filter" --> C[(Shared corpus)]
    M --> P[Context pack: personal budget]
    C --> G[Context pack: general budget]

How does memory get written without slowing the answer?

Through write-back, off the hot path. The person is already reading a streamed reply while the system decides what, if anything, this turn taught it.

flowchart LR
    T[Turn answered] --> S[Reply streams to the person]
    T -. enqueue .-> Q[(Async queue)]
    Q --> X[Extract durable facts]
    X --> D{Already known?}
    D -- exact or near-duplicate --> K[Skip]
    D -- new --> E[Embed]
    E --> I[(Write into the user's namespace)]
    I --> N[Visible to the next turn]

Each stage has a reason to exist:

⚠️ Pitfall — running write-back inline "just for now." Harmless at ten users; at a million it puts two extra model calls on every response, and p95 latency becomes the write path's problem.

How does the read path choose what to recall?

Two lookups run in parallel, a re-score follows, and a budget decides what survives.

flowchart TB
    Q[Rewritten query] --> K[Keyed lookup: profile facts]
    Q --> V[Vector search inside the namespace]
    V --> D[Re-score with temporal decay]
    D --> B1[Personal budget: top-K]
    Q --> G[Shared corpus search]
    G --> B2[General budget: top-K]
    K --> P[Context pack]
    B1 --> P
    B2 --> P

Temporal decay is the load-bearing detail. Raw similarity has no clock: a preference stated yesterday and a contradictory one from three years ago score identically if they use the same words. So the read path multiplies similarity by an exponential of age, with a gentle λ — recency breaks ties without erasing history:

TEXT
hits  = vector_search(namespace=user, query_vec, k=6)
for h in hits:
    h.final = h.similarity * exp(-LAMBDA * h.age_days)
personal = top(sorted(hits, by=final), k=4)

The budget is the other half. Because personal and shared scores are not calibrated against each other, the pack reserves slots for each (say three personal, two general) instead of sorting a fused list, so a brilliant shared document cannot evict the one personal fact that made the answer feel written for this person. How the split is trimmed to a token budget is in personalization.

🔍 See it happen — one fact, written Tuesday, recalled Saturday

Tuesday. Maya (a fictional persona) types: "I really love hiking and want to go again this weekend." The reply streams immediately. Behind it:

  1. The turn is queued for write-back.
  2. Extraction proposes "Maya wants to hike this weekend" and "Maya loves hiking".
  3. Dedup: the second is a near-duplicate of an existing row ("hikes Mount Tam most Saturday mornings") and is skipped. The first is new.
  4. It is embedded and written into Maya's namespace with a written-back tag and a timestamp.
  5. Her session's hot mirror records last_learned: "wants to hike this weekend".

Saturday. Maya asks: "Anything fun I should do today?"

  1. Keyed lookup returns the profile: San Francisco, peanut allergy, warm tone — every time, no ranking.
  2. Vector search inside her namespace returns six episodes; the Tuesday row scores 0.81 × e^(−0.01·4) ≈ 0.78, the older Mount Tam row 0.79 × e^(−0.01·200) ≈ 0.65. Recency wins the tie.
  3. The shared corpus contributes a trail-safety note.
  4. The pack ships with three personal facts and two general ones; the model suggests a trail and remembers the allergy when it mentions the trailhead café.

Nothing in this trace says which machine served Tuesday or Saturday. That is the next chapter's point.

Why does memory compound — and why must it be pruned?

Every useful turn adds a row; every later turn can recall it. Over months the assistant genuinely knows the person, at no extra cost per turn, because retrieval is top-K, not "everything." That is compounding, and it is the moat of a personal assistant.

Unbounded growth is the flip side. Dedup slows it; it does not stop it. Production systems add a periodic memory consolidation pass per namespace: merge overlapping episodes, retire rows whose decayed score can no longer reach the budget, and promote a fact that keeps recurring ("prefers short answers") from episodic memory into the profile, where it is guaranteed rather than ranked. It runs on the same async queue as write-back, at a slower cadence.

Forgetting must be as deliberate as remembering. A "forget me" request deletes the namespace — every episode, every written-back fact, the session mirror — as a single operation, because a memory that survives deletion in one store is a privacy incident, not a bug.

🎯 At consumer scale the write side is its own fleet. A hundred million people generating a few durable facts a day is a steady stream of extraction and embedding calls that never touches the chat path. It scales on queue depth, tolerates minutes of lag, and runs on the cheapest adequate model — because nobody is waiting on it.

How does the store grow from one person to a billion?

In three moves, each with a specific trigger — and the query the worker runs does not change at any of them.

Stage Shape of the store What triggers the move
One relational store with a vector index profiles, memory rows, embeddings and the audit trail in one database; per-user recall is filter by namespace, then rank a few thousand rows day one — and it stays correct for per-user memory far longer than intuition suggests, because every query is scoped
Replicas and a hot cache read replicas take the recall traffic; profiles and sessions are cached in front read volume exceeds one primary; p95 recall latency starts moving with user count
Dedicated vector services the shared, unscoped corpus moves to a purpose-built approximate-nearest-neighbour service; per-user namespaces stay where they are the shared corpus passes roughly ten million vectors — an unfiltered query must search everything, and exact scans stop being cheap
Sharded by user namespaces are distributed across shards by user id; each shard is a full relational-plus-vector store; a region pin per user decides which shard write throughput on the primary; and data residency — a person's memory must live in a chosen region

Notice that the shared corpus and the per-user namespaces graduate at different times, for different reasons — which is why keeping them separate on day one matters. And sharding by user is a routing change, not a schema change: a stateless worker already resolves user id → namespace; sharding adds namespace → shard, and the reference architecture shows that same lookup selecting a region.

🧭 Enterprise mapping — a memory namespace becomes a tenant data boundary, and the filter-before-rank rule is what an auditor asks to see. Write-back becomes a governed pipeline: what may be extracted is a policy, retention is a contract, and "forget me" is a legal obligation with a deadline. Sharding by user becomes sharding by tenant with region pinning — how residency promises are kept. The shared corpus becomes the enterprise knowledge base with its own ingestion, ranking and access control, and the rule that it never holds a person's private facts is enforced, not assumed.

Where next

Memory answers what a worker pulls for a person. The next chapter answers how a fleet of identical, stateless workers serves everyone at once, and how the control plane keeps exactly as many running as the moment needs. For the cost of the model calls write-back and recall make, see model serving; for keeping recalled memory from being confidently wrong, see quality and the glossary.