Skip to content

Integrating AI Agents

Frank Yglesias Bertheau edited this page Jul 7, 2026 · 1 revision
<h1>ᚾ&nbsp;NornGate — Integrating AI Agents</h1>

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.


ᚨ Before you start

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.

ᛗ Give the agent an identity

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).


ᚷ Make state-changing calls idempotent

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.


ᚲ Read the verdict

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).


ᚦ Handle approvals (G2)

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.


ᛉ What the agent must not do

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.


ᛒ Verify

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 403 record naming the gate.
  • An idempotent retry produces one outcome, not two.

Worked example — a retrieval agent writes PII.

  1. Agent POSTs the write with an Idempotency-Key.
  2. G1 allows with obligation require:approval → routed to G2; not auto-consented → 408.
  3. A human approves (debiting the approval budget).
  4. Agent retries with the same key → G4 commits once → receipt returned.
  5. Urd shows the full chain — G1 allow+obligation, G2 approve, G4 commit — one outcome, reproducible via audit replay.

ᚱ Next steps

  • Configuring Gates — set the approval threshold and autoConsent rules 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).

Iconography

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.

ᚾ NornGate — 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](API-Reference)); reconcile against your SDK.


ᚨ Before you start

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.

ᛗ Give the agent an identity

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)).


ᚷ Make state-changing calls idempotent

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.


ᚲ Read the verdict

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)).


ᚦ Handle approvals (G2)

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](Configuring-Gates)); the approval budget is a finite resource.


ᛉ What the agent must not do

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.


ᛒ Verify

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-15m

Then check three things:

  • Allowed actions produce outcome records with receipts.
  • A denied action (e.g. a PII write without approval) produces a 403 record naming the gate.
  • An idempotent retry produces one outcome, not two.

Worked example — a retrieval agent writes PII.

  1. Agent POSTs the write with an Idempotency-Key.
  2. G1 allows with obligation require:approval → routed to G2; not auto-consented → 408.
  3. A human approves (debiting the approval budget).
  4. Agent retries with the same key → G4 commits once → receipt returned.
  5. Urd shows the full chain — G1 allow+obligation, G2 approve, G4 commit — one outcome, reproducible via audit replay.

ᚱ Next steps

  • [Configuring Gates](Configuring-Gates) — set the approval threshold and autoConsent rules 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).

Iconography

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).

Clone this wiki locally