A technical build log of the Multi-Agent Control Room, where AI agents pay invoices, escalate denials, and every action is identity-governed through OPA policies, RFC 8693 delegation tokens, and the Maverics AI Identity Gateway.
Four AI agents share a corporate bank account with spending limits from $0 to $500K, enforced by OPA Rego policies evaluated on every MCP tool call through the Maverics AI Identity Gateway. No prompt engineering, no application-level checks. RFC 8693 delegation tokens fuse human and agent identity into a single JWT, making the human's role the authorization ceiling. The same agents produce different outcomes depending on who is logged in, with zero code changes. Agents connect to enterprise APIs exclusively through an identity gateway that translates REST to MCP tools, evaluates policy inline, and issues 5-second scoped tokens per tool call. Every decision is logged for audit. Denied agents automatically escalate work up an identity-governed org chain, creating emergent human-in-the-loop governance without explicitly programming that behavior.
If you've been following Clawdrey Hepburne's blog, you already know the problem. In her "Your Sub-Agents Are Running With Scissors" series, she lays it out with the kind of clarity only a lobster in a little black dress can muster: AI agents inherit credentials from their parents like a contractor walking around with a photocopied CEO badge. No scoped identity. No attenuation. No audit trail. Just ambient authority all the way down.
Clawdrey's work on OVID Mandates and Cedar-based policy engines tackles this from the agent's perspective. How agents should carry identity. How policies should be formally verifiable. How delegation should be mathematically provable as privilege-attenuating. She co-authors with her human, Sarah Cecchetti, co-founder of IDPro and former AWS engineer on the Cognito and Cedar teams.
Where Clawdrey asks "what should agents carry?", this post asks "what should the infrastructure enforce?" I took the problems Clawdrey identifies and built a working system that solves them. Four AI agents with a corporate bank account, governed by delegation tokens, OPA policies, and an identity gateway that enforces per-tool authorization on every single MCP call.
What I Built
A browser-based "Control Room" where four AI agents run a fictional finance department for a company called Initech:
| Agent | Role | Can Spend | Key Tools |
|---|---|---|---|
| Finance Clerk | Read-only bookkeeper | $0 | list_invoices, view_invoice |
| Finance Manager | Accounts payable | Up to $50K | + pay_invoice, approve_expense |
| Finance Director | Department head | Up to $250K | + pay_invoice, approve_expense |
| CFO | Top authority | Up to $500K | + pay_invoice, approve_expense |
Each agent runs in its own Docker container, connects to enterprise APIs through an identity gateway, and has its spending limits enforced by OPA policies on every single tool call. The clerk literally cannot spend a dollar. Not because I told it not to in the prompt, but because the gateway evaluates a Rego policy and rejects the request before it ever reaches the Finance API.
The fun part: a human can log in via Keycloak and their authority level becomes the ceiling for what every agent can do. Log in as a clerk? Even the CFO bot can't spend a cent. Log in as the actual CFO? Now it can process payments up to $500K. Same agents, same code, different human, different outcomes.
The Stack
The stack: 4x GoClaw containers (~35MB RAM each), Maverics AI Identity Gateway, Maverics Auth (OIDC Provider), Keycloak (human authentication), Finance API (Express service), Messaging Service (in-memory), OPA (policy engine), Loki + Promtail + Grafana (audit), n8n (workflow automation), LLM Egress Proxy (nginx SNI allowlist), React Control Room (2x2 agent chat grid). That's ~20 containers, 4 isolated Docker networks, and a lot of YAML.

Part 1: Making Agents Talk to APIs Through an Identity Gateway
The core architectural decision: agents cannot reach enterprise APIs directly. They live on agent-net. The Finance API lives on enterprise-net. There's no route between them. The only path is through the Maverics AI Identity Gateway.
Agents connect to the gateway using MCP's streamable-http transport. Three things happen on every tool call:
- OPA evaluates the request
- If allowed, the gateway does an RFC 8693 token exchange for a 5-second scoped token
- Forwards to the backend API with the scoped token
Gateway config:
finance-api-bridge:
type: mcpBridge
openapi:
spec:
uri: file:///etc/maverics/openapi/finance-api.yaml
baseURL: http://api-finance:3000
authorization:
inbound:
opa:
file: /etc/maverics/policies/finance-authz.rego
outbound:
type: tokenExchange
tokenExchange:
type: delegation
idp: auth-provider
audience: finance-api
tools:
- name: pay_invoice
ttl: 5s
scopes: [finance:write]
- name: list_invoices
ttl: 5s
scopes: [finance:read]Part 2: The OPA Policies (Where It Gets Fun)
RBAC: Who Can Do What
role_scopes := {
"clerk": {"finance:read"},
"manager": {"finance:read", "finance:write"},
"director": {"finance:read", "finance:write"},
"cfo": {"finance:read", "finance:write"},
}Spending Thresholds: How Much Can They Spend
thresholds := {
"manager": 50000,
"director": 250000,
"cfo": 500000,
}
threshold_deny contains msg if {
input.tool == "pay_invoice"
limit := thresholds[input.role]
input.amount > limit
msg := sprintf("Amount $%d exceeds %s limit of $%d", [input.amount, input.role, limit])
}Business Hours: When Can They Do It
business_hours_deny contains msg if {
input.tool in write_tools
not input.role in unrestricted_roles
clock := time.clock(time.now_ns())
hour := clock[0]
not (hour >= 8; hour < 18)
msg := "Write operations restricted to business hours (08:00-18:00 UTC)"
}Escalation Chain: Who Can Message Whom
escalation_allowed := {
"clerk": {"manager"},
"manager": {"clerk", "director"},
"director": {"clerk", "manager", "cfo"},
"cfo": {"clerk", "manager", "director"},
}Part 3: Delegation Tokens and the "Authority Ceiling" Trick
When a human logs in via Keycloak, the backend does an RFC 8693 token exchange:
{
"sub": "alice@initech.com",
"role": "clerk",
"act": {
"sub": "openclaw-manager",
"role": "manager"
},
"token_type": "delegation"
}The human's role is the authorization ceiling. Even though the manager bot natively has $50K spending authority, when Alice (clerk) is logged in, the token's role claim is clerk.
if (humanRole != "") {
claims["role"] = humanRole
}
act := map[string]interface{}{}
act["sub"] = agentSub
act["role"] = agentRole
claims["act"] = act| Human | Agent | pay_invoice | Why |
|---|---|---|---|
| Alice (Clerk) | CFO Bot | DENIED | role=clerk, no finance:write |
| Bob (Manager) | CFO Bot ($45K) | ALLOWED | role=manager, $45K < $50K |
| Bob (Manager) | CFO Bot ($75K) | DENIED | role=manager, $75K > $50K |
| David (CFO) | CFO Bot ($75K) | ALLOWED | role=cfo, $75K < $500K |
Part 4: The Token Lifecycle
The flow:
- Login: Human authenticates via Keycloak (OIDC PKCE). Backend acquires agent CC token, does RFC 8693 exchange, PUTs delegation token onto each agent's MCP server.
- Every tool call: Agent calls MCP tool → Gateway evaluates OPA → if allowed: exchanges delegation token for 5-second scoped token → scoped token sent to Finance API.
- Logout: Backend clears delegation tokens, acquires fresh agent CC tokens.
Part 5: Auto-Wake and the Escalation Loop
When an agent is denied, it queues the work by messaging the next role up the chain. Auto-wake polling checks inboxes every 5 seconds with a 15-second debounce. Agents read messages, process them, and reply. If also denied, they escalate further.
When a higher-authority human logs in later, the agents present the queued work and wait for approval. Human-in-the-loop governance that doesn't require the human to be present when work arrives.
Part 6: The Activity Feed
The Control Room shows a live activity feed from two sources: instant relay events (milliseconds) and Loki audit events (10-30 seconds late, authoritative).
[ALLOW] clerk list_invoices via finance-api 11:42:01
[DENY] clerk pay_invoice No spending authority 11:42:03
[MSG] clerk --> manager "INV-001, $75K" 11:42:04
[TOKEN] manager delegation sub=bob@initech 11:42:15
[ALLOW] manager pay_invoice via finance-api 11:42:18
[MSG] manager --> clerk "Paid INV-001" 11:42:20Part 7: Agent Security Hardening
- Read-only containers. Every agent runs with read_only: true, cap_drop: ALL, no-new-privileges: true.
- LLM egress proxy. Nginx SNI allowlist. Only api.groq.com and api.anthropic.com pass through.
map $ssl_preread_server_name $backend {
api.groq.com api.groq.com:443;
api.anthropic.com api.anthropic.com:443;
default 127.0.0.1:1; # deny
}- Server-asserted roles. Agent's role is signed into JWT by Maverics Auth.
- Identity-anchored context. Personality and operating instructions stored in PostgreSQL as immutable context files.
What I Learned
- Identity is the right abstraction for AI agent governance. Not prompt engineering, not application-level checks, not network rules.
- The gateway pattern works. Agents don't know they're governed. APIs don't know they're called by agents.
- Per-tool token exchange is underrated. Every tool call getting its own 5-second scoped token. Blast radius is effectively zero.
- Delegation tokens are the key insight. Same agents behave differently depending on which human is driving. No code changes.
- Auto-wake + escalation creates emergent behavior. I didn't explicitly program "if denied, escalate." The behavior emerged from the identity layer.
The full system runs via docker compose up. Built with GoClaw (agent runtime), Maverics (identity gateway + auth), OPA (policy engine), and Docker Compose.

