Run, Session, or Global? Scoping Agent Memory Correctly
Too-small a scope produces a visible annoyance; too-large produces a silent wrong answer. One question assigns any fact correctly, and one rule keeps multi-tenant reads from becoming an incident.
Most agent memory bugs are scope bugs. Something that should have been forgotten at the end of a task persists into the next one; something that should have persisted was dropped when the conversation ended; something learned about one user surfaces for another.
Three scopes cover nearly everything, and assigning each piece of state to the right one prevents an entire category of failure.
The three scopes
Run scope lives for one task and is discarded. Intermediate results, the current plan, which items are done, what's been tried. Nothing here should outlive the run — this is working memory, and its value is over when the task ends.
Session scope spans a conversation or a working period. What the user is currently working on, decisions made in this conversation, the file they're focused on, the ambiguity they resolved twenty minutes ago. Ends when the conversation does.
Global scope persists indefinitely, attached to a user, an account, or an entity. Stable preferences, durable facts, decisions with lasting consequence.
The assignment test
For each piece of state, ask: what's the smallest scope in which this remains true and useful? Put it there.
The bias should be toward the smallest workable scope, because the failure modes are asymmetric. Too-small scope produces a visible annoyance: the agent re-asks something. Too-large scope produces a silent wrong answer: the agent applies a stale fact from a context where it no longer holds, and nothing signals that it happened.
Examples of the error in each direction:
- ❌ Run state promoted to global: "the user wants the report in EUR" — true for that one task, wrong as a standing preference.
- ❌ Global state trapped at run scope: the user corrects a persistent preference and is asked again tomorrow.
- ❌ Session state at global scope: "currently investigating the checkout bug" surfacing three weeks later as though still current.
The promotion path
Facts should move between scopes deliberately, not accumulate at whatever scope they were first observed.
The useful rule: promote on repetition or on explicit statement. If a user says "always use EUR," that's a global statement. If they've chosen EUR in five consecutive sessions, that's evidence worth promoting. A single in-task choice is neither, and treating it as one is how preference stores fill with things nobody asked to be remembered.
Demotion matters too. A global fact contradicted at session scope should be handled explicitly — the session wins for now, and either the global is updated or it isn't, but the conflict shouldn't be resolved by whichever the retriever returned first.
🔍 The isolation boundary
Multi-tenant systems need one rule enforced structurally rather than by prompt: memory reads must be scoped by tenant at the query layer, not filtered afterwards.
A retrieval that fetches across tenants and filters the results is one bug away from a serious data leak, and it's exactly the kind of bug that surfaces as an anomaly nobody can reproduce. Scope the query itself — a tenant predicate in the WHERE clause, or a per-tenant index — and make the cross-tenant read impossible rather than filtered.
✅ Worth an eval case whose expected outcome is empty: user A's agent asked about user B's data, asserting nothing comes back. Cheap to write, and it fails loudly the day someone refactors retrieval.
Making scope visible in context
When memory enters the context, label its scope and age:
## What I know
[preference, since 2026-03] Reports in EUR
[this session] Investigating the checkout latency issue
[this run] Checked services: payments, ledger. Remaining: cdn
Two benefits. The model handles conflicts better with the labels present — a session fact visibly overriding a global one is easier to reason about than two undifferentiated statements. And you can read the block and immediately see when something is at the wrong scope, which is far quicker than inferring it from behavior.
The lifecycle nobody writes
Every scope needs an end. Run state is discarded on completion — including on failure, which is where it usually leaks. Session state needs a defined end: explicit close, inactivity timeout, or a new task. Global state needs review horizons and supersession.
⚠️ The commonest leak: a failed or abandoned run whose state never gets cleaned up, then gets picked up by the next run as though current. Clean up on all exits, not just successful ones.
The takeaway
Ask what's the smallest scope where each fact stays true, and put it there. Promote deliberately on repetition or explicit statement, scope multi-tenant reads at the query layer rather than filtering after, label scope and age when memory enters context, and make sure every scope has a defined end — including the failure path. Most "the agent remembered the wrong thing" bugs are one of these five, and they're all cheap compared to finding them in production.