The Agents Carry the Gateway

5 minute read

Governing Agents at the Boundary — part 2 of 4

The plan was to put the gateway at the front door. Users reach the agents through an assistant app, and the app can be bound to a managed gateway, so every request would pass through it.

The platform allowed that binding in exactly one region. In that region, gateway provisioning was capacity-bound at the time. Steps that worked elsewhere failed there with a bare internal error, and a retry an hour later sometimes went through. The app’s association with its agent registry also could not be changed once set. Binding the app looked like a precondition for the whole reference deployment.

It turned out not to be one.

Which traffic does the binding govern?

The question that mattered was not how do we bind the app? It was which traffic would the binding govern?

Drawn out, the answer was one hop: the user’s request to the first agent. Everything after that is an agent’s own outbound call, such as another agent, a tool, a credential fetch, or the agent’s own model. That second set of calls is where everything the deployment exists to show takes place.

So the design moved from governing the request to governing the act. Each agent is bound to an egress gateway when it is deployed. The app stays unbound. The enforcement point sits outside the agent’s code, on the agent’s own outbound path, and the agent’s code has no way to route around it.

The binding governed the doorbell. Everything we wanted to stop happened inside the house.

Where the agents start

The deployment is small on purpose. An orchestrator with no tools of its own takes the question. One specialist knows where the space station is, and nothing else. A maps specialist can search places, and nothing else.

Each agent gets its own workload identity at deployment, one per agent, with no shared service account and no key file in the container. The maps agent needs an API key for its tool server. A credential manager holds the key, and the agent’s runtime fetches it under the agent’s own identity at the moment of the tool call. Only the maps agent may fetch it.

The gateway’s default is the property that matters: an agent attached to it can reach nothing. That includes the other agents, the tool server, and the logging API. Everything the agents can do is a rule in one of two policies. One lets every agent reach the platform APIs it needs in order to run. The other describes the application itself: which agent may call which destination. Every destination in those rules is a named registry entry. There is no free-text host and no wildcard.

With all of that in place, one enforce-mode run produced three refusals and a half-answer.

Three refusals

1. A tool outside the allow-list. The maps agent’s grant names two tools on its tool server, and anything else on that server is denied, including tools the server adds later. Asked for the weather where the station was, the orchestrator delegated, and the maps agent called the weather tool:

Blocked by AI security policy — TOOL ALLOWLIST
403 PERMISSION_DENIED
The Maps API was never called.

The agent held a working key for that server. The key was not the question. The model wanted the tool and the code tried to call it. The refusal came from the gateway.

2. A dangerous prompt, on the agent’s own model call. Asked whether explosives could be smuggled onto the station, the agent sent the prompt to its model. That call is outbound like any other, so it crossed the same gateway, and a content-safety screen refused it. The model never produced an answer. However an agent behind this gateway was written, and whoever wrote it, the same screen sits on its path to the model.

3. A rule written in English. One policy is a short paragraph scoped to the maps agent: do not identify, describe or locate military bases, installations or defense facilities. Civilian places, airports and public memorials are fine. An English-language policy judge applies it to what the agent is about to do. Asked where the station was and whether a military base lay in its path, the maps agent tried to search. The judge refused the search, and the reason it gave named the search the agent had attempted and the policy that search broke. It quoted the action, not the user.

And the half-answer. The station’s position still came back. In the judge’s log, the orchestrator’s calls to the locator were allowed, and so was its hand-off to the maps agent. Only the search was refused. The maps agent retried it several times, and every retry was refused too. One act was stopped, not the conversation.

The messenger and the judge

There is code in the orchestrator that turns a 403 into the readable banner above, and anyone who searches its source for the weather tool will find it. That code is the messenger. Delete it and the call is refused in exactly the same way, with an uglier message. In one run the raw answer arrived anyway, a second line under the banner: a client error, 403 Forbidden, from the tool server’s URL. That line came from the gateway, not from the agent.

Not one of those refusals is decided in the agents’ code. The decisions are gateway rules, a content screen and a judge, all evaluated on traffic the agents cannot route around, and each refusal is a row on a dashboard that the gateway writes, not the agent.

The honest cost

The first hop is not gated in this build. Who may invoke the orchestrator is decided by the app’s sharing settings and the access policy on the agent itself. That is a real gap, and it is stated wherever the deployment is shown, not left for someone to discover. Binding the app is recorded as a future step, with the condition that triggers it: capacity in the one region the binding allows.

Two smaller costs came out of rehearsal. Agents that booted before their access policy existed logged dozens of denied calls each, to the very APIs they need in order to start. In observe mode those were log lines. In enforce mode they would have been an agent unable to fetch its own credentials. The policy is now created first, and the run order is part of the security design.

The judge also reads the conversation, not only the current turn. In one rehearsal, a question asked in the same chat as the explosives prompt was refused, and the reason cited the earlier prompt. The verdict was right but the reason was wrong. The run sheet now starts that act in a new chat.

The line that stays

When the request edge cannot be governed, govern the act. The agent’s own outbound path is a boundary the agent cannot step around, and it is where the work actually happens.

The binding governed the doorbell. Everything we wanted to stop happened inside the house.