Learn
How agents work
An agent chooses what to do while it is running. Most of the controls in a normal stack assume the opposite: that you know in advance what a program will call.
Start here
Every agent is code, models, and tools
An agent is three parts wired into a loop. Whatever language it is written in, whichever framework it uses, wherever it runs, this is the shape.
Three parts
The parts never change. How much of each there is changes a lot.
- A support chatbot: a little code, one model, no tools.
- A workflow agent: a lot of code, a few models, dozens of tools.
Wired into a loop
The code gathers what the agent knows and asks the model what to do next. If the answer is a tool call, the code makes the call and adds the result to what it knows.
One pass through those four steps is a round. If the model doesn't have an answer yet, the loop runs another one.
Deciding what to do next is the part nobody wrote
You gave the model the question and a list of three tools. It picked one. No code you wrote made that choice.
The model can use more than one tool. Two lookups that don't need each other go out together, in the same round. Claude and GPT both do this by default.
Keep going. Each round starts with everything the last one returned, until the model has enough to answer.
You know the tools. You don't know the route.
The three tools beside you are the whole set. You attached them, so you knew all three before it ran.
The route through them is not:
- Which of the three it uses, and how many at a time.
- In what order, and with what arguments.
- How many rounds it takes before it stops.
Ask the same question again and it takes a different route to the same answer. Nothing is broken when that happens. That is just the agent working.
The agent's own logic. Takes the input, keeps track of where it is, and makes the calls.
What it reasons with. Given everything so far, it answers with what to do next.
Everything it calls out to. APIs, databases, a Slack channel, a payments endpoint.
Every agent is made of code, models, and tools.
Check yourself
One ask made three tool calls, two of them in the same round. The next made one call and stopped. What can you write down about this agent before it runs?
The boundary
Each tool was approved. Nothing says who may use it.
Tools get signed off one at a time, for a specific agent. But an approval never says only that agent may call the tool, which is what keeps it from being a control.
Four tools, four agents
Each row beside you was approved for one agent, for a good reason.
Reading an HR record is the whole job of an HR assistant. Posting to Slack is what a standup bot does. Moving money is what a payouts service is for.
None of those approvals says “only this agent may use it.” There is no control that binds a tool to the agent it was signed off for.
Now give a new agent all of them
Now one agent has access to every tool, and there are no controls in front of any of them. This is the situation the rest of the page deals with.
It can call any of these, in any order, and feed the result of one into the next. It can also ask for several in the same round, so a combination can arrive all at once instead of building up somewhere anyone would notice.
Turn on as many as you like. Every combination is a gap nobody reviewed.
The next agent inherits the gap
Four tools make eleven combinations of two or more, before you count the orderings or the arguments. Nobody approved any of them.
The combination is a symptom. The cause is that nothing stopped this agent from holding tools that were approved for someone else.
Deploy a fifth agent tomorrow, attach the same four tools, and the same gaps come with it. Approval of the tool does not travel with the agent that was supposed to use it.
So the only thing left is to judge each call as it is made, with the caller in the picture. It is the last point where anything can still be stopped.
This agent has all four tools and no controls. Turn on any of them.
Four tools, each approved for a different agent, and none of them bound to it.
The gap
Turn on every control you already have
The agent is about to move $48,000 to an account nobody recognises. Switch on the protections you would normally rely on, then send the request.
Switch on what you rely on
Each of these does real work, and the question it asks is a fair one.
Add all four if you want.
Now send it
The request stops at each control in order, and each one passes.
Read the reasons as they come back. Every answer is correct.
None of these controls is asked whether this transfer should happen.
What the log does not say
The audit line records that a transfer happened, and that the API returned 200. Both of those are true.
Neither one tells you which agent acted, who it was acting for, or what allowed it.
Nothing in this path ever decided that, so there is nothing to write down.
transfer({ "amount": 48000, "to": "acct_9931" })Nothing in front of the call yet.
Waiting to send.
No controls yet. Switch some on.
Check yourself
With access control, scoped credentials, network policy, and logging all switched on, the transfer still went through. What was missing?
The fix, in general
Something has to look at the call before it runs
To stop that transfer you would put a checkpoint in front of it. Two things decide whether the checkpoint works: what it knows, and where it sits.
First, what does it need to know?
Hand the checkpoint facts, in any order you like, and watch what it can decide with them.
The $48,000 transfer is at the bottom. See what happens to it while the checkpoint is still guessing.
The last fact is the one that gets skipped
An agent is usually acting for somebody.
If the checkpoint knows the agent but nothing about who asked, everyone who uses that agent gets the same permissions, the intern and the finance director alike.
Carrying both identities is what makes a per-person rule possible.
Second, where does it sit?
A checkpoint only counts if nothing can go around it.
Three places to put it, from deepest inside the agent to the last point before a call goes out. They do not hold equally well.
The same argument applies to all three parts
Tool calls are the obvious case, but each of the three parts needs its own version of this.
Model calls leave the agent too. That is where the spend happens, and where your context goes to somebody else's API.
transfer({ "amount": 48000, "to": "acct_9931" })A request arrived and nothing about it is known.
The only alternative is to block every call, from everything.
No decision possible. The transfer goes through.
Check yourself
Where does the checkpoint have to sit to hold for every agent, including ones written after yours?
How Dome does it
Three control points on one shared plane
You could build all of this yourself, and some teams do. Dome is the same thing built once, with a control point for each part of the agent, in the same order as before: code, tools, models.
Registry, for the code
The agent registers and gets an identity.
Everything else depends on that. An identity is what a rule can name, what a log line can attribute, and what an operator can look up, version, or switch off.
Without one, you are writing rules about a process nobody can point at.
Tool Gateway, for the tools
Tool calls go through the Gateway, which sits in front of every one of them.
It does three things:
- Decides each call before it runs.
- Holds the credentials, so the agent never has the payments key. A mistake in the loop cannot spend what it cannot reach.
- Runs guards on the way back, so a response can come back with the sensitive field removed.
Model Broker, for the models
Model calls get the same treatment. Routed to the right provider, decided by the same rules, inspected by the same guards, recorded in the same trail.
Teams tend to reach this one late, when a bill arrives and nobody can say which agent sent what to whom.
And one plane underneath all three
If those three answered differently, they would be three separate products.
Underneath them is one set of answers, shared by all three. Rules are evaluated the same way every time, so the same request gets the same decision.
A denial arrives with the reason attached. That is the line that was missing earlier, when every control passed and the log could only say a transfer happened.
The agent is registered and has an identity
Every tool call is decided before it runs
Every model call is routed and decided the same way
Shared by all three
Access
Which agent, and which person it is acting for
Authorization
The rules, evaluated at the call
Audit
The decision and its reason, kept
The code is registered, so it can be named.
Trace it yourself
Everything one agent can reach, and what sits in the way
This is the picture Dome draws for a real agent. Select any node to see how a request reaches it, which guards run on the way, and which rules name it. One of the grants on this agent leads nowhere, and the dashed edge is the one to look at.
Routes to 1 model
Try it on an agent of your own
The fastest way to see this is to watch one of your own calls get denied by a rule you wrote. It takes about ten minutes in a sandbox.