-
Notifications
You must be signed in to change notification settings - Fork 0
Integrating AI Agents
Put an agent behind the gated path. The important thing first: enforcement is transparent. You do not rewrite the agent to call a special endpoint — you place it on the mesh and it inherits G0–G4 on every outbound call. Integration is four things (identity, idempotency, reading verdicts, approvals) and two hard don'ts.
This is framework-agnostic — LangGraph, CrewAI, the OpenAI Agents SDK, or your own loop all integrate the same way, because the gates live at the boundary, not in your code. The snippets show the HTTP contract (API Reference); reconcile against your SDK.
Two facts shape everything below:
- Transparent interception. The agent makes its normal request to a resource; the Envoy sidecar runs G0–G4 inline and returns either the upstream response or a fail-closed status. There is no path to the resource that skips the gates.
- The boundary is the product, not the model. The agent's reasoning stays nondeterministic; the gates make its effects deterministic and auditable. You do not make the agent "safe" — you make its actions gated.
Deploy the agent as a workload in a realm. SPIRE issues it a short-lived SVID; mTLS is automatic. That identity becomes the subject in every policy decision.
Why: without a valid SVID the agent is denied 401 at G0 before anything else runs — and because the subject comes from the signed SVID, not the request body, the agent cannot spoof a more privileged role (Gates & Attributes).
Attach an Idempotency-Key to every request that changes state.
curl -X POST https://app.norngate.com/records/4471 \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"field":"value"}'
Why: the key is what makes G4 exactly-once — a repeated request returns the already-committed outcome instead of committing twice. This is what makes retries on 409/503 safe. Never send a state-changing request without one.
On allow, read the verdict headers; on deny, parse the RFC 9457 body and branch on the gate field.
resp = client.post(url, headers={"Idempotency-Key": key}, json=body)
if resp.status_code == 200:
outcome_id = resp.headers["X-NornGate-Outcome-Id"]
receipt = resp.links["receipt"]["url"] # verify + reconcile billing
elif resp.status_code in (409, 503):
retry(key) # safe: same key, exactly-once
else:
problem = resp.json() # application/problem+json
gate = problem.get("gate") # which gate denied — or None
raise Denied(gate, problem["detail"], problem["requestId"])
Why the gate field matters: it tells you what to fix — ingress means fix the request, policy means the action is not permitted, approval means get a human, sandbox means the action itself failed. If there is no gate, the error came from your upstream service, not a gate (Error Codes).
When an action needs a human, the request does not silently hang — it returns an approval-timeout. Poll (or subscribe), then retry with the same key.
if problem["type"].endswith("approval-timeout"): # 408
if poll_approval(problem["requestId"]) == "approved":
retry(key) # same key → still commits once
A human decides via norngate-cli approvals decide <id> --approve or the approvals API. Why the same key: the approved retry commits the same action exactly once — the approval does not create a second effect. Keep approvals rare by moving low-risk actions to autoConsent rules (Configuring Gates); the approval budget is a finite resource.
Two invariants separate a governed agent from a copilot holding keys:
- It must not hold raw credentials. Secrets stay in the gateway and inject only into a committed action. An API key embedded in the agent defeats credential starvation — a compromised agent should have nothing to steal (Security & Fail-closed).
- It must not make the authorization decision. The agent plans; G1 decides. An agent that self-authorizes is nondeterministic and unauditable — the decision must live at the deterministic boundary, where there is no LLM in the loop.
Why: break either and the guarantees collapse — you are back to trusting the model, which is the thing the platform exists to avoid.
The agent is integrated only when its actions are visible in Urd under its own identity.
# every action carries the agent's SVID as subject
norngate-cli audit query \
--subject spiffe://norngate.com/jotunheim/retrieval-agent --from now-15m
Then check three things:
- Allowed actions produce outcome records with receipts.
-
A denied action (e.g. a PII write without approval) produces a
403record naming the gate. - An idempotent retry produces one outcome, not two.
Worked example — a retrieval agent writes PII.
- Agent
POSTs the write with anIdempotency-Key. - G1 allows with obligation
require:approval→ routed to G2; not auto-consented →408. - A human approves (debiting the approval budget).
- Agent retries with the same key → G4 commits once → receipt returned.
- Urd shows the full chain — G1 allow+obligation, G2 approve, G4 commit — one outcome, reproducible via
audit replay.
-
Configuring Gates — set the approval threshold and
autoConsentrules the agent hits. - API Reference — the full request/response contract.
- Error Codes — every denial the agent must handle.
- Agent Registry — NornGate's own internal agents (Óðinn, Huginn/Muninn, Thor).
Section glyphs are Elder Futhark runes (Unicode Runic block, U+16A0–U+16FF) — semantic, not emoji. Full set on Home:
| Rune | Name | Gloss | Marks |
|---|---|---|---|
| ᚾ | Nauðiz | need, constraint | the platform mark |
| ᚨ | Ansuz | the word, instruction | before you start |
| ᛗ | Mannaz | the self, identity | give the agent an identity |
| ᚷ | Gebo | one-for-one exchange | idempotency |
| ᚲ | Kenaz | the torch, seeing | read the verdict |
| ᚦ | Thurisaz | thorn, the approval gate | handle approvals |
| ᛉ | Algiz | protection, warding | what the agent must not do |
| ᛒ | Berkanan | growth, the change verified | verify |
| ᚱ | Raidō | the ride, the road | next steps |
References. SPIFFE/SPIRE (workload identity); RFC 9457 (Problem Details); RFC 6750 (Bearer). Contract and codes: API Reference, Error Codes. Naming doctrine: Prose Edda / Poetic Edda, per Norse Cosmology & Platform Design.
Put an agent behind the gated path. The important thing first: enforcement is transparent. You do not rewrite the agent to call a special endpoint — you place it on the mesh and it inherits G0–G4 on every outbound call. Integration is four things (identity, idempotency, reading verdicts, approvals) and two hard don'ts.
This is framework-agnostic — LangGraph, CrewAI, the OpenAI Agents SDK, or your own loop all integrate the same way, because the gates live at the boundary, not in your code. The snippets show the HTTP contract ([API Reference](API-Reference)); reconcile against your SDK.
Two facts shape everything below:
- Transparent interception. The agent makes its normal request to a resource; the Envoy sidecar runs G0–G4 inline and returns either the upstream response or a fail-closed status. There is no path to the resource that skips the gates.
- The boundary is the product, not the model. The agent's reasoning stays nondeterministic; the gates make its effects deterministic and auditable. You do not make the agent "safe" — you make its actions gated.
Deploy the agent as a workload in a realm. SPIRE issues it a short-lived SVID; mTLS is automatic. That identity becomes the subject in every policy decision.
Why: without a valid SVID the agent is denied 401 at G0 before anything else runs — and because the subject comes from the signed SVID, not the request body, the agent cannot spoof a more privileged role ([Gates & Attributes](Gates-and-Attributes)).
Attach an Idempotency-Key to every request that changes state.
curl -X POST https://app.norngate.com/records/4471 \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"field":"value"}'Why: the key is what makes G4 exactly-once — a repeated request returns the already-committed outcome instead of committing twice. This is what makes retries on 409/503 safe. Never send a state-changing request without one.
On allow, read the verdict headers; on deny, parse the RFC 9457 body and branch on the gate field.
resp = client.post(url, headers={"Idempotency-Key": key}, json=body)
if resp.status_code == 200:
outcome_id = resp.headers["X-NornGate-Outcome-Id"]
receipt = resp.links["receipt"]["url"] # verify + reconcile billing
elif resp.status_code in (409, 503):
retry(key) # safe: same key, exactly-once
else:
problem = resp.json() # application/problem+json
gate = problem.get("gate") # which gate denied — or None
raise Denied(gate, problem["detail"], problem["requestId"])Why the gate field matters: it tells you what to fix — ingress means fix the request, policy means the action is not permitted, approval means get a human, sandbox means the action itself failed. If there is no gate, the error came from your upstream service, not a gate ([Error Codes](Error-Codes)).
When an action needs a human, the request does not silently hang — it returns an approval-timeout. Poll (or subscribe), then retry with the same key.
if problem["type"].endswith("approval-timeout"): # 408
if poll_approval(problem["requestId"]) == "approved":
retry(key) # same key → still commits onceA human decides via norngate-cli approvals decide <id> --approve or the approvals API. Why the same key: the approved retry commits the same action exactly once — the approval does not create a second effect. Keep approvals rare by moving low-risk actions to autoConsent rules ([Configuring Gates](Configuring-Gates)); the approval budget is a finite resource.
Two invariants separate a governed agent from a copilot holding keys:
- It must not hold raw credentials. Secrets stay in the gateway and inject only into a committed action. An API key embedded in the agent defeats credential starvation — a compromised agent should have nothing to steal ([Security & Fail-closed](Security-and-Fail-Closed)).
- It must not make the authorization decision. The agent plans; G1 decides. An agent that self-authorizes is nondeterministic and unauditable — the decision must live at the deterministic boundary, where there is no LLM in the loop.
Why: break either and the guarantees collapse — you are back to trusting the model, which is the thing the platform exists to avoid.
The agent is integrated only when its actions are visible in [Urd](Urd-Ledger) under its own identity.
# every action carries the agent's SVID as subject
norngate-cli audit query \
--subject spiffe://norngate.com/jotunheim/retrieval-agent --from now-15mThen check three things:
- Allowed actions produce outcome records with receipts.
-
A denied action (e.g. a PII write without approval) produces a
403record naming the gate. - An idempotent retry produces one outcome, not two.
Worked example — a retrieval agent writes PII.
- Agent
POSTs the write with anIdempotency-Key. - G1 allows with obligation
require:approval→ routed to G2; not auto-consented →408. - A human approves (debiting the approval budget).
- Agent retries with the same key → G4 commits once → receipt returned.
- Urd shows the full chain — G1 allow+obligation, G2 approve, G4 commit — one outcome, reproducible via
audit replay.
- [Configuring Gates](Configuring-Gates) — set the approval threshold and
autoConsentrules the agent hits. - [API Reference](API-Reference) — the full request/response contract.
- [Error Codes](Error-Codes) — every denial the agent must handle.
- [Agent Registry](Agent-Registry) — NornGate's own internal agents (Óðinn, Huginn/Muninn, Thor).
Section glyphs are Elder Futhark runes (Unicode Runic block, U+16A0–U+16FF) — semantic, not emoji. Full set on [Home](Home#iconography):
| Rune | Name | Gloss | Marks |
|---|---|---|---|
| ᚾ | Nauðiz | need, constraint | the platform mark |
| ᚨ | Ansuz | the word, instruction | before you start |
| ᛗ | Mannaz | the self, identity | give the agent an identity |
| ᚷ | Gebo | one-for-one exchange | idempotency |
| ᚲ | Kenaz | the torch, seeing | read the verdict |
| ᚦ | Thurisaz | thorn, the approval gate | handle approvals |
| ᛉ | Algiz | protection, warding | what the agent must not do |
| ᛒ | Berkanan | growth, the change verified | verify |
| ᚱ | Raidō | the ride, the road | next steps |
References. [SPIFFE](https://spiffe.io/)/SPIRE (workload identity); [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (Problem Details); [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) (Bearer). Contract and codes: [API Reference](API-Reference), [Error Codes](Error-Codes). Naming doctrine: Prose Edda / Poetic Edda, per [Norse Cosmology & Platform Design](Norse-Cosmology-and-Platform-Design).
Welcome
Getting Started
Core Concepts
Guides
- Guides
- Configuring Gates
- Setting Up Subdomains (Realms)
- Integrating AI Agents
- Monitoring & Observability
- Disaster Recovery (Ragnarök Drill)
- Custom Domains & TLS
Reference
Silence means no.