Frontier Engineering
← Context & Memory

Working vs. Durable Memory

Also known as: scratchpad and store, session vs. long-term memory, externalized state

Separate the scratch state a task needs right now from the facts that should outlive the session, and store each differently.

9 min read

The problem

"Memory" gets treated as one undifferentiated thing, usually a vector store everything is dumped into. The result fails in both directions. Transient scratch — intermediate results, a step counter, a half-built list — gets persisted and pollutes future sessions with stale detail. Durable facts — the user's timezone, their stated preferences, their role — live only in a conversation and vanish the moment it ends, so the user is asked the same question every week.

The pattern

Model two stores with different lifecycles and different write policies. Working memory is scoped to a run, holds arbitrary scratch state, is written freely and discarded at the end. Durable memory is scoped to a user or organization, holds a small curated set of stable facts, is written deliberately through an extraction step, and is loaded into context at the start of every session.

Use when

  • The product spans multiple sessions and users notice being asked the same things repeatedly
  • Agents perform long tasks needing scratch state larger than the context window
  • You are already dumping everything into a vector store and retrieval quality is degrading
  • Preferences, constraints, or profile facts should persist but conversation detail should not

Avoid when

  • Single-session, stateless products where nothing should persist at all
  • Privacy or regulatory constraints make persisting user facts undesirable
  • The task fits comfortably in context and finishes in one session

Tradeoffs

  • GainDurable memory stays small and high-signal because scratch never enters it

    CostTwo stores with different semantics, and a rule for what qualifies as durable

  • GainWorking memory can be arbitrarily large — it lives outside the context window

    CostThe agent must learn to read and write it deliberately rather than just remembering

  • GainDurable facts are inspectable and correctable by the user

    CostAn extraction step per session, plus conflict resolution when facts change


The distinction is lifecycle, not storage technology. Working memory is deleted when the run ends; durable memory is meant to be true next month. Anything you cannot confidently place in one of those two buckets probably does not belong in memory at all.

The distinction is lifecycle, not storage technology.

Working memory: a scratchpad with tools

Working memory is best exposed as plain read/write/list tools over a run-scoped namespace. Files or key-value entries both work. Its value is that it removes the ceiling on task state: an agent processing four hundred records keeps its progress and partial results outside the window, and its context stays small no matter how long the run goes.

class WorkingMemory:
    """Run-scoped. Freely written, never promoted, deleted when the run ends."""

    def __init__(self, run_id: str):
        self.ns = f"run:{run_id}"

    def write(self, key: str, value: str) -> str:
        kv.set(f"{self.ns}:{key}", value, ttl=timedelta(hours=24))
        return f"wrote {key} ({len(value)} chars)"   # a receipt, not an echo

    def read(self, key: str) -> str:
        return kv.get(f"{self.ns}:{key}") or f"{key} is not set"

    def list(self, prefix: str = "") -> list[str]:
        return kv.keys(f"{self.ns}:{prefix}*")

    def discard(self) -> None:
        kv.delete_prefix(self.ns)
tip

Have the write tool return a receipt rather than echoing the content back. Echoing puts everything you just externalized straight back into the context, which defeats the purpose entirely — a surprisingly common bug.

Durable memory: extract, do not accumulate

The mistake with long-term memory is writing continuously — every turn embedded and stored — which produces a store full of transient noise that drowns the few facts worth keeping. Extract instead: at the end of a session, run one pass that proposes durable facts against explicit criteria, and write only those.

class DurableFact(BaseModel):
    kind: Literal["preference", "profile", "constraint", "relationship"]
    fact: str
    confidence: float
    source_turn: int


EXTRACT = """Identify facts from this session that will still be true and useful in
a month.

Qualifies: stated preferences, role and team, recurring constraints, systems they
work with, how they like output formatted.
Does not qualify: anything about this specific task, transient states, one-off
requests, anything the user is likely to change tomorrow.

If nothing qualifies, return an empty list. Most sessions produce zero or one fact."""
warning

"Most sessions produce zero or one fact" is doing real work in that prompt. Without it, extractors return eight facts per session and durable memory becomes the noisy dump you were trying to avoid.

Loading and reconciling

Durable memory is small enough to load wholesale into the system prompt — a few dozen lines, not a retrieval call. Because it is small, contradictions are detectable: when a new fact conflicts with a stored one, supersede rather than append, keeping the old value in history. A store holding both "prefers Python" and "prefers Go" is worse than a store holding neither.

def commit(user_id: str, facts: list[DurableFact]) -> None:
    existing = memory.for_user(user_id)
    for f in facts:
        if f.confidence < 0.7:
            continue
        if (conflict := find_conflict(existing, f)):
            memory.supersede(conflict.id, f, reason="contradicted in later session")
        else:
            memory.add(user_id, f)


def system_prompt_for(user_id: str) -> str:
    facts = memory.for_user(user_id, limit=40)
    if not facts:
        return BASE_SYSTEM
    return BASE_SYSTEM + "\n\nWhat you know about this user:\n" + bullets(f.fact for f in facts)
  • Working memory: run-scoped, freely written, TTL'd, never promoted automatically
  • Durable memory: extracted deliberately at session end against explicit criteria
  • Write receipts, not echoes, or externalized state comes straight back into context
  • Supersede contradictory facts instead of accumulating both
  • Keep durable memory small enough to load whole — if it needs retrieval, it has become a corpus
tip

Let users see and edit their durable memory. It is the cheapest trust feature in a memory-enabled product, it turns a creepy surprise into a controllable one, and user corrections are excellent training signal for the extractor.