Dome Systems

So You're Building an Enterprise Agent Hub

A pragmatic, technical guide to the responsibilities of an internal agent platform — and how to build each of them on Dome's APIs

August 17, 202632 min readDownload PDF

Introduction

Enterprises are adopting AI agents faster than the infrastructure to govern them. Agents now appear across business units — built on different frameworks, running in different environments, reaching into production systems — and platform teams are increasingly asked to provide a single, governed way to build and operate them: an internal agent platform, or hub.

This paper is for the platform engineer or architect building that hub. It sets out the responsibilities such a platform must discharge, identifies which you should build yourself and which you should delegate to a governance substrate, and shows how to implement each on Dome's APIs. It assumes you have your own view on runtime and infrastructure and does not prescribe one. It is a design guide, not a tutorial.

How to read this. If you are deciding what to build, read the first three chapters — the taxonomy and the boundaries. If you are working out how, the middle chapters take each governance responsibility in turn and drop into the actual APIs, CLI, and policy you would write. The last chapters cover the two platform patterns we see most often, how to choose an integration, and the common failure modes. Throughout, "Dome" is the governance substrate; "the platform" is the system you build on top of it.

One principle underpins everything that follows: an agent platform is two systems. The first is a runtime and a control surface — the system that lets a team declare an agent, packages it, runs it, and lets operators steer it. You should build that, to your own taste, because it is where your infrastructure and organizational choices matter. The second is a governance substrate — identity, authorization, model access, a governed path to tools, and an accountable record. You should not build that, for the same reason you do not build your own identity provider: the cost of a subtle mistake is high and is paid elsewhere.

The engineering that matters is the boundary between the two: deciding which responsibilities sit on each side, and wiring the small number of calls that join them. The rest of this paper maps that boundary.

What a platform must do

An agent platform, whatever its shape, discharges a fixed set of responsibilities. Naming them precisely lets you sort them into two groups: the responsibilities where a bespoke answer adds value, and those where a bespoke answer is a liability.

An agent, throughout, is three things: code that orchestrates, a model that reasons, and tools it calls to act on the world. A chatbot is thin code, one model, and (increasingly) a few tools; a revenue-analysis agent is heavier orchestration, a model, and a set of tools into real systems. The proportions vary; the components do not. A platform stands up all three, repeatedly, for many teams, without a person wiring each one by hand.

A shared-responsibility model

The responsibilities divide into two groups — much like the shared-responsibility model that governs cloud adoption. One group defines your platform; the other is provided by the substrate.

The platform surface — what you build:

ResponsibilityWhat it means
DeclareA team describes the agent it wants: model, instructions, permitted tools, and who it acts for. You design this schema and the surface that captures it — YAML, a form, an API.
ManufactureThe declaration becomes something runnable — a container image, a function bundle, a job spec.
RunThe runnable artifact gets somewhere to execute and a way to be invoked — Kubernetes, ECS, Cloud Run, serverless; your choice.
OperateOperators see the estate and drive lifecycle: pause, resume, retire. You decide how it surfaces.

The governance substrate — what Dome provides:

ResponsibilityWhat it means
IdentifyEvery agent has an identity the system can reason about, and acts on behalf of a user or service principal.
AuthorizeEvery action an agent attempts is checked against policy before it happens — deterministically, fail-closed.
Route modelsAgents reach models through a governed path: no raw provider keys, model choice expressed as policy.
Broker toolsAgents reach tools through a governed path: no raw tool credentials, discovery filtered by policy, responses governed on return.
AccountEvery governed action is recorded with enough context to answer "who, what, under which rule, with what result," and streamed to where security already works.

The boundary between these two tables is the most important decision in the paper. The first table is where your judgment adds value. The second is where a home-grown answer eventually fails an audit, leaks a credential, or becomes an authorization system nobody reviewed. Consume it; do not rebuild it.

How Dome provides the governance substrate

Each governance responsibility corresponds to a specific Dome component and a specific way to reach it. This table is the spine of the chapters that follow; each takes a row and carries it through to working calls.

ResponsibilityDome componentPrimary surfaceWhere you reach it
IdentifyRegistry, IdentityAgentRegistry, Identity (Connect RPC); dome agents (CLI)Build time: register, issue keys. Runtime: agents exchange keys for short-lived tokens.
AuthorizeAuthorization (Cedar engine)Authorization (Connect RPC); dome rules (CLI)Build time: deploy rule bundles. Runtime: evaluated at the gateway or in-process.
Route modelsLLM gatewayGateway /gateways/{id}/v1; dome model (CLI)Point a standard model client at the gateway; declare pools and routing.
Broker toolsGateway, GuardsGateway /gateways/{id}/mcp; dome tool, dome guards (CLI)Agents call tools through the gateway; assign response filters per connection.
AccountAuditAudit (Connect RPC), /api/v1/audit/* (REST); dome audit (CLI)Query and stream the record; export to your SIEM.

An agent platform is two systems. Above the boundary, the runtime and control surface you build. Below it, the five governance responsibilities you consume from Dome.

Everything from here is detail on how to build the first table's rows well, and how to consume the second table's rows without accidentally rebuilding them.

Balancing responsibilities between runtime and governance

Where your platform ends and the substrate begins is the most consequential decision you will make. Draw the line too high and you reimplement authorization yourself; too low and you couple your runtime so tightly to the substrate that teams cannot ship.

Dome draws this boundary as a split between a control plane and a data plane, always separate processes. The control plane is the system of record: the registry of agents, tools, models, and pools; identity and token issuance; the stored, versioned authorization rules; and the audit record. The data plane sits on the hot path of an agent's execution and enforces decisions. Your platform talks to both, for different reasons and with different credentials, and keeping those two conversations distinct is the core of a clean design.

  • Your platform → control plane is a build-time, administrative conversation. When a team declares an agent, your platform registers it, mints its credential, and deploys its policy. Authenticate this with a platform-level key that lives in your platform, never in an agent.
  • Your agents → data plane is the runtime conversation. The agent authenticates with its own scoped, short-lived credential and makes governed calls. It never sees an admin key, and it never holds a credential for any tool or model.

You attach agents to the data plane in one of two ways, and the choice determines where enforcement runs. Route traffic through the gateway and the agent stays thin: it makes an HTTP call to a governed endpoint, enforcement happens out-of-process, and the agent holds no tool or model credentials. This is the default for anything that touches a real tool or model. Or embed the SDK and enforcement runs in-process: the SDK syncs the agent's effective policy from the control plane and evaluates it locally, so an agent can check its own action before it acts. Use the gateway for egress and the SDK where a local pre-check is useful; the choice need not be uniform across the estate.

The two call sequences

Almost everything your platform does reduces to two sequences. One runs once, when a team declares an agent — the manufacturing step, made with the platform key against the control plane. The other runs on every invocation — the hot path, made with the agent's own short-lived credential against the data plane. The figure lays out both; the rest of the middle of this paper is what happens inside each numbered step.

The two sequences, and the credential each uses. Build time runs once per agent against the control plane; runtime runs on every invocation against the data plane, every call carrying the agent's token and correlation headers.

Identify

Nothing else works until every agent has an identity the system can reason about. Deterministic-era identity tooling was built for humans and services, and agents differ in two ways: there are many of them, created and destroyed faster than most onboarding processes expect; and they act on behalf of a user or service principal, so "who is doing this" has two answers at once — the agent, and the identity it acts for.

In Dome an agent is a first-class record in the Registry, created through AgentRegistry/RegisterAgent. Registration is where anonymous code becomes governable: the agent gets a stable identity, its permitted tools and pools, and a place in a four-level tenancy hierarchy — Organization → Tenant → Workspace → Agent — enforced at the database with row-level security. Map your organization onto this hierarchy deliberately before you register your first agent; it is how you keep one business unit's agents isolated from another's.

Registration and key issuance are the platform's job: RegisterAgent, then CreateAgentKey. The key is a long-lived credential the agent exchanges at runtime for a short-lived JWT via Identity/ExchangeToken (or the REST OAuth client_credentials endpoint). The token carries the agent's identity and tenancy — its agent_id and the organization, tenant, and workspace it belongs to — and it expires in minutes, so a leaked token is a contained problem. Because the token is short-lived, a change to an agent's profile — a revoked permission, a suspension — takes effect on its next exchange without a key rotation.

Agent lifecycle

An agent moves through a defined lifecycle, and each transition is an explicit, audited operation. Revoke cascades to descendants by default, so a revoked parent cannot leave live children behind.

OperationCLIEffect
Registerdome agents registerCreate the agent record; state becomes provisioned.
Issue keydome agents create-keyMint a credential; the agent can now exchange tokens; state becomes active.
Rotate keydome agents rotate-keyReplace the credential without changing identity.
Suspend / reactivatedome agents suspend / reactivateTemporarily disable, then restore, token exchange.
Revokedome agents revokePermanently disable the agent and cascade to descendants.
Deletedome agents deleteRemove a revoked agent record.

The agent lifecycle. Each transition is an explicit, audited operation; revoke cascades to descendants before an agent can be deleted.

Ensuring correct identity for agents

An agent should act on behalf of an identity, not as an anonymous service. Dome expresses this with act-as: the agent authenticates with its own credential, and every governed call carries an X-Dome-Act-As assertion naming the user or service principal it is acting for. Authorization and audit then reason about the real actor, not the agent in isolation.

The acting identity is not a separate entity type; it is the act_as attribute on the agent principal — subject, email, roles, and groups — verified against the OIDC providers you register for the workspace. Because authorization reads this attribute, the same agent calling the same tool on the same record can be permitted for one person and denied for another. The audit trail names the human, and policy can express constraints — conflicts of interest, need-to-know, per-customer boundaries — that are meaningless at the level of a bare service account.

Prefer act-as for essentially every agent. A user-facing agent acts as the invoking user; a background or scheduled agent acts as a designated service principal so its actions remain attributable. An agent that acts purely as itself is a rare exception, appropriate only when there is genuinely no user or service principal behind the work.

Register an agent with its tools and act-as configuration, then mint a runtime key:

bash
dome agents register --name revenue-analyzer --tool crm/list_accounts --tool crm/get_account --actas-method oidc --actas-provider corp-idp --actas-required
dome agents create-key revenue-analyzer --name runtime

You declare the agent's tools and pools; registration wires up what it needs to reach them, so you never hand-manage low-level permissions.

Guideline. Default to act-as. The agent authenticates as itself but carries the identity of the user or service principal it acts for, so authorization can vary by who that is and the audit trail names the real actor. An agent that acts as no one is the exception, not the starting point.

Authorize

Authorization is the center of the system. Identity, model routing, and the tool gateway all exist to make one decision possible and to enforce its result. The question is narrow, and at runtime it is the only one that matters: this agent, acting as this identity, is about to take this action against this resource — is that allowed?

Dome answers it with Cedar, an analyzable policy language built for authorization. Cedar is the engine, not the product: treat "authorization" as the capability and Cedar as the language it speaks, as you treat persistence with Postgres underneath. Two properties make it the right fit. It is deliberately not general-purpose — small, deterministic, and analyzable — which is what you want where "it depends what the model decided" is unacceptable. And it is fail-closed: if the decision cannot be made, the action does not happen.

The entity model is small. Getting the names exactly right matters, because you will write them:

ConceptIn CedarHow you express it
The agentDome::AgentThe principal. Carries an act_as record — the identity it acts for.
The actionDome::Action::"mcp:call", etc.String-typed. Tools: mcp:call, mcp:discover. Models: llm:invoke, llm:embed, and siblings.
A toolDome::MCPToolThe resource for a tool call.
A modelDome::LLMModelThe resource for a model call. Reference a whole pool with the resource.pool attribute.
Any other resourceDome::ResourceThe general resource type; connection attributes surface as resource.<key>.

Everything you need is expressed with these five types plus attributes. The user an agent acts for is the act_as attribute on the principal; a model pool is resource.pool; a Gateway is resource.gateways. So a rule permitting a whole pool reads resource.pool == "premium", and a rule that varies by the acting user reads principal.act_as.roles.contains("...").

Rules you write versus grants the platform generates

One distinction clarifies most of the design: a rule versus a grant.

A rule is Cedar you author. You deploy it as a versioned bundle through Authorization/DeployBundle (or dome rules apply, with validate and simulate to check it first) at one of four scopes: org, tenant, workspace, or agent. Bundles merge into one policy set, and the merge has one rule worth memorizing: forbid always wins, across every scope. A workspace-level forbid cannot be undone by an agent-level permit. This is how a security team sets a floor no team beneath them can dig under.

A grant is Cedar your platform generates from a declaration, and it is the default for a self-service platform. When a team declares an agent that may use three tools, the platform writes no policy by hand; it compiles that tool list into a least-privilege grant — a permit for mcp:call on exactly those three Dome::MCPTool resources, bound to the agent's identity, plus a narrower mcp:discover grant so the agent sees only the tools it may use. The declaration is the policy.

A generated grant is a short program the platform emits and deploys:

cedar
permit(
  principal is Dome::Agent,
  action == Dome::Action::"mcp:call",
  resource is Dome::MCPTool
) when {
  principal.id == "a1b2c3d4-...-agent-uuid" &&
  (resource.name == "crm/list_accounts" ||
   resource.name == "crm/get_account")
};

Two tools in a declaration become an exact-match permit, and the agent can do precisely those two things — step 3 of the build-time sequence.

The two shapes of policy: from declaration, and from identity

The grant above is generated from a declaration — policy that follows from what the author asked for, and the workhorse of a horizontal platform. The second shape is identity-driven, where the decision turns on who the agent acts for.

An information barrier is the canonical case. A person on the wrong side of a barrier — a banker screened from a live deal, a clinician outside a patient's care team, a support agent scoped to one customer — must not reach across it, whichever agent acts for them. That is a property of the acting identity and the resource, not the agent. You express it as an authored forbid that reads the act-as roles on the principal:

cedar
// A person screened from a deal cannot open its records, whichever agent acts for them.
forbid(
  principal is Dome::Agent,
  action == Dome::Action::"mcp:call",
  resource
) when {
  principal has act_as &&
  principal.act_as has roles &&
  principal.act_as.roles.contains("barrier_screened") &&
  resource.name like "*open_deal*"
};

Consider how this composes at runtime. The agent first makes a permitted, audited call to read the resource's restriction status. It stamps what it learns onto the act-as roles, then attempts the sensitive action. If the acting user is screened, the forbid fires at the gateway, the call returns 403, and the agent — which never touched the resource — takes an escalation path instead. The model never gets the opportunity to be talked around, because the decision is made outside the model, in infrastructure, on a tool call.

This is the central point: the rules that matter belong in governance infrastructure, not in a system prompt. A prompt that says "do not touch conflicted records" is a suggestion to a probabilistic system; a forbid at the gateway is a control. The test for what belongs where is simple: if a sufficiently clever conversation with the model could get around it, it does not belong in the prompt.

Deploy and test agent-scoped rules from the CLI. simulate answers the decision without making a call, which is useful in CI:

bash
dome rules validate ./rules/revenue-analyzer.cedar --agent revenue-analyzer
dome rules apply    ./rules/revenue-analyzer.cedar --agent revenue-analyzer
dome rules simulate --agent revenue-analyzer --action mcp:call --resource crm/open_deal --resource-type mcp_tool --actas-email dana@firm.example --actas-roles barrier_screened

Where evaluation happens

Both integration modes enforce the same rules. Through the gateway, evaluation happens on the hot path as the call passes. In-process, the SDK pulls the agent's effective policy (GetAgentEffectivePolicy) on an interval and evaluates locally, so the agent can check its own action first. Either way it is the same Cedar against the same bundles. One note on what you read back: references resolve to stable identifiers rather than names, so renaming a model or tool never orphans a grant — you author with names and the system resolves them on deploy.

Route models

Every agent needs a model, and the naive approach is an environment variable holding a provider key. Across a hundred agents that produces three problems at once: credential sprawl, no cost attribution, and no reliable answer to which model an agent is using. The model broker makes model access a governed capability rather than a shared secret.

The vocabulary is Broker / Provider / Pool. A provider is an upstream — Anthropic, OpenAI, Google, Bedrock, Azure OpenAI, or an OpenAI-compatible endpoint such as a self-hosted vLLM — which Dome speaks as distinct wire families, so the broker is provider-agnostic. A pool is a named, policy-selected set of models with a routing strategy. The broker is the path an agent uses to reach a model without holding the provider's key.

The integration is intentionally simple: the agent points a standard Anthropic- or OpenAI-shaped client at a Gateway URL (/gateways/{id}/v1) instead of at the provider. Dome resolves the upstream, fetches the real credential server-side, authorizes llm:invoke against the resolved Dome::LLMModel, records the call, and returns a normal response. In the agent's code it is a one-line change of base URL.

The capability that justifies the hop is that model selection becomes policy rather than code. You declare pools and routing; the broker chooses:

yaml
pools:
  - name: premium
    routing: priority_weighted
    match_when: '{"any":[{"principal.act_as.roles":{"contains":"complexity_high"}}]}'
    members:
      - { model: claude-opus-4-8, priority: 0 }
      - { model: claude-sonnet-4-6, priority: 1 }   # failover
  - name: standard
    match_when: '{"principal.act_as.roles":{"contains":"tier_standard"}}'
    members: [{ model: claude-sonnet-4-6 }]
  - name: triage
    default: true
    members: [{ model: claude-haiku-4-5 }]

The application does not choose a model. It stamps dimensions — a request's tier, an assessed complexity — onto the acting identity, and the broker's match_when predicates route: a hard request to the premium pool with an Opus primary and a Sonnet failover; routine triage to Haiku. Change the routing policy and every agent's model selection changes with it, without a redeploy. Because authorization is re-evaluated for each candidate in a failover chain, a forbid holds even when the broker falls back to the second model. Cost quotas attach to pools as a separate barrier, so a spend ceiling is enforced in the same place, in real money.

Declare a connection, then a routed pool, from the CLI:

bash
dome model add claude-opus   --provider anthropic --model claude-opus-4-8   --api-key "$ANTHROPIC_API_KEY"
dome model add claude-sonnet --provider anthropic --model claude-sonnet-4-6 --api-key "$ANTHROPIC_API_KEY"
dome model pool create premium --routing-strategy priority_weighted --failover-max 2 --match-when '{"any":[{"principal.act_as.roles":{"contains":"complexity_high"}}]}'
dome model pool member add premium claude-opus   --priority 0
dome model pool member add premium claude-sonnet --priority 1

You need not route every call on day one. Route the model when model choice must be policy — cost tiers, data-residency-constrained models, per-request routing — or when provider keys cannot sit beside agent code. Until then, keep a single model call-site in your runtime so the later switch is a one-file change.

Broker tools

If authorization is the decision, the gateway is where it meets the reality of tools. It is the single endpoint an agent's tool traffic flows through, and it upholds one invariant: agents never access tools directly and never hold tool credentials. A tool call is ultimately a call to a backend API, and the security posture depends on the agent never holding that API's credential.

The concrete form is the Gateway — a workspace-scoped, curated view over a set of callable resources: MCP tools, REST-backed tools, and model pools. An agent addresses it at /gateways/{id}, appending /mcp for tools (tools/list to discover, tools/call to invoke) or /v1 for models. A resource in no Gateway is unreachable, which gives you a structural boundary before any policy runs: admission is a coarse yes/no that can only restrict, checked before Cedar, and it filters discovery too — a non-admitted agent sees an empty catalog, not a forbidden one.

A single tool call exercises the whole substrate, in order: the gateway authenticates the token; verifies the act-as assertion; checks kill switches and drain state; confirms the tool belongs to this Gateway and the agent is admitted; evaluates Cedar; and only then executes egress — fetching the real tool credential from the vault server-side, injecting it, and making the upstream call. On return it can filter the response, then emits an audit event. Routing through this path means your platform and agents get all of it without implementing credential injection, fail-closed evaluation, or audit emission. You register the tool sources, assemble a Gateway, admit the agent, and make a well-formed call.

One governed tool call. The agent holds no tool credential; the gateway runs a fixed, fail-closed sequence — authenticate, verify identity, admit, evaluate Cedar, inject the credential from the vault on egress, filter the response, and record it.

Governing what comes back: Guards

Authorization governs whether an action happens; it says nothing about what the action returns. In an agent system the return is often the sensitive part — a customer record carrying a national ID, a document quoting another client's matter. Guards govern the response. A Guard inspects governed traffic and can rewrite or withhold it, and it is direction-aware: a guard on the request direction sees the outbound prompt or tool arguments; one on the response direction sees the completion or the tool result. It can redact a matched span, block the whole payload, or omit a field. The matchers suit this job — substring, national-ID and Luhn-checked card patterns, phone, and N-digit patterns for text; JSON-path field filters for structured tool results. Guards are named, workspace-scoped, and versioned like rule bundles, and assigned per connection and direction. So "this tool may be called, but its results always have card numbers masked and this field dropped" is configuration, not code.

bash
dome guards filters create pii-redact --description "Redact PII in tool responses" --redact-ssn --redact-substring "internal-only"
dome tool guards filters set crm --direction response --filters pii-redact

Account: the audit payoff

Everything that flows through the governed path is recorded. This is the responsibility that turns "we have agents" into "we can answer for our agents," and it is where the design pays off: because identity, authorization, and egress already run through the substrate, the record is a byproduct, not a separate instrumentation effort.

Audit in Dome is a system of record, not a log. On the control plane, an audit event is written in the same database transaction as the change it describes, so a failure to record rolls the change back — you cannot make a governed mutation and fail to account for it. That is a guarantee a best-effort log cannot make.

What a governed action records

Each event is a typed, versioned envelope. In customer terms, it captures:

  • The actor and the acting identity — the agent, plus the full act-as chain: who it acted for, with which roles and groups, and how that identity was verified.
  • The scope — the organization, tenant, workspace, and agent the action targeted.
  • The action and resource — for example mcp:call on a named tool, or llm:invoke on a resolved model.
  • The result — attempted, succeeded, denied, failed, or filtered.
  • Correlation — a trace_id for the single request and an activity_id for the whole run, with a trust label recording whether Dome assigned the id or the caller asserted it.
  • Data handling — flags recording whether content was redacted, omitted, or truncated by a Guard.

Actions emit a paired *.attempted and *.completed event rather than one overloaded record, so an in-flight action and its outcome are always distinguishable. Representative event types:

CategoryRepresentative event types
Tool trafficmcp.tool_call.attempted, mcp.tool_call.completed, mcp.tools_list.completed, mcp.tool_result.filtered
Model trafficllm.model_call.attempted, llm.model_call.completed, llm.model_call.failover, llm.model_result.filtered
Authorizationauthorization.decision, access.denied, authorization.act_as.rejected
Rules and policyauthorization.rule_bundle.deployed, authorization.rule_bundle.rolled_back, authorization.policy_simulation.completed
Agent lifecycleagent.registered, agent.token.issued, agent.suspended, agent.revoked, agent.api_key.rotated

An activity chain, end to end

Every governed call in a run carries the same X-Dome-Activity-Id, and the substrate groups them at read time — within your workspace scope — into one activity chain. Here is the information-barrier run from the Authorize chapter as it appears in the record: the agent, acting as a screened banker, reads a customer then attempts to open a restricted deal.

#Event typeActing identityResourceResult
1agent.token.issuedagent, acts-as dana@firmissued
2mcp.tools_list.completedagent, acts-as dana@firmcrm (admitted tools)succeeded
3mcp.tool_call.attemptedagent, acts-as dana@firmcrm/get_customerattempted
4authorization.decisioncrm/get_customerpermitted
5mcp.tool_call.completedagent, acts-as dana@firmcrm/get_customersucceeded
6mcp.tool_call.attemptedagent, acts-as dana@firmcrm/open_dealattempted
7access.deniedagent, acts-as dana@firmcrm/open_dealdenied (barrier)

The chain shows the whole story in one query: the same identity throughout, a permitted read, and a policy-denied write with the rule that stopped it — the evidence a reviewer or regulator asks for, without reconstructing it from scattered logs.

The operational surface

Because the substrate is the record, the operator's view is a query over it rather than a bespoke pipeline. The registry is a live inventory of every agent, its status, and the tools and pools it can reach; the audit record streams live over server-sent events and exports in batch, with CEF and OCSF formatters for your SIEM. Audit is the evidence-grade record; webhooks are the selective, real-time reflex for triggering automation. Use each for its purpose.

Illustrative operator view: the agent estate with live status, and one activity chain expanded to show the permitted read and the policy-denied write. The visual payoff of routing every action through the substrate.

Follow one run, inspect the chain, and export a window for the SIEM:

bash
dome audit chain "$DOME_ACTIVITY_ID"
dome audit query --results denied --since 2026-08-01T00:00:00Z
dome audit export --format ocsf --since 2026-08-01T00:00:00Z > audit.ocsf.jsonl

Two patterns

The responsibilities are constant; the platforms built on them are not. Two patterns recur, at opposite ends of a spectrum. Understanding why each is shaped as it is will help you place your own build — and most enterprises run both on the same substrate.

Two patterns, one substrate. Both act on behalf of a real identity; they differ in scope and in the richness of that identity, not in the primitives beneath them.

Pattern 1 — The Self-Service Agent Platform

A horizontal paved road: any team declares an agent — instructions plus a list of tools — and the platform manufactures, governs, and runs it. This is the pattern that resembles the managed agent platforms teams already know from the market: bring a prompt and a tool list, get a running governed agent. Its defining property is that governance is automatic and invisible to the author. A team writes a manifest — your schema, not Dome's:

yaml
apiVersion: v1
kind: Agent
metadata:
  name: revenue-analyzer
spec:
  model: claude-haiku-4-5
  instructions: |
    You are a revenue analyst...
  tools:
    - crm/list_accounts
    - crm/get_account
  acts_as: invoking_user       # the agent carries the identity of the user who triggered it
  governance:
    owner: revenue-team

The manufacturing step runs the build-time sequence: register the agent, mint its key, compile the tool list into a least-privilege grant and deploy it, admit the agent to a Gateway carrying those tools, and run it. The author never sees a policy, a credential, or the platform key. The pattern runs on generated-from-declaration authorization, and agents act as the invoking user (or a shared service principal for background work), so every call remains attributable to a real actor.

The transferable idea is the split of roles it forces. The operator stands up the substrate once — registers the tool sources, builds the Gateways, holds the platform key. The author writes a manifest and nothing else. Everything in between is your platform translating declared intent into governance artifacts. Build that translation once and the long tail of internal agents becomes self-service.

Pattern 2 — The Purpose-Built Agent Service

A vertical service embedded in a single high-stakes workflow, where the rules are domain rules and the agent acts for a specific person. Where the self-service platform optimizes for breadth, this optimizes for depth. There is usually one workflow, often one team, and the governance is not generic least-privilege but a domain policy that must be exactly right:

  • Finance — an adviser agent that may act for one client's book but is walled off from a deal it is screened from; restricted lists and information barriers enforced as forbid rules on the acting identity.
  • Healthcare — a case agent held to minimum-necessary access: it can reach the records of patients in its care team and no others, decided by the clinician it acts for.
  • Support and multi-tenant SaaS — an agent hard-scoped to one customer's data boundary, where the tenant is a property of the acting identity, not a parameter the model can change.

The pattern runs on identity-driven authorization — the information-barrier forbid from the Authorize chapter is its signature, the same action flipping outcome on who the agent represents. It usually routes the model too, by case attributes (complexity, tier, data residency), so a sensitive matter gets the capable, correctly-located model and routine work gets the inexpensive one. And because the acting identity is a specific person, the audit trail names that person — frequently the reason the workflow was allowed to be automated at all.

Choosing between the patterns

DimensionSelf-Service PlatformPurpose-Built Service
Optimizes forBreadth — many agents, many teamsDepth — one workflow, done exactly right
DeclarationAuthor writes a manifestEngineers write the workflow
Acting identityThe invoking user or a service principalA specific named person; roles drive per-record decisions
AuthorizationGenerated from the declarationAuthored, identity-driven
Model accessRouted where cost or residency mattersRouted by case attributes
Audit answers"What can this agent do?""What did this agent do, and for whom?"

You do not have to choose. The two share every primitive in the governance table — the same registry, Cedar, gateway, and audit — and differ in scope and in the richness of the acting identity. A realistic enterprise builds the self-service platform for the long tail and stands up a handful of purpose-built services for the workflows where the rules are load-bearing. Because both sit on one substrate, that is one governance model and one audit trail, not two.

Integration and rollout

Two practical questions remain: how your platform attaches to the substrate, and where to begin.

Attachment is the choice described earlier. Route tool and model traffic through the gateway for thin agents that hold no credentials — the default, and what both patterns use for anything touching a real tool or model. Embed the SDK when an agent should evaluate its own actions in-process against synced policy. Many platforms use both, and the choice need not be uniform across the estate.

On where to begin: do not build the whole machine before anything is governed. The substrate supports phased adoption, and the phases are a genuine maturity path.

PhaseWhat you doWhat you get
1 — VisibilityRegister existing agents. Catalog the tools they touch. Establish identity. Turn on audit. Change no agent's behavior.A queryable picture of the estate: what agents exist, what they can reach, what they do.
2 — PolicyAuthor rules; generate least-privilege grants from declarations. Version and test in staging. Enforce at the gateway.Every governed action checked before it happens. Fail-closed, deterministic, auditable decisions.
3 — ScaleExtend the same primitives across teams and runtimes. Route models where model choice must be policy. Stream audit to the SIEM.One governance model across the whole estate.

Phase 1 is non-invasive by design: it delivers value — and tells you what you actually have — before you change a single running agent. Start there.

Common failure modes

Each pattern has a failure mode, and the failures transfer better than the successes because they recur.

Reimplementing governance in the runtime. Under a deadline, someone adds a quick permission check "just for now." It ships and becomes a parallel authorization system with no audit, no versioning, and no forbid-wins guarantee, inside the runtime where no security review will find it. Governance belongs behind one contract, evaluated in one place. If you must ship before that contract is wired, make the gap explicit and temporary, never a quiet local path that grows into the real decision.

Governance in the system prompt. "You must not access records outside your region" is not a control; it is a hope. Anything a sufficiently clever conversation could subvert belongs in a forbid evaluated outside the model. The prompt is for competence; policy is for permission.

Provider keys beside agent code. The environment-variable API key becomes a sprawl you cannot rotate, attribute, or revoke per agent. Route credential-holding through the substrate so no agent holds a raw key; the blast radius of an incident is then one short-lived token, not a shared secret in a hundred deployments.

Treating audit as logging. Logs are for you; audit is for the auditor. A best-effort write that can silently drop events and cannot answer "under which rule" will not survive a regulator. Use the transactional, typed, chained record for the questions you will be required to answer.

RBAC-only thinking. Role-based access controls who can invoke the agent. It says nothing about what the agent, once invoked, decides to do — and that runtime decision is why agents need new governance. The action-level check at the tool-call boundary is the one that matters.

Building the platform before governing anything. A fully configured platform with pools, quotas, and filters, before a single agent is governed, spends months on infrastructure and ships nothing. Start with visibility, add policy where the risk is, and route models where model choice must be policy.

Conclusion

The instinct on being asked to "build the agent platform" is to build all of it, because it appears to be one system. It is two. The first — declaring, manufacturing, running, and operating agents — is yours to build, because it is where your judgment adds value. The second — identify, authorize, route models, broker tools, account — is a substrate to consume, as you long ago stopped building your own identity providers.

The work is therefore narrower than the request first appears. A practical sequence:

  • Draw the boundary. Decide which responsibilities your platform owns — declare, manufacture, run, operate — and which you delegate to the substrate.
  • Give every agent an identity. Register it and have it act for a real user or service principal; never let it act anonymously.
  • Make every action a decision. Enforce authorization outside the model, at the tool-call boundary, and fail closed.
  • Route models through the broker. Express model choice as policy and keep provider keys out of agent code.
  • Reach tools only through the gateway. No agent holds a tool credential; govern what returns with Guards.
  • Account for everything. Treat the audit record as the system of record and stream it to your SIEM.
  • Start with visibility. Register and observe what you already run before you begin to enforce.

Build the part that is genuinely yours as well as you can, on a substrate you can rely on.