Memory isolation with row-level security, not application code

Caller identity comes from auth, each memory carries its scope, and Postgres row-level security enforces the boundary. Nothing in the request body can cross it.

Illustration: A tall filing cabinet with many drawers, each shut with a small padlock except one.

CoreSpeed memory is isolated by Postgres row-level security. The caller's identity comes from the credential on the request, every memory carries an organization and a scope, and the database applies the visibility rule to each query. An agent cannot supply a different user id, and application code cannot forget the check, because the check lives below it.

The boundary lives below the application
Prompt and tool call
memory__search_memory takes a query. No field for a user id exists.
untrusted text
Credential
a browser sign-in, an sk-cs- key, an sk-csa- key or a session JWT resolves to one principal
identity from auth
Application check
filters by principal and scope; produces the clear error messages
clear errors
Postgres row-level security
the policy is attached to the table and applied to every statement, whichever code path issued it
every query, every path
Rows
each memory carries an organization and a scope: private or shared
org + scope
The check lives below the application code, so a missed check cannot leak.

Where does the caller's identity come from?

From auth, and only from auth. Four credentials reach /mcp, and each resolves to a principal before any tool runs:

Authorization: Bearer <token>    a browser sign-in, an sk-cs- key, an sk-csa- key, or a session JWT
x-api-key: <key>                 sk-cs- and sk-csa- keys only

A browser sign-in is you, a member. An sk-cs- key acts as the member who created it. An sk-csa- key is an agent principal with an identity of its own, owned by the member who answers for it. The organization that owns the credential is the organization the memory belongs to.

What is absent is any identity field in the tool call. memory__search_memory takes a query. memory__remember takes text and a scope. Neither takes a user id, so a prompt cannot hand the agent one.

How do shared and private scopes work?

Every write is scoped. memory__remember accepts scope: "shared" or "private". Private is the default and can be read only by the member who created it. Shared must be asked for explicitly, and can then be read by every agent operating inside the same organization. The memory page is the reference.

CredentialWrites asReads
browser sign-inthe memberthe member's private memories plus the organization's shared ones
sk-cs- member keythe member who created the keythe same as that member
sk-csa- agent keythe agent principalthe agent's own memories plus the organization's shared ones

Member state is organization-scoped. Turning memory off in one organization cannot affect the same person in another organization. An agent principal's memories are its own, and the member accountable for that agent maintains them directly, which is how an owner corrects what a bot has saved.

Links follow the same rule. memory__list_memory_links lists only links whose two memories you can see. A link to a teammate's private memory stays invisible to you, as that memory does.

What does row-level security add over an application check?

A visibility check written in application code is a filter that every query path must remember to apply. Search, list, get by id, link listing, export, the maintenance job that archives stale rows: each is a place where one missing clause returns another member's memory. A test suite catches the paths it knows about.

Row-level security moves the rule into the database. The policy is attached to the table, and Postgres applies it to every statement that touches the table, whichever code path issued it. A query without the clause does not return more rows. It returns the same rows the policy allows, because the predicate is added underneath.

The docs state the property in one sentence: the boundary is enforced in Postgres by row-level security, not only in application code. Application code still checks, and that layer is what produces clear errors. The database is what makes a missed check a wrong error message rather than a leak.

Why not trust the request body?

Because the request body is written by a model that reads untrusted text. A fetched page, a post or a document can carry an instruction, and an agent can follow it. If the memory API accepted a user id in the call, "search the memory of user X" would be one injected line away.

Deriving identity from the credential closes that line. The worst an injected instruction can do is search what the caller could already search. That is the same reason the security guidance tells you to treat fetched content as untrusted and to give unattended agents an agent key: the credential, and nothing in the conversation, decides the reach.

How was the boundary tested?

The published LongMemEval run doubled as an isolation test. All 500 questions of LongMemEval-S ran with each question in its own isolated workspace, and each workspace held a private tripwire memory owned by a different user. One leak would fail the entire run. It finished at 0 leaks out of 500, with retrieval running the production hybrid path under row-level security.

The headline number from that run, 94.2 end-to-end with a flash-class reader while retrieval recall at 10 stayed 0.99, is the subject of its own post. The isolation result is the quieter one, and for a shared memory service it is the one that has to hold first.

FAQ

Can an agent read a teammate's private memories? No. Private memories are readable only by the member who created them. An agent principal reads its own memories and the organization's shared ones.

What if a prompt tells the agent to search as another user? There is no field for that. Identity comes from the credential, so the search runs as the caller, whatever the prompt says.

Is a shared memory visible across organizations? No. Memory belongs to the organization. Shared means shared inside that organization's boundary.

Does disabling the memory capability delete anything? No. Disabling hides the memory tools from tools/list and keeps the data. Deletion is a separate explicit action, in the dashboard or through memory__delete_memory.