Every agent in the first multi-agent system we built had a name, a tone and a backstory, and none of them knew anything the others didn’t. Each persona had a character. None of it changed what any agent could do, because capability comes from which files are in the context window when the agent runs.

That was not obvious while we were building it. The persona abstraction maps onto how we think about people. You hire a “PM,” a “reviewer,” a “researcher,” and you expect different behavior from each, so we built the same thing for agents. We wrote a system prompt that said “you are a meticulous code reviewer who values correctness above all,” and it felt like progress. It wasn’t. We had told a stateless model to role-play. It played the part and answered from whatever happened to be in its context, and when all we had given it was a persona, that context held nothing relevant to the task.

How we check what an agent knows

Here is the test we use now. Ask why the agent knows X. If the answer is “because we named it the X-specialist,” it doesn’t know X. If the answer is “because the files that contain X were loaded into its context for this task,” it does.

A persona is a claim about identity. A scope is a claim about contents, and only the second can be checked. When we designed the PM engine for our agent system, we made this the load-bearing decision: the unit of context is a scope, not a persona. A scope is a deterministic file set derived from the task/area/epic hierarchy. Give us a task id and it resolves to a stable bundle of files. Give us an area and it resolves to the layered set of docs that define that area. The agent running against that bundle knows what is in the bundle, no more and no less, and we can list it.

Structurally it is the same thing as a Unix file mode or an OAuth scope: a concrete, enumerable grant. “You are a senior architect” is vibes. “Your context is L0 session manifest + L2 epic doc + L3 ticket spec for task-1234” is a grant. You can diff two grants, and you can’t diff two backstories. In code the whole interface is one function:

// The entire scope abstraction is one signature.
// Everything (briefing, PM, oracle) sits on top of this.
resolve(ref: ScopeRef): FileBundle
// ScopeRef = { kind: "task" | "area" | "epic", id: string }
// FileBundle = ordered, deduped, deterministic list of file paths
resolve({ kind: "task", id: "task-1234" })
// =>
[
"sessions/task-1234/manifest.md", // L0 what is running
"epics/checkout-rework/epic.md", // L2 why it exists
"tickets/task-1234/spec.md", // L3 what to build
]
// Same ref in, same bundle out, for every consumer.
// "Why does this agent know X?" is answered by grepping this list,
// not by reading a system prompt.

One resolver, three consumers

What made this practical was already half-built before we understood it. The agent dashboard we had was loading layered scopes purely for display. It walked from L0 (session manifests) up through L5 (harness docs) so it could render what was going on across the fleet. The scopes existed and stopped at the screen. Nothing loaded them into the agents we spawned.

So the change was small. We extracted a thin scope resolver that maps any scope reference to a stable file bundle, and let several policy consumers sit on top of it. The resolver has one job. It doesn’t decide what to do with the files. It hands back the deterministic set and each consumer applies its own policy. There are three, all on the same resolver:

  • Briefing. Given a scope, produce a human-readable summary of state. Read-only. It loads the bundle and tells you what is happening.
  • PM. Given a scope, produce a decision: one recommended next action, a blocked-on-us queue, and the exact worker handoff payload it would dispatch if authorized. Same bundle, different policy on top.
  • Oracle. Given a scope and a question, answer from the loaded files. A context query, and no action engine.

That is three behaviors and one shared notion of which files are in play for a scope. The briefing consumer can’t see something the oracle was scoped away from, because they pull the same bundle through the same resolver. There is no per-consumer file-loading code to drift. If a fourth consumer shows up tomorrow, it asks the resolver and gets the bundle everyone else gets.

In the persona version, each role carried its own ad-hoc idea of what to look at, baked into a prose system prompt nobody could statically inspect. We couldn’t guarantee two personas saw a consistent slice of reality. With one resolver we get that for free.

Multi-tenancy fell out of it

With personas as the context primitive, multi-tenancy gets painful. Every persona has to be taught about tenant boundaries, and you are one leaky prompt away from the agent for tenant A reasoning over tenant B’s files. The boundary lives in fragile instructions.

With scopes, the tenant boundary is the scope boundary, and the resolver already enforces it. Onboarding a tenant means defining which scopes belong to it. The same briefing, PM and oracle consumers run unchanged. They never knew about tenants and never needed to, because they only see the bundle the resolver hands them, and the resolver only hands them scopes inside the tenant’s set. The isolation comes from the file-set math and doesn’t depend on how well we worded a warning.

Shipping the resolver first was the wrong order

When we planned the phases, the architecturally clean first deliverable was the scope resolver itself, the reusable primitive everything else needs. A codex review pass flagged that it was backwards from a trust standpoint. The pain we actually felt was “our agents do not make progress on our backlog,” which is the PM consumer’s job and has nothing to do with how tidy the resolver is.

So the first visible deliverable became PM in propose-only mode against a real backlog slice. It does the full reasoning, shows us one recommended action and the handoff it would dispatch, and acts on nothing until we have seen enough correct proposals to flip execution on. The resolver still had to exist underneath. But the first thing you ship should be the thing the user feels.

Propose-only turned out to be a scope story too. The agent’s recommendation is only as good as the files it loaded for that task. When a proposal is wrong, the fix is almost never to tune the persona. The scope was incomplete and the right file wasn’t in the bundle. You can grep, diff and fix that. A persona you can only argue with.

What to ask of your own agents

For any AI agent setup, ask which exact files are loaded into its context for a given task, and ask for that list. If the answer is a role description instead of a file list, nothing checkable says what the agent knows, and separation between customers depends on how well a prompt was worded. Start new agents in propose-only mode, and when a proposal is wrong, check whether the right file was in its scope.