model field. Dome chooses which connection serves the call. That split is what lets you swap providers, add failover, or change weights later without touching the app.
Hand this to an AI agent. It adds models, puts them in a pool, shows routing/failover and spend caps, and hands you the curl that proves the pool holds.
1
Add a model connection
Register a provider model and hold its key in Dome.
2
Put it in a pool
Create a pool, add the connection, and attach the pool to the gateway.
3
Cap the spend
Learn how pool quotas work. Set one on Pro, or skip on Free.
4
Authorize the pool
Deploy a rule that permits this pool and nothing else.
5
Point at the LLM ingress
Form the Gateway
/v1/chat/completions URL and export credentials.6
Call the pool
POST once naming
employee-summary.7
Route across two members
Add a second model, flip priority, and curl again with the same pool name.
8
Verify the results
Check the audit trail, and the spend if you set a quota.
Prerequisites
For this tutorial, you will need:- Govern your first agent completed, with its sandbox still active. This tutorial reuses that workspace, its Default gateway, and the
hr-assistantagent. - An API key for OpenAI or Anthropic. Dome calls the provider on your behalf, so the spend lands on your provider account.
- A second model for the routing section: another model on that same key, or a key for the other provider.
- An agent token (
dome_…) forhr-assistant, the key from the first tutorial, or mint another withdome agents create-key.
Optional: a Pro plan if you want to set a spend quota on the pool. Quotas are included on Pro. Free workspaces skip that step and continue with the rest of the tutorial.
sandbox-get-started. Switch back if it does not:
This tutorial runs entirely in a sandbox. In a production workspace, adding a provider connection and setting spend caps are typically finance and operator actions, not developer ones.
Add a model connection
A model connection is a named route to one provider model, holding the credential Dome uses to reach it. Agents can call a connection by name, but this tutorial puts the connection in a pool instead. Naming the pool keeps the client’smodel field stable when you later add failover, change weights, or swap the upstream provider.
- OpenAI
- Anthropic
openai-mini. Substitute your connection name if you chose Anthropic.
--api-key stores the credential in Dome’s secret store, which is why nothing downstream needs a copy. For providers with their own identity story, --use-workload-identity skips the stored credential and uses the gateway’s cloud-native chain instead, such as GCE metadata for Google or the AWS SDK chain for Bedrock. That avoids holding a long-lived key at all.
Leave the connection off the Gateway for now. A connection that is not a pool member and not attached to a gateway is registered but unreachable. You store the credential first, then expose the model only through the pool in the next step, so the agent never learns a direct connection name to call around the pool.
Confirm the connection is registered and the credential is stored:
Put it in a pool
A pool is the name your client will put in themodel field. Dome picks which member serves each request, and can try another member when the first fails.
Create employee-summary and attach it to Default:
Then add the connection as its only member:
--priority 0 marks this member as primary. Dome tries a --priority 1 member only if the primary fails.
Even with one member, the pool still matters. The client and the rule both name employee-summary. After the first curl succeeds, you will add a second model and switch which member serves without changing either.
Confirm the pool and its membership:
Cap the spend
A quota is a dollar cap on a subject over a window. Enforcement happens before the provider is called, so an exhausted cap costs nothing. That is how you stop a runaway session from becoming an invoice surprise, and why quotas belong next to pools even when you are not setting one yet.Cost quotas are included on the Pro plan. Free workspaces cannot create them (
limit-max-cost-quotas is zero). On Free, read this section and continue to Authorize the pool. The rest of the tutorial does not depend on a quota.--subject pool caps everything flowing through this pool no matter which member served the call. --subject model caps one connection across every pool it belongs to. --subject agent caps one agent across every model. --subject act-as caps an individual end user, which is what you reach for when one person’s runaway session should not exhaust the team’s budget.
Windows are daily or monthly, and a quota can be created with --disabled if you want it in place before you start enforcing it.
Authorize the pool
The Gateway attachment makesemployee-summary reachable. It does not decide who may call it. Tool rules from earlier tutorials cover mcp:call, not inference. Inference is a separate action, llm:invoke, on Dome::LLMModel.
Allow hr-assistant to call models in the employee-summary pool, and deny every other inference call. Scope the rule to the pool name rather than a connection like openai-mini, so a failover member you add later inherits the same permit.
Create hr-assistant-llm.cedar:
hr-assistant-llm.cedar
resource.pool is how Cedar sees membership. Authorizing on Dome::LLMModel::"openai-mini" would lock the rule to one connection and miss any member you add later.
ALLOW. The llm_pool resource type is how simulation addresses a pool the way a live call does: it stamps resource.pool so the permit above matches. Substituting a pool name you never created should return DENY.
Dome rewrites pool-name literals to pool UUIDs when the bundle deploys, so the rule you write with
"employee-summary" is what the gateway evaluates against the unspoofable pool id. You author names. The platform stores ids.Point at the LLM ingress
Point curl at the Default gateway’s LLM ingress and authenticate as the agent. That path is where OpenAI- and Anthropic-shaped model traffic enters the data plane:/mcp. Model traffic shares the /gateways/<id> prefix and uses /v1/... instead.
Retrieve the three placeholders:
-
AGENT_API_KEY. TheToken: dome_…value from Govern your first agent. Tokens are shown once and cannot be recovered. If you did not save it, mint another: -
GATEWAY_HOST. Prependhttps://to your current Dome host, for examplehttps://gateway.dev.domesystems.ai. -
DEFAULT_GATEWAY_ID. The UUID of the Default gateway.
https://gateway.dev.domesystems.ai/gateways/3f9a2c14-8d7e-4b1a-9c02-5e6f7a8b9c01/v1/chat/completions.
Export once. DOME_GATEWAY_URL stops at the Gateway. curl appends /v1/chat/completions:
Call the pool
The Gateway speaks the same OpenAI chat-completions protocol. Compared with calling the provider directly, three fields change:
Dome picks which member serves the call. The client never names the upstream model or connection.
Call the pool once:
employee-summary, not gpt-4o-mini or openai-mini.
Leave the curl exports in your shell. The next section reuses them after you change pool membership.
Provider SDKs work the same way later: OpenAI clients use base URL …/gateways/<id>/v1 and model: "employee-summary". Anthropic clients omit the trailing /v1 on the base URL because their SDK appends /v1/messages. This tutorial sticks to curl so the wire shape is visible without an SDK.
Troubleshooting
Troubleshooting
These are remediation steps if you get stuck:
-
400withselect a gateway. The URL is missing its/gateways/<id>segment. A bare gateway host serves no inference. -
403withmodel not available in this gateway. The pool exists but is not attached to Default, or it has no members. Confirm both: -
403with apermission_errornaming a rule. Cedar denied the call. Reproduce it without spending anything: -
404withno connection for requested model. You passed a provider model id such asgpt-4o-mini, or the connection nameopenai-mini, where Dome expects the pool name. Check what this workspace exposes: -
429withllm: quota exceeded. The cap is working. The message names the subject that ran out. Review it, and raise it deliberately rather than by reflex:If you never set a quota (Free plan), this error will not appear from Dome. Check your provider account instead. -
401from the gateway. The bearer token is the provider’s key, not Dome’s. This endpoint authenticates the agent, and the provider credential never leaves the platform.
Route across two members
A single pool name can front more than one upstream. Add a second member for failover when the primary is down, shift traffic with weights, or promote a cheaper or stronger model without rewriting curl or Cedar. Clients and rules keep sayingemployee-summary. Membership decides who answers.
In this section you add a failover member, then flip priorities so the new connection becomes primary. Failover is already set: priority_weighted with --failover-max all tries a --priority 1 member only if the primary fails.
Add the second connection
A pool can mix providers. The sameemployee-summary name can hold OpenAI and Anthropic members side by side. Pick same provider or other provider below.
- Same provider
- Other provider
Reuse the key you already stored. Match the tab to your primary connection:
- OpenAI
- Anthropic
If your primary was
openai-mini, add a stronger OpenAI model:0 (primary) and priority 1 (failover). Healthy calls still hit priority 0. The second member is not a second name for the app to learn. curl and Cedar keep saying employee-summary.
Switch the primary
Flip priorities so the second member becomes primary. The curl keeps the samemodel value, but Dome routes it to the new primary. Substitute the connection names you actually added:
model: "employee-summary":
0 and the alternate at priority 1. With --failover-max all, Dome tries the backup when the primary fails.
Flip priorities back if you want the original primary for Build a governed app. The application only names employee-summary. It does not care which member is on top.
Verify the results
Confirm the calls went through the pool and landed onhr-assistant, not a shared provider key. On Pro, also confirm they counted against your spend cap.
Read the record:
llm.model_call.attempted and llm.model_call.completed for hr-assistant, carrying token usage and the member that served each call. The first curl and the post-switch curl should name different members if you flipped priority. Denials appear as access.denied, the same event type a rejected tool call produces, because both are authorization decisions.
If you set a quota on Pro, check the spend. The used figure moves after each call:
429 and never reaches the provider. Restore the cap afterward with another update, or remove it:
Next steps
You learned how to hold a provider key in a model connection, route through a pool with failover, authorizellm:invoke on the pool, and attribute spend. Keep the sandbox-get-started workspace for the rest of this track. Continue with:
- Build a governed app to wire model and tool calls in one application
- Pools for routing strategies and membership
- Set Usage Limits for spend caps on pools, models, and agents