employee-summary pool, may call tools through the same gateway, and every step stays governed, including allow, redact, and deny.
Hand this to an AI agent. It runs the HR application so you see model + tools together, then allow, redact, and deny.
employee-summary and may invoke tools. Dome governs every allow, redact, and deny, and records each step in the audit trail.
The walkthrough uses the Vite + Hono demo-hr-desk reference app.
To do this, you will:
1
Mint a credential for the app
Issue a second key on the existing agent identity.
2
Set up the project
Clone the reference app and fill
.env.3
Understand the agent flow
See the generic loop, then the app files that implement it.
4
Run the agent loop
Chat through the pool and watch model and tool steps.
5
Show allow, redact, and deny
Click the three tool actions on the Tools tab.
6
Verify the results
Match what you saw against the audit trail.
Prerequisites
For this tutorial, you will need:- Govern your first agent completed, with its sandbox still active. That tutorial created the
demo-hrconnection, theredact-contactFilter, thehr-assistantagent, and the tool rule bundle. - Call a model through a pool completed. This application needs the
employee-summarypool and thehr-assistant-llmrule so Chat can invoke the model. - Node.js 18 or later.
- The Dome CLI, installed and signed in.
sandbox-get-started. If it does not, switch back:
This tutorial runs entirely in a sandbox. In a production workspace, minting agent credentials is a developer action, while the rules this app runs under are owned by security.
Mint a credential for the app
An agent identity can hold several credentials. Your editor already has one. The app needs its own, so you can revoke either without disturbing the other.Token: dome_… value. Both keys resolve to hr-assistant, so both are governed by the same rules and both appear in audit under the same agent.
Set up the project
Clone the reference HR application:.env from the CLI. The gateway URL must include the /gateways/<id> segment and must not include /mcp or /v1. The Hono proxy appends those itself:
.env
…/gateways/<id>/v1, and tools go to …/gateways/<id>/mcp.
How a governed agent works
Any app that puts a model and tools behind Dome follows the same shape. The frontend never calls Dome. The agent token is a bearer credential for the Gateway. If it lives in the browser, anyone who opens DevTools can call tools and models as that agent. Keep the token on your backend and have the UI talk only to your server.- UI → your backend. The client posts a user message (or a direct tool click) to your server, never to the Gateway.
- Backend → model (pool). The server
POSTs OpenAI-shaped chat completions to…/gateways/<id>/v1/chat/completions, withmodelset to the pool name (employee-summary), not a vendor model id. The agent token stays in server env. - Model may request tools. If the completion includes
tool_calls, the server maps each short name to a qualified MCP tool andPOSTs JSON-RPCtools/callto…/gateways/<id>/mcp. - Tools return through Dome. Allow, redact, and deny happen on that MCP path before your code sees the result. Denied calls become structured errors you feed back to the model.
- Loop. Tool results go into the message list. The server calls the pool again until the model returns plain text (or you hit a round limit).
- Optional direct tools. A Tools UI can hit your backend’s
/api/tool, which then calls/mcp, with still no browser → Dome path. Useful for proving allow / redact / deny in isolation.
How this application implements it
demo-hr-desk is the reference application for this tutorial. The sections below are a guided tour of how it implements each step above. Skip ahead to Run the agent loop if you want to try it first.UI → your backend
src/App.tsx posts conversation history to /api/chat. The agent token never leaves the server.
src/App.tsx
server/index.ts mounts the backend routes. Only these handlers (and the helpers they call) may use DOME_TOKEN or reach the Gateway.
server/index.ts
Backend → model (pool)
server/llm.ts is the curl from Call a model through a pool in TypeScript: Gateway + /v1/chat/completions, bearer agent token, model = pool.
server/llm.ts
Model may request tools
When the completion includestool_calls, server/agent.ts maps each short name to a qualified MCP tool and calls server/dome.ts.
server/agent.ts
Tools return through Dome
server/dome.ts posts JSON-RPC tools/call to /mcp. Allow, redact, and deny happen on that path before your code sees the result.
server/dome.ts
Loop
server/agent.ts runs the rounds. Tool results go into the message list. The server calls the pool again until the model returns plain text (or you hit a round limit).
server/agent.ts
steps entry is what the Chat transcript and View gateway call sheet show: model requests naming employee-summary, tool requests naming demo-hr/….
Optional direct tools
A Tools UI can hit/api/tool for one MCP call without the model loop. Still no browser → Dome path. Useful for proving allow, redact, and deny in isolation.
src/App.tsx
server/index.ts
Run the agent loop
Start the application:- A model step that named
employee-summary(open View gateway call on that step to confirm"model": "employee-summary"). - A tool step for
get_employeewith contact fields already[REDACTED]. - A final assistant reply that uses the tool result.
get_salary. The gateway denies it, and the transcript shows the refusal, the same Cedar rule as a direct tool click, now inside the loop.
Show allow, redact, and deny
Chat mixed allow, redact, and deny into the model loop. The Tools tab isolates each outcome with one click, so you can see which control fired without the model choosing the tools. Open Tools and click each button in order. Your application does not decide who may call which tool, and it does not mask fields. You already defined those controls on the Gateway. Your code only receives the allowed, redacted, or denied result.
The second result is the important one for Guard Filters. The backend returned a real address. The
redact-contact Guard Filter replaced it on the response path before your proxy deserialized it. The page never asked for masking and cannot switch it off.
Each result lists the outcome. Click View gateway call to open the side sheet with the outbound request and gateway response (token redacted).
Why each click behaved that way:
Troubleshooting
Troubleshooting
These are remediation steps if you get stuck:
-
400withselect a gateway, or an application outcome ofdeniedon every call including list_employees. The gateway URL is missing its/gateways/<id>segment, or the token is wrong. Re-read both values: -
The page loads but every click fails with a network error.
Confirm
npm run devis running and that/api/chatand/api/toolare reachable. The browser talks only to the Hono proxy. Dome is never called from the client. -
Every call is denied, including
list_employees. The rule bundle from the first tutorial is missing from this workspace: -
The employee record still shows a real email address.
The Filter is not assigned to the response direction on this connection:
-
Chat fails with a model / pool error, or tools work but Chat does not.
Confirm the pool tutorial landed:
employee-summaryis attached to Default, andhr-assistanthas thellm:invokerule..envmust setDOME_POOL=employee-summary.
Verify the results
The outcomes you saw are also on the record, attributed to the agent rather than to your process.llm.model_call.attempted/llm.model_call.completedfor Chat turns throughemployee-summary.mcp.tool_call.attemptedandmcp.tool_call.completedforlist_employees(and any tools the model invoked).mcp.tool_result.filteredforget_employee, which is the Filter reporting what it changed.access.deniedforget_salary, carrying the reason your UI showed.
Next steps
You learned how to run model and tool calls from one application through the same agent and Gateway. Keep thesandbox-get-started workspace for the next tutorial. Continue with:
- Govern per end user to authorize from the person, not only the agent
- Develop to authenticate and call Gateways from your application
- Guards to inspect and filter request and response content