Guides

Agentic Systems

AI agent memory isolation for multi-user agents

What a shared agent's memory and credentials have to be keyed to once more than one person uses it, so one person's context cannot show up in a Slack group or another customer's workspace.

By Tirth Gajjar · Founder & CTO

14 min

At a glance

AI agent memory in a multi-user agent has to be keyed to the acting user inside the tenant, or one person's notes and OAuth grants show up in a Slack group or another customer's workspace. A scoped tool token does not prevent that, because a scope limits operations and says nothing about whose request it is. Key memory and credentials to the user and the tenant, and decide in code what a direct message may carry that a channel may not.

Bigcircle scopes what an agent can read to the entitlements of the person it acts for, and enforces that in code rather than in the prompt.

Who this is for
Engineers building AI systems and technical leads reviewing the implementation.
Topics
  • Agent Memory
  • Model Context Protocol (MCP)
  • AI Agent
  • Tool / Function Calling
  • Guardrails
  • LLM Observability & Tracing

Published Updated

AI agent memory covers everything an agent keeps beyond a single reply: the current thread, and the facts, preferences and past results it carries between runs. Once one AI agent serves more than one person, that memory and the credentials its tools use have to be keyed to the acting user inside the tenant, and a rule in code has to decide what a direct message may carry that a group channel may not. A scoped tool token is necessary and still not enough.

Key facts

Definition

AI agent memory in a multi-user agent is keyed to the acting user inside the tenant, so one person's notes and OAuth grants cannot show up in a Slack group or another customer's workspace.

How Bigcircle does it

  1. Bigcircle's Solarpunk desktop agent keeps its credentials, local retrieval and planner memory on one person's machine, so every fact and every token already belongs to the person using it.
  2. On Solarpunk, secrets are injected into the tool adapter when a tool runs and never enter the prompt; a shared agent needs the same split, with the vault keyed by tenant and user.
  3. On a current telecom engagement, Bigcircle's analytics agent sees only the tables, columns and rows the asking user is entitled to, with personal data masked or left out by policy rather than by the prompt.

Comparison

A scoped tool token limits which operations a call may run, while the memory key and the channel rule decide whose facts enter the prompt and which room sees the reply.

Key number

Slack deployed a patch on 20 August 2024 after PromptArmor showed Slack AI could be steered by a public channel into answering from private ones.

The leak does not look like a permission error, which makes it hard to see. The tool call succeeds, because the token is valid. The reply reads well, because the memory it used is a fact someone really stored. The only thing wrong is that the person reading the channel is not the person the fact was stored for, and no status code reports that.

The problem appears when an agent stops having one owner. Solarpunk, a desktop agent we built for executives, is designed so the problem cannot arise. Its credentials, its local retrieval and the planner memory that carries context between runs all sit on one person's machine, so every fact and every token already belongs to the person using it. Move the same design into a shared workspace bot and those stores become common to everyone who can mention it.

This guide covers the kinds of memory an agent stores and who owns each, the three units a shared agent has to separate, the three ways a fact or a token leaves its owner, and the keys that keep them apart. It then covers when shared memory is allowed, who may see stored memory, and how this sits in front of MCP.

What is AI agent memory, and who owns each kind?

We sort agent memory by who owns it rather than by what it holds, because in a shared agent the owner decides who may read it. Short-term, or working, memory is the context for the task in hand: the current thread, tool results and temporary state. Long-term memory persists across runs. It holds facts and preferences, past interactions and their outcomes, and instructions, workflows and policies.

Working memory belongs to one conversation. Facts, preferences and past requests usually belong to one person, because each is about whoever made it. Instructions and procedures can belong to the tenant, but only when the procedure is already visible to everyone in that workspace. Decide the owner when you design the store, not when the first leak report arrives.

A memory product does not make that choice for you. Cloudflare's Agent Memory, for example, isolates memory by profile, and a profile can be shared across sessions, agents and users. Create one profile for the whole bot and every person's facts end up in the same store, so the profile has to stand for the owner you chose.

What does each isolation unit own?

Those owners become three units in a multi-user agent, each with its own store. The user holds private facts and the credentials they granted. The tenant, meaning one customer's workspace, holds the boundary between customers. The conversation holds only the current thread. A fact written into the wrong store is later read by the wrong unit, and the read looks like ordinary retrieval.

UnitIt ownsIt must not hold
UserPreferences, private facts they asked the agent to keep, and the OAuth tokens they grantedAnother person's facts, or a token issued to the app as a whole
TenantWhich users exist, procedures already visible across the workspace, and the key prefix that stops one customer reading anotherA person's private memory, or any token that can cross to a second customer
ConversationMessages in this thread, scratch notes for this task, and which channel the thread sits inLong-term memory, credentials, or facts from a different thread

Key memory only by the conversation, and the next person in that thread can read the previous person's notes. Key it only by the tenant, and one person's preference goes into everyone's prompt. Key credentials only by the app, and every tool call runs as the bot, with channel membership as the only check.

The channel belongs to the conversation, and it decides which of the user's facts may enter the prompt on this turn. A direct message may carry that user's private notes, while a group channel may carry only what was stored for that channel. Ignore the channel and even the correct user key will put a private note into a public room.

How does memory leak between users?

Memory bleed, OAuth reuse and over-sharing into a group channel are three separate failures, and fixing one leaves the other two in place. A narrower token scope fixes none of them on its own. The figure walks one imagined bot through all three.

Three misses on one shared botIllustrative model. Two people, one process. Not an incident and not a rate.Person A, in a direct messageRemember Thursday. Connects a calendar.Person B, in a group channelAsks what the calendar holds this week.One bot processMemory key is the bot. Token is the installer's.Memory bleedThursday's note is inthe prompt built for B.OAuth reuseA's calendar is fetchedfor B's question.Group over-shareThe reply is postedin the group channel.A narrower scope on the installer's token does not move any of the three.

The figure is an illustration, not an incident or a measured rate. One person messages a Slack bot directly, asks it to remember a review on Thursday, and connects a calendar with OAuth. A teammate later mentions the bot in a public channel and asks what the calendar holds this week.

If the memory key is the bot, Thursday's note is already in the teammate's prompt. If the tool uses the first person's token, or a bot token that was granted calendar access at install, the events come back into the channel. The reply then goes to the whole room.

We hold a shared agent to one rule: it may not read, use or repeat anything the person asking could not open on their own, and private context leaves only through a direct message, a private channel or a reply only that person sees. A store that cannot tell the asking user from the previous one cannot follow that rule, however good the prompt is.

Getting the user right is still not enough if the channel can steer the answer. In August 2024 PromptArmor showed that instructions planted in a public Slack channel could make Slack AI pull content from private channels the asking user could see and the attacker could not. Slack deployed a patch on 20 August 2024 and said it had no evidence of unauthorized access to customer data.

The permission check on the data was right for the person asking. The failure was letting text from a public room shape an answer built from private ones. Check the room before the reply is posted, not only the user before the read, and treat channel text as data whenever the answer draws on anything private.

Token reuse is the quieter failure. A token can be scoped, short-lived and stored encrypted, and still be the wrong token for the person asking, because scope limits operations and carries nothing about whose request this is.

The usual form is an installer who connected once, after which every later caller uses that grant. A Slack bot token belongs to the app rather than to a person, and the app stays installed even after the installing user is deactivated, so the grant outlives the person who gave it. Choose the token by the acting user's id on every call, and use the bot token only for posts that should appear as the bot.

How do you key memory and credentials to each user?

Keep the credential and the memory on the acting user, inside the tenant, and let a channel rule decide what may leave that store. The tool call uses a token that user granted, chosen by their id, and the tenant prefix stops two customers colliding on one key. Build the prompt only after the channel rule has run.

StoreKeyWritten whenRead when
User credentialTenant and userThe user finishes OAuth, and again on refreshA tool call for a request that user started
User memoryTenant, user and factThe user asks to remember, or confirms a writeA later request by that user, in a room the channel rule allows
Channel memoryTenant and channelThe fact was posted in that channel, or a member explicitly files it thereA request in that same channel
Conversation scratchTenant and conversationDuring this threadUntil the thread ends, then discard it

The model should never receive the token. On Solarpunk we keep secrets in an encrypted vault, and they are injected into the tool adapter, the code that calls the outside system, when the tool runs, so they never enter the prompt. A shared agent needs the same split, with the vault keyed by tenant and user. The adapter attaches the token, then returns either a result or a typed refusal: a named status, such as "not connected", that the model can repeat without guessing.

On a current engagement where we are building agents for a telecom marketing platform, the analytics agent reads through a data layer that applies the client's access rules. It sees only the tables, columns and rows the asking user is entitled to, and personal data is masked or left out by policy rather than by an instruction in the prompt. That is the same rule as the user key, applied at the data instead of at the memory store.

When the request has no user id, refuse. Falling back to the bot token turns the installer's grant into everyone's grant, and a wider credential while you wait for the person to connect is the leak itself.

Refresh tokens stay on the same key as the access tokens they replace, because writing a new access token onto the app at refresh undoes the split the moment the old token expires. The guide to AI agent permissions binds each call to the person and the target, and this key is how that person is looked up.

When can a multi-user agent share memory?

Shared memory is allowed when the people in the room can already open the fact, and forbidden when sharing would widen that audience. A company procedure already visible across the workspace can live in a tenant store and be read in any channel of that tenant. The figure and the table sort facts by who may receive them, with no frequencies, because the mix depends on your workspace.

When shared memory is allowedFramework. Who may receive the fact. No frequencies, because the mix is yours.DM with ownerSame channelOther userOther tenantCompany procedurealready visibleShareShareIn tenantRefuseNote postedin this channelPointerShareMembersRefusePersonalpreferenceThat userRefuseRefuseRefuseFact storedfrom a DMThat userIf filedRefuseRefuseOAuth orrefresh tokenNeverNeverNeverNeverA token never enters the prompt. Filing a DM fact into a channel is a new publication.
FactDirect message with the ownerChannel where it was postedAnother user, same tenantAnother tenant
Company procedure already visible in the workspaceYesYesYes, inside the tenantNo
Note posted in this channelOnly as a pointer back to the channelYesOnly if they are in the channelNo
Personal preferenceYes, for that userNoNoNo
Fact the user stored from a direct messageYes, for that userNo, unless they explicitly file it into the channelNoNo
OAuth token or refresh tokenNever placed in the promptNeverNeverNever

The row teams argue over is the channel note. It stays in that channel even when the tenant matches a person outside it, because a summary posted into a larger room is a new publication. Let the agent publish it only when a member of the source channel files it under their own name.

Store a pointer to the message rather than its body, and fetch the content when it is needed. A memory that holds only the pointer gets the source's permission check again on every read, so a later change to who may see the message reaches the agent too.

A preference looks small until it pulls a document the room should not see, such as an unreleased pricing sheet. Store the preference on the user, and still ask the source whether this room may see the sheet before anything from it enters the prompt.

Every row stops at the tenant boundary. An identifier that matches across two customers refers to two different people, and the tenant prefix on the key keeps their stores apart. Check the prefix before the read, not after a result comes back.

Refuse a request with no tenant, and treat a demo workspace as a real tenant, because a demo token copied into production is one way a test user's notes become a customer's context.

Who may see stored memory, and when does the agent refuse?

A fact the matrix refuses should not be dropped silently, because a silent skip leaves the model to invent a softer answer. Seeing the body, seeing that an entry exists, and acting on it are different permissions. Write them down before anyone argues about a tool scope.

WhoWhat they may seeWhat they may do
User the fact is keyed toThe body, in a room the channel rule allowsRead it back, or delete it
Tenant administratorThat an entry exists, who wrote it, and whenDelete it. The body stays closed unless a separate grant says otherwise
Incident investigatorA redacted record by defaultThe body only under a named grant that expires and is logged
The agentThe body only inside that user's own requestNo search across other users to answer one person's question

A search across users, opened because the question was whether anyone else had Thursday held, builds a directory of private notes and then asks the model to read it. Refuse the search and say so.

An empty list will be retold as "nothing is scheduled", which is a different fact, so return a typed refusal and keep the two outcomes apart in the log. AI agent monitoring covers why a refusal that looks like an empty success gets sent to the wrong owner.

The audit line names the tenant, the acting user, the conversation, the channel type, the memory key and the credential id. Keep the token in the vault and only a reference to it in the log, because a token in the log is a copy for everyone who can read logs. The telecom engagement keeps the same kind of record: who asked, what the agent did, which data it touched, and what the user approved.

Deletion has to reach the copies. A note removed from the user store can still sit in a conversation summary, in a channel memory someone filed, and in a trace kept for a later eval, and the next summary will copy whatever it can still see.

Delete or redact those copies before the next summary is written. An AI audit trail has to show what a challenged decision relied on even once the body is gone.

Does MCP authorization keep users' memory apart?

No, and we do not treat a passing audience check as evidence that it does. A scope on an MCP server covers the credential half of this pattern, and it does not choose the memory key. MCP authorization covers the token side in full.

As of the 28 July 2026 revision of the MCP specification, a protected server acts as an OAuth 2.1 resource server, the side that checks tokens rather than issuing them. It must reject a token that was not issued for it, and it must not pass a client's token on to an upstream service.

That stops a token issued for one server from being spent at another. It does not stop your own process from attaching one user's token to another user's request, and it does not choose which memory was loaded before the call. Resolve the acting user and the tenant in your own layer before a tool is chosen, load memory on that key, apply the channel rule, and only then call the tool with that user's token.

The 2026 revision also removed protocol sessions. MCP is now stateless, so every HTTP request carries its own authorization, and a server that needs state across calls hands out explicit handles passed as tool arguments. Under the specification's security guidance, a server must not treat possession of such a handle as authentication, and it should bind each handle to the user taken from the verified token. Store server state under the user id and the handle together, which is this guide's user key applied on the server.

The failure that remains sits on the client side. A gateway that connects once as the installer and reuses that token for every person in the workspace still passes the audience check, the server's test that a token was issued for it, because the token really was issued for that server. Send the acting user's own token on every call, and refuse when that user has not connected.

Prompt injection raises the cost of a wrong key without changing which key is right. A channel message can tell the agent to repeat a direct-message note, or to call a tool with a broader token than the asker holds.

Read the acting user from the request, never from a name the model supplied, or the injected text chooses the person and the token follows. Guardrails that trust the model for the user's identity apply a correct scope to the wrong person.

Common questions

What is AI agent memory?#

AI agent memory covers everything an agent keeps beyond a single reply. Short-term memory holds the current thread, tool results and temporary state. Long-term memory persists across runs as facts and preferences, past interactions and their outcomes, and instructions or procedures. In an agent that serves more than one person, each kind needs an owner: the conversation, the user, or the tenant when everyone there can already see it.

Why is a scoped tool token not enough?#

A scope limits which operations a token may call. It says nothing about which person the request is for or which room will see the result. A narrowly scoped token that belongs to the installer still returns that person's data when someone else asks. Key the token and the memory to the acting user inside the tenant, and decide in code what a group channel may receive.

What is memory bleed between users?#

Memory bleed is a later request reading a fact that was stored for someone else, or for a different room from the one now asking. It happens when the store is keyed by the bot, by the workspace, or by a thread more than one person can continue. The tool call succeeds and the reply reads well, because the fact is real. The failure is that the owner of the fact is not the person in the room.

Can one bot token serve every user?#

A bot token belongs to the app, not to a person, and Slack keeps it working even after the installing user is deactivated. Use it for posts that should appear as the bot. When a call reads or changes a person's data, use a token that person granted. If the request has no such token, refuse rather than falling back to the bot token to answer anyway.

When is shared workspace memory acceptable?#

When the audience can already open the fact, and only inside one tenant. A company procedure that is already visible across the workspace can be read in a channel. A note from a direct message, a personal preference and any credential cannot. A fact posted in one channel stays there unless someone who can see it explicitly files a copy somewhere wider.

Who is allowed to read stored user memory?#

The user the fact is keyed to may read it, in a room the channel rule allows, and may delete it. A tenant administrator may see that an entry exists and delete it, without seeing the body by default. An investigator gets a redacted record, or the body under a named grant that expires and is logged. The agent does not search across users to answer one person's question.

How should a memory refusal be returned?#

Return a typed status saying this caller may not use that memory or credential, and tell the user the same thing in plain language. Do not return an empty list, which is easy to retell as "nothing is scheduled". Log the refusal with the user, the tenant, the channel type and the refused key, and leave the secret and the note body out of the log.

Do MCP audience checks replace per-user memory keys?#

No. The server should still reject a token issued for a different server, and since the July 2026 revision it should bind any stored state to the user in the verified token. In front of that, your own layer has to pick the acting user's token and load only that user's memory after the channel rule has run. A gateway that reuses the installer's token for everyone passes the audience check and still leaks.

What happens to memory when a user is deactivated?#

Revoke or delete their credential at once, so a refresh cannot issue a new access token for someone who has left. Keep the audit references that record reads and refusals. Remove the private body from the user store, from conversation summaries, and from any channel copy they did not explicitly file as shared knowledge.

Further reading