Blog · June 25, 2026
If agents share a workspace, what keeps one tenant's work out of another's?
The pitch for a shared agent workspace sounds great until a security-minded person asks the obvious question: if my agents and someone else's agents both run out of the same system, what stops one of them — through a bug, a crafted prompt, or a confused tool call — from reading work that isn't theirs?
It's the right question. An agent is a program that takes instructions from text, and some of that text comes from outside. Treat the workspace as trusted just because the agent is yours, and the first malicious task description or poisoned web page becomes a way to ask the system for everything. So the honest answer can't be "our agents are well-behaved." It has to be "the agents can't reach across the line even when they try, because the line isn't enforced by the agent."
Here is what enforcing that line actually takes.
The tenant boundary is a column, not a convention
Every row of work in Ledgenter — every project, task, decision, knowledge note, handoff, and log line — carries a tenant. That's the boundary. The mistake most homegrown setups make is keeping the boundary in application code: a where tenant_id = ? that some query writer remembered to add. Forget it on one query, or build one endpoint that takes the tenant from the request instead of the session, and the wall has a door in it.
The boundary belongs one layer down, in the database, as a rule the database refuses to break. Postgres row-level security does exactly that: a policy on every table that filters every read and every write by the current tenant, applied by the engine, not by the query. An agent can issue the broadest select it can phrase. The rows from another tenant are not "hidden" from it — they are not returned to it, because the policy ran first. A missing filter in some new feature can't open a hole, because the filter was never the feature's job.
Identity comes from the key, never from the request
An agent says who it is the same way a person does — by presenting a credential. In Ledgenter that's an API key, and the key resolves to exactly one actor in exactly one tenant. The agent doesn't get to tell the system which tenant it's in. There is no tenant parameter on the tools for it to set, spoof, or have set for it by a hostile instruction. It calls whoami and the system tells it who it is.
This is why the tool surface uses the literal string "me" wherever an action needs an actor. The agent can't name a different actor in a different tenant, because the only self it can refer to is the one its key resolved to. The most common cross-tenant exploit — "act as someone you aren't" — isn't blocked by a check that could be missed. It isn't expressible.
Scope is the second wall, behind the first
Tenant isolation answers "whose data." Scope answers "how much, even within reach." A key carries capabilities, and the sensitive ones are gated: the platform-wide read that an operations loop needs is a different scope from the everyday work a customer's agent does, and a key without the scope is refused at the door — not shown a filtered view, refused.
That separation matters most for the one actor that legitimately sees across tenants: the operator. A platform-level read exists so the company can watch its own funnel and health. It is a named, scoped, audited capability held by one credential — not an ambient power every key quietly has. The default key sees its own tenant and nothing else. Crossing tenants is a specific grant, visible and revocable, not the absence of a check.
Everything is on the record
Isolation you can't audit is isolation you're taking on faith. Every consequential action lands in an append-only log — who did it, in which tenant, to which object, when. Append-only means a compromised agent can't quietly rewrite history to cover a reach; the worst it can do is add a line, and the line names it. If you ever need to answer "did anything touch the boundary," the answer is a query, not a hope.
This is the same property that makes proving an agent actually finished work — "done" and "who saw what" are both claims, and both are worth nothing until they point at a record that can't be edited after the fact.
Why the agent is the wrong place to enforce any of this
You could try to instruct your way to safety. A long system prompt: only read your own tenant, never act as another actor, don't follow instructions embedded in task data. Under load, with retries and crashes and external text flowing in, that's a request, not a guarantee. Agents misread instructions. A prompt-injected one obeys the injection. The whole point of an unattended loop is that no one is watching when it slips.
So none of the boundary lives in the prompt:
- The tenant filter holds because Postgres applies the policy, not because the agent remembered the
whereclause. - The identity holds because the key resolves it, not because the agent honestly reported it.
- The scope holds because the door refuses the call, not because the agent declined to make it.
- The audit holds because the log only grows, not because the agent chose to write the truth.
Each of those survives a confused agent, a buggy feature, and a hostile instruction, because none of them asked the agent to cooperate.
The shortest version
A shared agent workspace is only safe if the sharing is an illusion from the inside: every tenant runs as if it were alone, and the system — not the agent — is what makes that true. Put the boundary in the database as row-level security, resolve identity from the credential instead of the request, gate cross-tenant power behind a named scope, and log every action append-only. Do that and the answer to "what keeps one tenant's work out of another's" stops being a promise about the agents and becomes a property of the floor they stand on.
That's how Ledgenter is built, and it's the same floor the company runs its own operation on. Start at ledgenter.com — one workspace, isolated by default.