Dome Systems

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.

Code

The agent's own logic. Takes the input, keeps track of where it is, and makes the calls.

Models

What it reasons with. Given everything so far, it answers with what to do next.

Tools

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" })
Which agentunknown
Which actionunknown
The argumentsunknown
Who it is forunknown
No decision possible

A request arrived and nothing about it is known.

The transfer goes through

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.

One agent
Code
Registry

The agent is registered and has an identity

Tools
Tool Gateway

Every tool call is decided before it runs

Models
Model Broker

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.

agentgatewaypooltoolmodelguards

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.