Short answer: Scoped memory is memory attached to a clear owner boundary: a user, session, project, or similar key, not a global pile.
Real systems serve many users and many conversations at once. Scope decides who a memory belongs to and who can see it. Per-user, per-session, and per-project scopes keep preferences, temporary state, and shared work from leaking into the wrong context.
The last few chapters have described several dimensions along which a memory can vary, shared or private, bounded or unbounded, a fact or a narrative, without spending much time on a more basic question sitting underneath all of them: to whom, exactly, does a given piece of memory belong? “The user” is too coarse an answer once a real system is handling many users, many conversations, and many projects at once, and getting the granularity of that answer wrong is one of the most common ways a memory system ends up leaking information it should never have surfaced.
What Does “Scope” Actually Mean for a Stored Memory?
A scope is the specific identifier, or combination of identifiers, that determines which requests are allowed to see a given memory. The three most common levels are per-user, meaning the memory belongs to one identifiable person across everything they ever do; per-session or per-conversation, meaning it belongs to one specific interaction and nothing beyond it; and per-project, meaning it isn’t tied to any individual at all and is instead shared across everyone using a particular application or workspace.
These aren’t just three flavors of the same idea, they’re genuinely different answers to genuinely different questions. Per-user scope answers “what does this person, specifically, know or prefer, no matter which conversation they’re in?” Per-session scope answers “what’s true only within this one specific exchange, and nowhere else?” Per-project scope answers “what should everyone using this system benefit from, regardless of who they individually are?”
Why Does Picking the Wrong Granularity Cause Real Problems?
Scoping something too broadly is the more dangerous mistake. A detail that should have stayed confined to one specific conversation, an offhand comment, a temporary state, a fact that stopped being true the moment that exchange ended, suddenly surfaces in an entirely unrelated conversation weeks later, confusing or even embarrassing the person it resurfaces in front of. This isn’t a hypothetical edge case, it’s the direct, predictable consequence of attaching something to a `user_id` when it should have been attached to a narrower, session-specific identifier instead.
Scoping something too narrowly has a quieter cost, but it’s still a real one. A preference that genuinely belongs to a person as a whole, not just to the specific conversation where they first mentioned it, gets trapped inside that one session and never resurfaces again once the conversation ends, forcing the same information to be re-explained from scratch every time a new session starts. Neither mistake announces itself loudly. Both just quietly produce a system that behaves worse than it should, in ways that are easy to miss until a user actually notices.
How Do You Decide Which Granularity a Given Piece of Information Actually Needs?
The deciding question is almost always about lifespan and ownership together: does this fact belong to the person, following them wherever they go next, or does it belong specifically to this one exchange, expiring the moment the exchange is over? A stated preference for how someone likes explanations phrased belongs to the person. A detail about what’s currently being discussed in one particular support ticket belongs to that ticket, and has no obvious reason to follow the same person into an unrelated conversation about something else entirely.
Project-wide scope earns its place whenever the information isn’t really about any individual at all, a shared procedure the whole team benefits from, or a piece of general knowledge that should be available no matter who happens to be asking. None of these three levels is inherently the “default” one. Each kind of content has to be evaluated against the actual question of who it belongs to, rather than everything getting funneled automatically into whichever scope happens to be easiest to reach for.
Can a Single Piece of Content Need More Than One Scope at Once?
It can, and this is more common than it first sounds. A conversation summary is a clear example: it needs to be tied to a specific user, since it shouldn’t be visible to anyone else, but it also needs to be tied to a specific conversation, since a person having five separate conversations shouldn’t have all five summaries collapse into one undifferentiated blob. Getting this right means combining two scope identifiers at once, one narrowing by person and a second narrowing further by which specific exchange is being referred to.
This combined case is exactly why treating scope as a single fixed dimension, “is it shared or is it private,” undersells how the decision actually works in practice. Real systems frequently need to combine a required identifier, like the user, with an additional, optional one, like the conversation, and the right answer depends on the specific content type being stored, not on a single system-wide rule applied uniformly to everything.
How Does Weaviate Engram Let Scope Be Composed Rather Than Fixed?
Weaviate Engram treats scope as something a topic declares rather than something baked permanently into how the whole system behaves. A topic can require a `user_id`, additional custom `properties` like a `conversation_id`, both together, or neither at all, and that declaration determines exactly what has to be passed at both storage and search time. Consider a corporate learning-and-development platform helping employees work through training modules, where a durable skill-level fact belongs to the employee alone, while a specific coaching note belongs to one particular course session:
from engram import EngramClient
client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
client.memories.add(
"Already comfortable with basic spreadsheet formulas; struggles specifically with pivot tables and needs them explained slowly.",
user_id="employee-77410",
)
This fact should follow the employee into every future training module they take, regardless of which specific course session produced it, so only `user_id` is attached. A coaching note tied to one specific session of one specific course, by contrast, needs a second identifier layered on top so it doesn’t bleed into unrelated sessions:
client.memories.add(
"In this session, the employee got confused between VLOOKUP and INDEX-MATCH and asked for a side-by-side comparison.",
user_id="employee-77410",
properties={"course_session_id": "excel-intermediate-run-14"},
)
results = client.memories.search(
query="What has this employee struggled with across all their training so far?",
user_id="employee-77410",
)
Because the search omits `properties`, it reaches across every session this employee has ever had, surfacing both the durable pivot-table struggle and the session-specific VLOOKUP confusion together. A narrower search that does include `properties={“course_session_id”: “excel-intermediate-run-14”}` would return only what happened in that one specific session, leaving every other session’s notes untouched. Neither call requires guessing which scope is the “right” one in the abstract, because the topic configuration and the parameters passed at request time make the boundary explicit every single time, rather than leaving it to be inferred or, worse, assumed.
Scoping settles who a memory belongs to and how far it’s allowed to travel. It says nothing about whether that memory still reflects reality once time has passed, since a fact that was true and correctly scoped the day it was written can quietly stop being true without anything about its scope ever changing. Our next chapter, What is temporal memory?, turns to exactly that problem.