Skip to main content
If your app has more than one end user, you need one space with scoped writes and reads, not one space per user. This guide walks through why, and how.

The mistake to avoid

It is tempting to create a space per end user, since a space is already an isolation boundary. Don’t:
  • Spaces are capped per plan.
  • Memories in different spaces are never synthesized together, so you lose the cross-conversation, cross-session learning a memory layer exists to give you.
One space, plus a scope key on every call, is the pattern.

Write under a scope

Add user_id (or agent_id, or session_id) to record:
user_id is a real field, not metadata. Putting a user id in metadata stores it but does not isolate anything. metadata is returned with results, never filtered on.

Read under the same scope

Pass the identical key to retrieve (or reason, or the chat proxy):

What “strict” means in practice

  • A scoped search returns only memories written under the same scope. Never another user’s, and never an unscoped memory either.
  • If a space already has history and you turn scoping on today, that old history stays visible to unscoped searches but invisible to scoped ones. Backfill the scope onto it if scoped callers need to see it.
  • Passing more than one key ANDs them: user_id + session_id returns only that user’s memories from that specific session.
  • Consolidated synthesis (note memories) is built per user, so a synthesis never blends two users’ facts, while still rolling a user’s sessions up together so the memory improves across conversations, not just within one.
  • A note carries only that one identity: the user, or the agent when the facts have no user, or the session when they have neither. It keeps its source facts’ own tags and provenance, but not their agent_id or session_id. So a fact written with user_id: alice, agent_id: support-a produces a note scoped to alice alone. Two consequences:
    • An agent_id- or session_id-scoped read sees the facts, not the note. The memory list returns those facts even under the default prefer_observations=true, so nothing is hidden behind a note the filter cannot reach.
    • A user_id-scoped read sees the note, and that note can draw on facts written by any of your agents for that user. That is the point: what you know about Alice is shared across agents. For a per-agent synthesis, use a memory model; for agents that must never share anything, use separate spaces (see two agents, one space).

Choosing which key

In the drop-in proxy

The same scope keys work as tunables on the chat/responses/messages proxy, as a body field or a header:
See Migrating to the drop-in proxy if you haven’t wired that up yet.

Reserved tags

Tags beginning with anona: are rejected with 422 reserved_tag. Scope is implemented as tags internally, and this stops a hand-written tag from forging someone else’s scope. Always use the user_id/agent_id/session_id fields, never a tag that looks like one.

Reading back one user’s history

Once you’re writing under user_id, User Profiles is the direct way to read it back: everything a space knows about one user, or a question answered from that user’s memories only, without hand-rolling a wide retrieve call yourself. It uses the same user_id scope described above, so it only ever sees what was recorded under it.

Next steps

User Profiles

Read back everything a space knows about one scoped user.

Retrieve API

The full scoping reference, including tag filters.

Core Concepts

Spaces vs. scope, in one page.