*Part 2 of An OS Where the AI Is Never a Principal.*
The previous post made an argument: the model is never a principal, it is a subject confined by the capabilities it holds, and the worst case of a prompt injection should be misuse of least authority — never escalation. That was the *why*. This post is the bridge from slogan to machinery.
Because here is the thing about that reframe — it sounds like it should require tearing the whole agent stack down and rebuilding it from a new substrate. It doesn't. An LLM agent, as the industry already builds it, is *startlingly close* to an object-capability citizen already. It just has one fatal habit, and a handful of name confusions, between it and the real thing.
There are five reframings. Underneath all five sits a single axiom. Let me take them in order.
1. A tool is a capability
Look at what a tool call already is. A named operation, with a typed schema, that the model invokes by reference rather than by writing arbitrary code. send_email({to, subject, body}). read_calendar({range}). query_db({sql}). That is a typed message sent to a named object. It is — and I mean this precisely — about 90% of a capability.
The missing 10% is the part that ruins everything. Today's tool *implementations* reach into ambient authority. send_email opens a connection using the SMTP credentials sitting in the process environment. query_db grabs the global connection pool. The tool's schema looks bounded; its *authority* is whatever the process happens to be able to touch. So the model gets handed a tool named send_email and what it actually holds is "send mail as the operator, to anyone, forever." The name is a fig leaf over ambient reach.
Close that gap and the whole picture snaps into focus. Make the tool a capability *object* whose authority is exactly what was endowed when it entered the agent's context — no credential fishing, no global pool, nothing the tool can reach that wasn't handed to it. Now send_email isn't "the ability to send mail"; it's "this particular, attenuated send-facet, scoped to these recipients, with this quota." And then the consequence:
The tool registry stops being a menu and becomes the agent's cap set.
The list of tools the model can call *is* the list of things the agent can do. Not "can name." Not "can request." Can *do*. Add a tool to the context, you've delegated authority; remove it, you've revoked. The tool-calling interface every agent framework already has turns out to be a capability-passing interface that nobody wired up correctly.
2. An agent is a vat
The next reframe borrows a word from Mark Miller's E language and the Agoric lineage: a vat. A vat is a single-threaded event loop that holds a cap set and communicates only by sending asynchronous messages to caps it holds. (If you read the Endo and Agoric tutorials, this is old furniture.)
An AI agent maps onto a vat exactly. The agent *is* the vat. One turn — one inference call plus the messages it decides to send — is one reasoning step. The cap set is the tool registry from reframe one.
The single-threadedness is usually sold as a concurrency story. Here it does something more important. Classic vats run *trusted* code; this vat's "code" is an LLM you cannot audit and an attacker can steer. Because the turns are serialized — nothing happens concurrently behind your back — you can inspect and gate every message the vat tries to send, at its membrane, before it leaves.
The model proposes a send. The membrane disposes. That sentence is only true because the agent is single-threaded; if turns overlapped, a send could slip out while you were busy adjudicating another. The vat boundary becomes a containment boundary around an untrusted reasoner — and you get a clean causal record for free: these caps held, this inference happened, these messages went out.
3. The model itself is an attenuable capability
Here's the reframe that surprises people. The inference endpoint — infer(prompt) → completion — is *itself* a capability. A reference to a thing that does work for you when you message it.
And capabilities are attenuable. So wrap the model behind a membrane and you can hand an agent not "the model" but a *weakened* view of it: a token budget, a model-tier ceiling, a content policy, a spend cap. The agent doesn't get inference; it gets *this much* inference. The classic ocap move — hand over less than you hold — applies to the reasoner the same way it applies to a file handle.
This matters more than it looks. The ability to think is itself a resource an injected agent can abuse — burn the budget, loop forever, escalate to a tier it shouldn't reach. Treating the model as a metered capability means even *that* is bounded by construction rather than by hoping the agent is frugal.
4. The one axiom — and the petname bridge
Strip away the furniture and Aegis has exactly one law:
An agent can only act on capabilities it already holds.
That's it. There is no ambient namespace to reach into, so naming a resource you don't hold cannot magically reach it. "Reaching" only ever means "invoke a cap I hold." Everything else — brokers, resolvers, powerboxes — is a *pattern built from* that axiom, never an exception to it. A brokered request isn't an escape hatch; it's just the case where one of the caps you hold happens to be a cap to a broker that *may hand back a new cap* after a human or a policy says yes.
Which exposes the real tension between LLMs and ocaps, the one the whole architecture is organized around:
Naming is not authority. And the model traffics in nothing but names.
The model wants to say "the user's calendar," "the prod database," "/etc/passwd." Every one of those is a *forgeable string* — a designator the model made up, that an injection can equally make up. Object capabilities require *unforgeable* references. So you cannot let the model's names resolve to authority directly, ever. That would be ambient authority rebuilt with extra steps.
The bridge is the petname resolver. The model proposes a designator. A trusted resolver — never the model — turns that name into an attenuated capability, or refuses. The division of labor is the whole point:
The model is *excellent* at proposing petnames and *completely untrusted* with their resolution.
So the agent's outbound *language* is petnames — legible, forgeable, harmless: "I added an event to your calendar." Its outbound *actions* are caps — unforgeable, authoritative, adjudicated. Petnames are the human-legible projection of the capability graph; the resolver is the membrane between the register you talk in and the register you act in. (This is genuinely hard, and I won't pretend otherwise — two clients both called "Acme," a trailing-space homograph, and a grant the human approved for one silently binds to the other. Petname confusion is a confused deputy at the naming layer, and the trusted base owns uniqueness and disambiguation, not the model.)
5. Task decomposition is authority decomposition
The fifth reframe is where it all becomes recursive, and it's my favorite, because it dissolves the entire multi-agent coordination problem into the axiom.
When an agent spawns a sub-agent "to book the flight," what is it actually doing? It is creating a child vat and endowing it with exactly the caps that subtask needs — the flight-booking cap, the budget cap, nothing else. The child is *structurally incapable* of touching email or the calendar, no matter what an injection whispers to it, because it was never handed those references. You don't write a policy saying "the flight-booker may not read email." The flight-booker simply has no email cap.
Task decomposition and authority decomposition are the same act.
This is the multi-agent protocol in full. There is no "agent A calls agent B's API with shared credentials" — that's ambient authority with extra hops. There is only: A introduces B to a *subset* of A's caps. Inter-agent communication just *is* capability-passing.
And the property that makes this sound under an untrusted model:
A child can never exceed its parent. The model can only ever propose *narrowing*.
A trusted spawn primitive enforces it. The model proposes "spawn a flight-booker with these caps"; the primitive checks that every cap is one the parent already holds, attenuated no weaker than the parent's. The model's proposal can *subtract* authority but never *widen* it. POLA holds by construction down the entire spawn tree. (There's a subtlety I'll flag and defer: narrowing bounds the *magnitude* of a child's authority but not its *composition* — an injected model could propose a split that's individually legal but combines into an exfiltration pipeline. That can't be a model decision, and fixing it needs the second axis. Hold that thought.)
What we just bought
Five reframes, each a small rotation of something the agent stack already does:
- A tool is a capability — fix the ambient-credential leak and the registry *is* the cap set.
- An agent is a vat — single-threaded turns let the membrane gate every send.
- The model is an attenuable capability — meter the reasoner like any other resource.
- The one axiom — act only on caps you hold; names are proposals, the resolver disposes.
- Decomposition is authority decomposition — spawning a child *is* handing it a subset.
None of this required a new model, a smarter alignment scheme, or a better-behaved agent. It required taking the interface that already exists and refusing to let names mean authority.
But I'll be honest about the seam I kept stepping around. Every property here bounds what an agent can *do* — its authority outflow. It says nothing about what an agent can *know*, or where what it knows is allowed to travel. A flight-booker confined to its caps can still read a secret-labeled itinerary and summarize it down a perfectly legitimate outbound channel. Authority confinement, on its own, does not stop that.
Bounding *doing* is only half the job. You also have to bound *knowing*. That's the second axis, and it's the next post.
End of Transmission.