NORNRMandates, approvals and evidence for autonomous agents.
Guide / Anthropic / Claude
10 minutesHow to govern Claude API spend in autonomous agent workflows
Set budget limits, mandate checks and human approval gates on Claude API calls from autonomous agents using NORNR — using the Anthropic SDK alongside wallet.pay().
1. Why Claude API calls need a governance gate
The Anthropic Claude API is powerful and increasingly used as the backbone of autonomous agents that run without a human watching every step. When those agents operate at scale, a single runaway loop or a misrouted task can generate hundreds of unintended API calls within minutes. The Anthropic account-level spending limit is a blunt instrument — it applies across every project and every agent in your account, and it triggers only after money has already moved.
NORNR provides a pre-transaction governance layer you add in Python, next to your existing anthropic import. Before any call to client.messages.create(), your agent calls wallet.pay(). NORNR checks the call against your policy — daily limit, per-call threshold, counterparty allowlist — and returns a decision: approved, queued, or rejected. Only on approved does your agent proceed to the actual Claude call. Every other outcome is logged, and the agent stops cleanly rather than burning budget undetected.
2. Installation
Install both packages in the same virtual environment. They are independent — NORNR does not patch or wrap the Anthropic SDK; it simply runs before it.
pip install anthropic nornr
3. Create a wallet with a per-call budget
A NORNR wallet holds your spend policy for a specific agent or workflow. The daily_limit caps total spend for the day. The require_approval_above threshold triggers human review for any single call that exceeds that amount. The allowed_counterparties list ensures only Anthropic API calls can be authorized from this wallet — any attempt to route spend to an unexpected vendor is rejected outright.
import nornr
nornr.api_key = "nornr-..."
wallet = nornr.Wallet.create(
owner="claude-research-agent",
daily_limit=25.00,
require_approval_above=5.00,
allowed_counterparties=["anthropic"],
)
print(wallet.id) # store this as NORNR_WALLET_ID in your env
You only need to create the wallet once. Save the returned ID as an environment variable and reference it in your agent code at runtime.
4. Calling wallet.pay() before client.messages.create()
This is the core pattern. Every time your agent is about to call the Anthropic API, it first calls wallet.pay() with the estimated cost, the counterparty ("anthropic"), and a human-readable purpose. NORNR evaluates the request against your policy in milliseconds and returns a decision object. The status field determines what happens next.
import anthropic
import nornr
import os
nornr.api_key = os.environ["NORNR_API_KEY"]
wallet_id = os.environ["NORNR_WALLET_ID"]
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
wallet = nornr.Wallet(wallet_id)
def governed_claude_call(prompt: str, estimated_cost: float = 0.02):
# Step 1 — ask NORNR for permission
decision = wallet.pay(
amount=estimated_cost,
to="anthropic",
purpose=f"Claude message: {prompt[:60]}",
)
if decision.status == "approved":
# Step 2 — proceed with the actual Claude call
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
elif decision.status == "queued":
# Step 3 — notify reviewer and pause the agent
print(f"[NORNR] Decision queued for human review: {decision.id}")
return None # caller handles the pause
else: # rejected
# Step 4 — log and halt without spending
print(f"[NORNR] Spend rejected: {decision.reasons}")
raise PermissionError(f"Claude call blocked by policy: {decision.reasons}")
5. What each decision state means
- approved — the spend is within mandate, below the approval threshold, and Anthropic is on the counterparty allowlist. Call
client.messages.create()immediately. - queued — the estimated cost exceeded the per-call review threshold. The intended spend is held in NORNR. A human reviewer sees it in the control room and can approve or reject. Your agent should pause and wait for a webhook callback before retrying.
- rejected — policy blocked the call outright. Common reasons:
daily_limit_exceeded,counterparty_not_allowed,mandate_not_found. No money moves, no API call is made, and the reason is written to the audit trail.
6. Handling queued decisions with a webhook
When a decision is queued, NORNR holds the intent and notifies you via a webhook when the reviewer resolves it. Configure the webhook URL in the NORNR control room under Settings → Webhooks. Your server receives a POST with the resolved status and the original decisionId.
# FastAPI webhook handler — resume agent when reviewer approves
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/nornr-webhook")
async def nornr_webhook(request: Request):
payload = await request.json()
decision_id = payload["decisionId"]
status = payload["status"]
if status == "approved":
# look up the pending agent task by decisionId and resume it
resume_agent_task(decision_id)
else:
# status is "rejected" — log and discard the pending task
cancel_agent_task(decision_id, reason=payload.get("reasons", []))
7. Common mistakes
- Placing wallet.pay() after the Claude call. The governance check must run before money moves. Checking after the fact creates an audit record but does not prevent the spend.
- Using a fixed amount of 0.00. An amount of zero will always pass the threshold check but produces a misleading audit trail. Estimate the cost from the prompt length and model pricing, even if the estimate is approximate.
- Not handling queued as a first-class case. If your code only branches on
approvedvsnot approved, a queued decision is silently treated as a rejection. The reviewer approves it in the control room but your agent never resumes. Add explicit queued handling. - Sharing one wallet across all agents. A shared wallet makes it impossible to attribute spend to a specific agent or task. Create one wallet per logical agent or workflow so the audit trail remains meaningful.