One Agent, Many Clients: Stopping Scheduled Agent Memory From Leaking Across Users
One Agent, Many Clients: Stopping Scheduled Agent Memory From Leaking Across Users Agencies and solo operators love the economics of scheduled agents: one workflow, one schedule, many clients served. A Monday-morning briefing agent that summarizes the weekend's support tickets. A nightly lead-research agent that enriches new signups. Build it once, aim it at every client. There is a catch that does not show up in the demo. A scheduled agent with memory serves whoever its memory serves. The mom
One Agent, Many Clients: Stopping Scheduled Agent Memory From Leaking Across Users
Agencies and solo operators love the economics of scheduled agents: one workflow, one schedule, many clients served. A Monday-morning briefing agent that summarizes the weekend's support tickets. A nightly lead-research agent that enriches new signups. Build it once, aim it at every client.
There is a catch that does not show up in the demo. A scheduled agent with memory serves whoever its memory serves. The moment the same memory store holds facts about two different clients, you have a leak waiting for a trigger. And because scheduled agents wake up on timers instead of in conversations, the leak does not announce itself. It shows up as Client B's weekly summary mentioning Client A's launch date, or as an enrichment note attached to the wrong account. By the time anyone notices, the wrong data has been sitting in the wrong place for weeks.
Keeping memories apart per user is a different job from giving agents memory at all. Memory is about recall. Isolation is about boundaries. Here is how to build the boundary.
Why scheduled agents are the worst case
A chat agent is anchored to a user by the conversation itself. The request arrives with a user attached, and the memory lookup inherits that context. A scheduled agent starts from a clock. The trigger carries no user, no tenant, no conversation. The agent must decide whose memory to load based on whatever the workflow passes in, and if the workflow passes in nothing, the agent reaches for the whole store.
Two common setups make this worse. The first is the single shared workflow: one Make.com scenario or one n8n workflow that loops over clients and runs the agent per client, all against one memory connection. The second is the generic agent prompt: the same instructions for every client, with the memory injected wholesale at the top of the context. Both are fine for one tenant. Both are silent contamination machines for many.
Start by mapping who can see what
Before choosing a mechanism, draw the boundary on paper. For each memory your agent stores, answer three questions:
- Which user does this memory belong to?
- Which users are allowed to see it?
- What happens when the agent cannot tell whose memory it is holding?
The third is the one most designs skip. A memory store full of facts with no owner is not a memory store; it is a rumor mill. If a record cannot answer "whose is this?", it should not be readable by anyone until it can.
Most operators land on one of three scoping models. Per-user scoping gives every end user their own memory: the support agent remembers each customer's own history. Per-client scoping groups users under a client: the agency's agent keeps each client's data together but apart from other clients. Shared-plus-private keeps one layer of common knowledge (product docs, public procedures) plus a private layer per user. The shared layer is where teams get burned: a "common knowledge" store that someone quietly started writing client-specific facts into becomes a cross-tenant channel with a friendly name.
Make the boundary structural, not conventional
The DIY answer most people build first is a filter: every memory row gets a user_id or client_id, and every query filters on it. This works, and it is also the boundary most likely to fail at 2 AM six months from now. Filters are conventions. Conventions depend on every future workflow, helper, and teammate remembering to apply them. The failure mode is one unfiltered query in a new automation, and nothing in the system will flag it.
Structural boundaries are harder to build and harder to break. Separate databases, separate schemas, or separate collections per client mean the wrong data is not merely filtered out; it is unreachable. Session keys scoped per user inside the agent framework give per-user continuity in one workflow: the agent for client-acme and the agent for client-beta read different histories by construction. The test for a structural boundary is simple: take away every convention, every careful query, every disciplined teammate, and check whether a leak is still impossible. If it is only unlikely, it is not structural.
Fifty clients times fifty stores is real operations work: connection pools, backups, migrations, all multiplied. The honest rule of thumb: make the boundary as strong as the data is sensitive. A leak of support-ticket summaries is embarrassing. A leak of pricing, contract terms, or customer lists is a contract problem. Spend the ops budget where the consequences live.
Let a hosted layer carry the boundary
There is a middle path between a hand-rolled filter and fifty databases: a memory service whose job is to hold the boundary for you. Your scheduled agents read and write through it, and the isolation lives in the service rather than in your workflow code. The failure mode you are eliminating is the human one: nobody has to remember the filter because there is no filter to remember.
Vilix AI takes this approach. It is cloud-hosted and reached over MCP, so your agents get memory without you running anything: no database, no schema migrations, no connection pools. The isolation boundary is the account itself. Data is isolated per user, and every AI tool you connect to the same account shares the same memory. For an operator running their own automations, this is the shape that fits: one tenant, many tools, one memory, nothing that can leak sideways.
The tradeoff: because the boundary is per account, two clients' memories stay apart via two accounts, not a client_id filter inside one. That is a heavier mechanism than namespacing, and deliberately so; it cannot be undone by a forgotten WHERE clause in a Friday-afternoon workflow edit.
The memory itself is full conversation history, not just extracted facts, and retrieval combines semantic search with keyword matching, so exact strings like order IDs and policy names match literally. It is free to start, with a free plan forever and a 7-day Pro trial that asks for no credit card. If you ever want out, you can export everything in a portable format or wipe the account instantly.
The audit that takes an afternoon
Here is a practical sequence for an operator who suspects the boundary is soft:
- List every scheduled agent that reads or writes memory, and note which of them touches more than one user or client.
- For each of those, check what identifies the tenant at read time. A stable, user-scoped session key or an explicit tenant filter counts. "The workflow knows" does not.
- Run the leak test: plant a fact in one user's memory, then ask for it from another user's context. If it surfaces, the boundary is decorative.
- Fix the structural gaps first (unscoped stores, shared session keys), then the conventional ones (missing filters).
Agents that forget between runs are a solved problem. Agents that remember the wrong user's data are a trust problem, and trust problems do not get second chances with clients. Build the boundary before the first leak, while it is still a design decision and not an apology.