> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scoping Memory by User, Agent, or Session

> Serve many end users from one space, without their memories mixing, using user_id, agent_id, and session_id.

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`:

```bash cURL theme={null}
curl -X POST https://api.anonalabs.com/v1/record \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "space_id": "support",
    "content": "Prefers email over phone.",
    "user_id": "alice"
  }'
```

```python Python theme={null}
client.record(
    space_id="support",
    content="Prefers email over phone.",
    user_id="alice",
)

client.record(
    space_id="support",
    content="Escalates billing questions to a human.",
    user_id="bob",
)
```

```typescript TypeScript theme={null}
await anona.record({
  spaceId: "support",
  content: "Prefers email over phone.",
  userId: "alice",
});

await anona.record({
  spaceId: "support",
  content: "Escalates billing questions to a human.",
  userId: "bob",
});
```

> [!NOTE]
>
> `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):

```bash cURL theme={null}
curl -X POST https://api.anonalabs.com/v1/retrieve \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "space_id": "support",
    "query": "how should we contact them?",
    "user_id": "alice"
  }'
```

```python Python theme={null}
results = client.retrieve(
    space_id="support",
    query="how should we contact them?",
    user_id="alice",
)
# Only Alice's memories come back. Bob's never appear here.
```

```typescript TypeScript theme={null}
const results = await anona.retrieve({
  spaceId: "support",
  query: "how should we contact them?",
  userId: "alice",
});
// Only Alice's memories come back. Bob's never appear here.
```

## 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](https://docs.memory.anonalabs.com/api-reference/models.md); for agents that must never
    share anything, use separate spaces (see
    [two agents, one space](https://docs.memory.anonalabs.com/use-cases/two-agents-one-space.md)).

## Choosing which key

| Key | Use for |
| - | - |
| `user_id` | The end user of your product. Almost always the one you want. |
| `agent_id` | Which of *your* agents or bots produced the memory, when you run more than one. |
| `session_id` | One conversation or run, when you need per-conversation isolation *within* a user (rare: most apps want memory to persist across sessions, which is what `user_id` alone gives you). |

## 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:

```bash cURL theme={null}
curl https://api.anonalabs.com/v1/chat/completions \
  -H "Authorization: Bearer anona_live_YOUR_KEY" \
  -H "X-Anona-Space-Id: support-bot" \
  -H "X-Anona-User-Id: alice" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "What plan am I on?"}]}'
```

```python Python theme={null}
from openai import OpenAI

client = OpenAI(
    api_key="anona_live_YOUR_KEY",
    base_url="https://api.anonalabs.com/v1",
    default_headers={
        "X-Anona-Space-Id": "support-bot",
        "X-Anona-User-Id": "alice",
    },
)
```

```typescript TypeScript theme={null}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "anona_live_YOUR_KEY",
  baseURL: "https://api.anonalabs.com/v1",
  defaultHeaders: {
    "X-Anona-Space-Id": "support-bot",
    "X-Anona-User-Id": "alice",
  },
});
```

See [Migrating to the drop-in proxy](https://docs.memory.anonalabs.com/guides/migrating-to-proxy.md) 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](https://docs.memory.anonalabs.com/api-reference/user-profiles.md) 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](https://docs.memory.anonalabs.com/api-reference/user-profiles.md)**
>
> Read back everything a space knows about one scoped user.

> **[Retrieve API](https://docs.memory.anonalabs.com/api-reference/retrieve.md#scoping-within-a-space)**
>
> The full scoping reference, including tag filters.

> **[Core Concepts](https://docs.memory.anonalabs.com/concepts.md)**
>
> Spaces vs. scope, in one page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.