Python SDK for NORNR, the control plane for AI agents operating with real budgets.
NORNR sits between agent intent and real-world execution. It evaluates whether an action should happen, routes larger or riskier actions into approval, surfaces anomaly posture before it hardens into normal behavior, and leaves behind a finance-ready audit trail after the action completes.
In practice, the SDK helps you prove one governed spend lane at a time:
Governed runtimefor one canonical decision pathBrowser checkout governancefor paid or risky browser-side actionsMCP control serverfor autonomous tool requests that need policy, review and export
Across all three, the same control-plane objects stay intact:
policy decisionapproval stateanomaly posturereceipt trailaudit export
The fastest install path now assumes one lane first:
provider-wrapperfor one spend-aware ingress into existing provider coderuntimefor one generic governed actionopenai-agentsfor one governed tool lanelanggraphfor one approval branchmcpfor one local tools review lanebrowser-guardfor one checkout lane
The published package name is nornr-agentpay, while the Python import remains agentpay and the lower-level client remains AgentPayClient. For new Python code, start with NornrRuntime for one governed action or NornrWallet when you need the broader client surface.
For a control-plane-first namespace above Wallet, use NornrRuntime.
The SDK defaults to the hosted NORNR control plane at https://nornr.com.
Override it in either of these ways:
- pass
base_url="http://127.0.0.1:3000"directly toAgentPayClient,NornrWallet, orAsyncWallet - set
NORNR_BASE_URL=http://127.0.0.1:3000in your shell or.env
That keeps production usage friction low while still making localhost testing explicit and easy.
Provider-side limits on Modal, OpenAI, Replicate, or cloud infra are still a good idea. Keep them on.
NORNR adds a different layer:
- decide before the expensive run starts
- route larger or unusual spend into approval
- keep a shared operator and finance trail
- replay policy changes over historical decisions
Recommended production pattern:
- NORNR decides whether the run should start.
- Provider-side spend caps stay enabled as a second defensive layer.
- Your code uses checkpointing so interrupted or queued runs can resume safely.
Fastest path:
pip install nornr-agentpayThen start with exactly one of these:
NornrRuntime.execute(...)for one governed actionBrowserCheckoutGuardfor browser-side paid actionscreate_mcp_server(...)for operator review and autonomous-agent control
If you want an installable default instead of a blank file, use nornr init:
nornr init provider-wrapper --owner runtime-agent
nornr init runtime --owner runtime-agent
nornr init openai-agents --owner runtime-agent
nornr init langgraph --owner graph-agent
nornr init mcp --owner desktop-agent --mcp-client claude
nornr init browser-guard --owner browser-agentThat writes a starter env file plus one scaffold for the lane you picked.
From this repo during local development:
pip install -e packages/sdk-pyOptional extras:
pip install nornr-agentpay
pip install "nornr-agentpay[async]"
pip install "nornr-agentpay[pydantic]"
pip install "nornr-agentpay[openai-agents]"
pip install "nornr-agentpay[langchain]"
pip install "nornr-agentpay[crewai]"The PyPI distribution name is nornr-agentpay. The import path stays agentpay.
Build a release locally:
python3 scripts/python-sdk-setup-dev.py
cd packages/sdk-py
python -m build
python -m twine check dist/*To build and publish the package to PyPI when you're ready:
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=<your-pypi-token>
python -m twine upload dist/*For a repeatable pre-release check without uploading, run:
npm run python-sdk:release-checkIf you only run three examples, run these:
Everything else in examples/ is secondary to those three golden paths.
If you want copy-pasteable reference apps instead of smaller snippets, start with:
agentpay is published under the MIT license. See LICENSE.
from agentpay import NornrWallet
wallet = NornrWallet.create(
owner="research-agent",
daily_limit=100,
require_approval_above=25,
base_url="https://nornr.com",
)
decision = wallet.pay(
amount=12.50,
to="openai",
purpose="model inference",
)
if decision.status == "approved":
print("Continue with the paid API call")
elif decision.requires_approval:
print("Queued for review:", decision.to_summary_dict())
else:
print("Blocked:", decision.to_summary_dict())from agentpay import NornrWallet
wallet = NornrWallet.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
)
decision = wallet.pay(
amount=18.0,
to="modal",
purpose="Launch one A100 batch run for embedding refresh",
budget_tags={
"team": "ml-platform",
"project": "embedding-refresh",
"customer": "internal",
"costCenter": "inference",
},
)
if decision.status != "approved":
raise SystemExit(f"Do not launch the GPU run yet. NORNR returned {decision.status}.")
# Keep provider-side caps enabled even after NORNR clears the run.The SDK now exposes typed high-level records for the most common operator flows.
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
decision = wallet.pay(amount=4.5, to="openai", purpose="tool call")
print(decision.status)
print(decision.payment_intent.counterparty)
balance = wallet.balance()
print(balance.available_usd)
review = wallet.audit_review()
print(review.finance_packet.score)Useful high-level methods:
wallet.pay(...)wallet.pending_approvals()wallet.approve_if_needed(...)wallet.balance()wallet.simulate_policy(...)wallet.list_policy_packs()wallet.replay_policy_pack(...)wallet.apply_policy_pack(...)wallet.audit_review()wallet.finance_packet()wallet.timeline()wallet.weekly_review()
The most important new SDK surface is the canonical governed action lifecycle.
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
record = wallet.execute_governed(
action_name="gpu-refresh",
amount=18,
to="modal",
counterparty="modal",
purpose="Launch one governed GPU refresh job",
callback=lambda: {"jobId": "job_123"},
receipt_id="receipt_123",
evidence={"surface": "worker", "jobType": "embedding-refresh"},
)
print(record.execution_status)
print(record.to_summary_dict())Useful runtime helpers:
wallet.begin_governed_action(...)wallet.resume_governed_action(...)wallet.execute_governed(...)run.to_handoff_dict()run.attach_receipt_evidence(...)run.attach_evidence(...)run.attach_receipt(...)run.complete(...)run.fail(...)run.wait_for_approval(...)
If you want the smallest drop-in version of this flow, start from 17_governed_runtime_app.py.
Async parity exists on AsyncWallet as well.
If you want a single entrypoint that reads like NORNR's product model instead of the older wallet-first model:
from agentpay import NornrRuntime
runtime = NornrRuntime.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
)
record = runtime.execute(
action_name="provider-call",
amount=5,
to="openai",
counterparty="openai",
purpose="Run one governed provider call",
callback=lambda: {"ok": True},
receipt_id="receipt_123",
)The SDK now ships opinionated kits for the highest-signal operator outcomes.
from agentpay import NornrWallet, create_framework_kits
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
for kit in create_framework_kits(wallet):
print(kit.to_summary_dict())The currently productized kits are:
create_openai_agents_kit(wallet)create_pydanticai_kit(wallet, business_context=...)create_langgraph_kit(wallet)create_browser_agent_kit(wallet, blocked_domains=[...])create_mcp_kit(wallet)
Each kit bundles the right helpers plus a recommended official governance pack.
Kits can now validate and bootstrap themselves:
from agentpay import NornrWallet, create_openai_agents_kit
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
kit = create_openai_agents_kit(wallet)
print(kit.validate_environment())
print(kit.bootstrap(mode="shadow"))
print(kit.scaffold_config())The SDK now ships reusable scenario templates for the highest-signal integration lanes:
browser_checkout_template()paid_tool_call_template()mcp_local_tool_template()finance_close_template()delegated_sub_agent_budget_template()scenario_templates()
These are intended as reusable onboarding assets, not just examples.
from agentpay import credential_posture, recommended_scopes
template = recommended_scopes("openclaw")
posture = credential_posture(template.scopes)
print(template.to_dict())
print(posture.to_dict())from agentpay import governed_route, nornr_middleware
app.middleware("http")(nornr_middleware())
@app.post("/governed")
@governed_route(
action_name="fastapi-governed-endpoint",
amount=lambda *args, **kwargs: 9,
counterparty=lambda *args, **kwargs: "openai",
purpose=lambda *args, **kwargs: "Serve one governed endpoint call",
)
async def governed_endpoint(request):
return {"ok": True, "traceId": request.state.nornr_trace_id}The MCP server now exposes more than tools:
- resources:
nornr://finance-packetnornr://weekly-reviewnornr://intent-timelinenornr://pending-approvalsnornr://anomaly-inboxnornr://policy-workbenchnornr://finance-close
- prompts:
nornr.operator-guidenornr.policy-simulationnornr.finance-close
See:
Finance-close and weekly-handoff helpers now sit above the raw accounting bridge so teams can ship finance workflows, not just payload transforms.
from agentpay import NornrWallet, run_monthly_close
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
report = run_monthly_close(wallet, provider="fortnox", workspace_label="NORNR finance close")
print(report.to_summary_dict())Useful helpers:
build_finance_close_bundle(...)run_weekly_finance_handoff(...)run_monthly_close(...)
OpenClaw is a distribution surface, not NORNR's product identity. The fit is simple: put policy before paid actions, require approval for risky autonomous actions, and keep a finance-ready audit trail after execution.
from agentpay import NornrWallet, OpenClawGovernanceAdapter
wallet = NornrWallet.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
agent_id="replace-with-agent-id",
)
adapter = OpenClawGovernanceAdapter(wallet)
result = adapter.preflight_paid_action(
action="purchase",
amount_usd=25,
counterparty="openai",
purpose="Run the paid OpenClaw research action",
)
print(result.to_dict())The minimal official skill bundle lives in integrations/openclaw/nornr-governance.
Fastest supporting config path when the same team also runs local MCP clients:
nornr mcp claude-config --server-name nornr --agent-id openclaw-agent
nornr mcp cursor-config --server-name nornr --agent-id openclaw-agentDefault rollout posture:
mcp-local-tools-guardedwhen OpenClaw actions can trigger consequential local tool execution- stop queued actions, inspect the review path, then approve or reject explicitly
- treat the plugin as the control layer before autonomous execution, not as a generic growth or plugin hack
Useful OpenClaw review surfaces:
adapter.pending_approvals()adapter.approve(payment_intent_id, comment=...)adapter.reject(payment_intent_id, comment=...)adapter.anomalies(counterparty=...)
The SDK now exposes explicit least-privilege scope templates instead of leaving every integration to guess.
from agentpay import recommended_scopes, review_scopes
template = recommended_scopes("openclaw")
print(template.to_dict())
review = review_scopes(["workspace:read", "audit:read"], surface="openclaw")
print(review.to_dict())Useful named surfaces:
read-onlyfinance-closebrowser-guardmcpopenclawworker
The SDK now ships a Python-side verifier for NORNR webhook payloads.
from agentpay import verify_webhook_request
verified = verify_webhook_request(
secret="replace-with-webhook-secret",
payload=raw_body,
headers=request.headers,
)
print(verified.event_type)
print(verified.payload["id"])You can also route events through dispatch_webhook_event(...) after verification.
Counterparty posture and sub-agent budget delegation now have first-class SDK helpers.
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
review = wallet.review_counterparty("openai")
print(review.to_dict())
mandate = wallet.delegate_mandate(
target_agent_id="agent_child",
daily_limit=15,
counterparty="openai",
purpose_prefix="delegated-research",
)
print(mandate.to_business_context())adapter.intent_timeline()adapter.review_bundle(counterparty=...)adapter.audit_export()adapter.monthly_close(provider="quickbooks")
Queued decisions surface a direct control-room URL when one exists:
from agentpay import ApprovalRequiredError, NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
try:
wallet.pay(amount=40.0, to="modal", purpose="Large GPU run")
except ApprovalRequiredError as exc:
print(exc) # ... Approve it here: https://nornr.com/app/approvals/...If you want NORNR to feel native inside existing ML or agent code, start here. These are the three highest-signal patterns:
@nornr_guard(...)around an async functionwith wallet.guard(...)around a local expensive blockcreate_spend_aware_openai_client(OpenAI(), wallet, ...)to secure an existing client in one line
from agentpay import NornrWallet, nornr_guard
wallet = NornrWallet.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
)
@nornr_guard(
wallet,
amount=5.0,
counterparty="openai",
purpose="One completion run",
)
async def get_completion(prompt: str) -> str:
return f"ran for {prompt}"You can also gate code blocks with a context manager:
from agentpay import NornrWallet
wallet = NornrWallet.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
)
with wallet.guard(amount=18.0, counterparty="modal", purpose="Launch one GPU run"):
print("Start the expensive run only after NORNR approves it.")The decorator preserves the original function metadata for IDEs and static analysis.
Wrap an existing OpenAI- or Anthropic-style client in one line:
from openai import OpenAI
from agentpay import NornrWallet, create_spend_aware_openai_client
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
client = create_spend_aware_openai_client(
OpenAI(),
wallet,
max_spend_usd=2.5,
purpose="chat completion",
model="gpt-5-mini",
)Use the spend-aware wrapper as a thin ingress into existing provider code. Once the lane becomes consequential for ops, finance or review ownership, upgrade it into governed runtime, MCP control server or browser governance instead of leaving it as a permanent budget-cap utility.
If you want a live policy decision without reserving funds or creating ledger entries, use dry_run=True.
decision = wallet.pay(
amount=18.0,
to="modal",
purpose="Preview one GPU run",
dry_run=True,
)
print(decision.to_summary_dict())For function- or block-level control, use a temporary local budget scope:
from agentpay import budget
with budget(5, purpose_prefix="research", dry_run=True):
wallet.pay(amount=3.0, to="openai", purpose="probe")import asyncio
from agentpay import AsyncWallet
async def main() -> None:
wallet = await AsyncWallet.connect(
api_key="replace-with-nornr-api-key",
base_url="https://nornr.com",
)
decision = await wallet.pay(
amount=6.0,
to="openai",
purpose="async inference run",
)
print(decision.status)
asyncio.run(main())If httpx is installed, the async client uses it directly. Otherwise it falls back to a thread-backed sync transport so you can still integrate it without extra runtime dependencies.
For explicit streaming flows, use guarded_stream(...) or guarded_async_stream(...) so the decision happens before the first chunk leaves the generator.
Use the built-in estimator for cheap pre-flight checks before you even call the model provider:
from agentpay import estimate_cost
estimate = estimate_cost(model="gpt-4.1-mini", prompt="Summarize the anomalies", completion_tokens=400)
print(estimate.estimated_total_usd)If you want an extra local safety net against runaway loops, enable the wallet circuit breaker:
wallet.enable_circuit_breaker(max_requests=10, window_seconds=1, max_spend_usd=5)If you want NORNR to understand why the agent is spending, add business context once and let it flow into the decision:
from agentpay import business_context
with business_context(
reason="Production incident response",
ticket_id="INC-402",
customer_segment="enterprise",
priority="critical",
tags={"workflow": "incident"},
):
decision = wallet.pay(amount=6.0, to="openai", purpose="Summarize outage logs")That metadata is attached to the payment intent as structured context instead of only living in local code.
Use wallet.check(...) when the agent wants to ask for permission before it chooses a plan:
check = wallet.check(
intent="Analyze 1TB of support transcripts",
cost=18.0,
counterparty="openai",
business_context={"reason": "VIP escalation", "ticketId": "SUP-9201", "priority": "high"},
)
if not check.allowed:
print(check.recommended_action)This keeps the agent adaptive: it can scale the plan down or route for approval before it starts spending.
Agreements can now carry simple proof requirements so escrow only releases after the work product is attached and verified:
agreement = client.create_agreement(
{
"buyerAgentId": "agent_123",
"title": "Dataset labeling engagement",
"counterpartyName": "labeling-agent",
"counterpartyDestination": "0xlabelingagent",
"milestones": [
{
"title": "Deliver verified dataset",
"amountUsd": 15,
"proofRequirements": {
"requireSummary": True,
"requireUrl": True,
"requireArtifactHash": True,
"acceptedResultTypes": ["dataset"],
},
}
],
}
)
client.submit_milestone_proof(
agreement_id="agreement_123",
milestone_id="milestone_123",
payload={
"summary": "Uploaded labeled dataset",
"url": "https://example.com/labeled-dataset",
"artifactHash": "sha256:dataset-bundle",
"resultType": "dataset",
},
)If a proof bundle fails the mandate check, NORNR blocks release until the operator reviews, disputes, or resubmits the milestone package.
Use client.get_trust_profile() when you want the workspace-level trust posture and counterparty signals in one payload.
Use the higher-level A2A helper when both sides of an agent service need to attest before escrow is released:
from agentpay import A2AEscrow, AgentAttestation, NornrClient
client = NornrClient(base_url="https://nornr.com", api_key="...")
escrow = A2AEscrow(client)
handshake = escrow.create_three_way_handshake(
buyer_agent_id="manager-agent",
worker_agent_id="research-worker",
worker_destination="0xresearchworker",
title="Research brief delivery",
milestone_title="Submit signed executive brief",
amount_usd=25,
)
outcome = escrow.settle_handshake(
agreement_id=handshake.agreement_id,
milestone_id=handshake.milestone_id,
worker=AgentAttestation(
agent_id="research-worker",
role="worker",
summary="Uploaded the signed brief package",
artifact_hash="sha256:brief-bundle-v1",
),
buyer=AgentAttestation(
agent_id="manager-agent",
role="buyer",
summary="Reviewed and accepted the brief",
status="accepted",
artifact_hash="sha256:brief-bundle-v1",
),
artifact_hash="sha256:brief-bundle-v1",
summary="Dual attestation completed for milestone delivery.",
)
print(outcome.settlement_action)This keeps NORNR in the handshake: create the agreement, attach both attestations, then either release or dispute from the same governed surface.
Use the trust manifest helpers when you want a machine-readable verification layer for a workspace:
manifest = client.get_trust_manifest()
signed_manifest = client.get_signed_trust_manifest()
validation = client.verify_trust_manifest(signed_manifest)
print(manifest["verification"]["tier"])
print(validation["ok"])For counterparty checks:
handshake = client.handshake_trust_manifest(
{
"envelope": signed_counterparty_manifest,
"minTier": "verified",
"minScore": 70,
}
)
print(handshake["decision"])
print(handshake["reasons"])Use the adapter when a LUKSO Universal Profile should act as one authority source, while NORNR still owns mandate, policy decision, counterparty scope and audit export.
The correct reading is:
- LUKSO proves who can hold authority on-chain
- NORNR decides what clears in business context
- the adapter binds one external authority surface into the same NORNR trust and audit model used everywhere else
Use it when:
- an agent is born in a LUKSO-native environment
- a buyer cares about on-chain authority provenance
- you still need the resulting action to survive into review, proof and audit export outside the identity layer
Do not use it to replace NORNR identity semantics or to make NORNR depend on one ecosystem as its only identity backbone.
The adapter stays intentionally thin:
- input: Universal Profile, Key Manager and controller posture
- binding: NORNR mandate, policy decision and counterparty scope
- output: one portable
nornr.lukso-binding.v1packet around the canonical NORNR trust manifest
Example:
from agentpay import LuksoIdentityAdapter, NornrClient
client = NornrClient(base_url="https://nornr.com", api_key="...")
adapter = LuksoIdentityAdapter(client)
identity = adapter.normalize_identity(
{
"universalProfileAddress": "0x1111111111111111111111111111111111111111",
"keyManagerAddress": "0x2222222222222222222222222222222222222222",
"chainId": "42",
"network": "lukso",
"profileMetadata": {
"name": "NORNR Research Worker",
},
"controllers": [
{
"address": "0x3333333333333333333333333333333333333333",
"permissions": ["CALL", "SUPER_CALL"],
}
],
}
)
binding = adapter.build_binding(
identity,
mandate={"scopeLabel": "research procurement lane"},
policy_decision={"policyId": "research-safe", "decisionMode": "review_required"},
counterparty_scope={"allowedCounterparties": ["openai"], "reviewRequiredAboveUsd": 25},
)
print(binding.to_dict())What this means in practice:
- on-chain authority can be verified from LUKSO posture
- off-chain release still runs through NORNR control
- the output is portable enough to travel with the same manifest and verification posture as the rest of the NORNR stack
Public explainer:
Use the resume generator when you want a public-ready summary of a workspace or agent lane:
from agentpay import AgentResumeGenerator, NornrWallet
wallet = NornrWallet.connect(api_key="...", agent_id="research-worker")
resume = AgentResumeGenerator(wallet).build()
print(resume.public_profile_url)
print(resume.to_dict())This pulls the signed trust manifest, trust profile and ecosystem directory into one portable summary:
- verification label and tier
- trust score
- verified proof / dispute / refund rates
- completed agreement count
- inferred capabilities
- public NORNR Verified profile URL
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
simulation = wallet.simulate_policy(
template_id="research-safe",
rollout_mode="shadow",
)
print(simulation.rollout_mode)
print(simulation.summary)
print(simulation.delta)Replay is especially useful when you want to answer:
- would this new policy reduce operator load?
- how much spend would it have prevented?
- how many false positives would it introduce?
Official governance packs let you install a proven control posture instead of hand-tuning every field from scratch.
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
catalog = wallet.list_policy_packs()
print(catalog["recommendedPackId"])
print(catalog["packs"][0]["title"])
replay = wallet.replay_policy_pack("research-safe", mode="shadow")
print(replay["replay"]["candidate"]["summary"])
applied = wallet.apply_policy_pack("research-safe", mode="shadow")
print(applied["install"]["mode"])Current official packs focus on:
- research / model spend
- GPU + compute bursts
- vendor / procurement workflows
- browser ops
- finance review lanes
- customer support spend
- local MCP tools
- finance close / export-heavy workflows
If you want NORNR to plug into MCP-native agent tooling, expose the wallet as a small stdio tool server:
from agentpay import NornrWallet, create_mcp_server
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
server = create_mcp_server(wallet)
print(server.list_tools())The built-in tool set includes:
nornr.check_spendnornr.request_spendnornr.pending_approvalsnornr.balancenornr.finance_packet
This is a good fit when you want NORNR to sit underneath Claude Desktop, Cursor, or another MCP-speaking local agent without teaching each app NORNR from scratch.
The CLI serves the tools over MCP-style stdio using Content-Length framing, so local MCP clients can talk to it directly:
export NORNR_API_KEY=replace-with-nornr-api-key
export NORNR_AGENT_ID=agent_123
nornr mcp manifest
nornr mcp claude-config
nornr mcp serveThe recommended official pack for local MCP clients is mcp-local-tools-guarded.
If you want a minimal operator review reference app instead of a lower-level snippet, start from 19_mcp_review_app.py.
For browser agents, add a purchase-aware guard before the agent clicks a checkout or fills payment details:
from agentpay import BrowserCheckoutGuard, NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
guard = BrowserCheckoutGuard(wallet, blocked_domains=["stripe.com", "openai.com"])
decision = guard.guard_click(
url="https://platform.openai.com/checkout",
selector="button.buy-now",
text="Buy now",
amount=24.0,
purpose="Renew team API credits through a browser agent",
)This is intentionally framework-agnostic so you can wire it into Playwright, browser-use, or your own browser controller.
The recommended official pack for browser agents is browser-ops-guarded.
For checkout-like actions, passing amount= explicitly is still the preferred path. The browser guard can infer amount hints from DOM evidence, but high-risk taxonomies such as checkout, vendor_purchase, and invoice_payment now fail closed if the inferred amount is low-confidence.
For callback-style browser automation:
result = guard.guard_playwright_click(
page,
url="https://platform.openai.com/checkout",
selector="button.buy-now",
text="Buy now",
amount=24.0,
)If you want stronger extraction without hard-coding the amount in your app, pass selectors for the checkout total and cart summary:
result = guard.guard_playwright_click(
page,
url="https://platform.openai.com/checkout",
selector="button.buy-now",
amount=None,
text="Buy now",
capture_evidence=True,
amount_selector="[data-order-total]",
cart_selector="[data-order-summary]",
merchant_selector="[data-merchant-name]",
)If you want a minimal browser governance reference app instead of a small snippet, start from 18_browser_checkout_app.py.
NORNR already captures monthly statements, cost reports, finance packet state, and audit review context. The accounting bridge turns that into journal-friendly payloads:
from agentpay import AccountingBridge, NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
batch = AccountingBridge(wallet, workspace_label="NORNR growth workspace").build_batch()
print(batch.to_quickbooks_payload())Portable serializers are included for:
- QuickBooks journal payloads
- Xero manual journal payloads
- Fortnox voucher payloads
That makes it easier to build an accounting worker or webhook bridge without re-deriving NORNR activity into finance rows yourself.
If you want a webhook-aware export worker:
from agentpay import AccountingWorker
worker = AccountingWorker(wallet, workspace_label="NORNR growth workspace")
result = worker.export(provider="fortnox")
print(result.exported_payload)
print(result.matched_deliveries)If you want the workflow layer instead of just the export payload:
from agentpay import run_weekly_finance_handoff
report = run_weekly_finance_handoff(wallet, provider="xero", workspace_label="NORNR weekly handoff")
print(report.to_summary_dict())If you want a controller agent that manages other agents' budget posture, use the meta-governance helper:
from agentpay import AgentOutcome, NornrWallet, VpOfFinanceController
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
controller = VpOfFinanceController(wallet)
review = controller.review_workspace(
target_agent_id=wallet.agent_id,
current_daily_limit=100.0,
outcome=AgentOutcome(revenue_usd=900.0, leads=10, tasks_completed=31),
)
print(review.recommendations[0])This lets you build a controller lane that raises, tightens, or holds budget caps based on finance packet quality, anomaly pressure, and open operator actions.
The recommended official pack for teams that want cleaner close posture around these decisions is finance-close-export.
from agentpay import NornrWallet
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
review = wallet.audit_review()
print(review.executive_summary)
print(review.finance_packet.open_actions)
print(review.finance_packet.packet_history)This is useful for:
- weekly operator review
- finance packet automation
- compliance exports
- internal approvals and handoffs
If pydantic is installed, typed records can be converted into validated Pydantic models:
from agentpay import NornrWallet, PYDANTIC_AVAILABLE
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
decision = wallet.pay(amount=3.0, to="openai", purpose="validation demo")
if PYDANTIC_AVAILABLE:
validated = decision.to_pydantic()
print(validated.model_dump())You also get a lightweight PydanticAI integration surface:
from agentpay import NornrDeps, NornrWallet, create_pydanticai_tools
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
deps = NornrDeps(wallet=wallet)
tools = create_pydanticai_tools(wallet)FastAPI dependency injection:
from fastapi import Depends, FastAPI
from agentpay import wallet_dependency
app = FastAPI()
get_wallet = wallet_dependency(base_url="https://nornr.com")Canonical governed execution from a request handler:
from agentpay import governed_execute
record = await governed_execute(
request,
action_name="api-provider-call",
amount=9,
counterparty="openai",
purpose="Serve one governed endpoint call",
callback=lambda: {"ok": True},
business_context={"routeName": "api-provider-call"},
)LangGraph state integration:
from agentpay import record_decision, record_handoff, record_resume
state = record_decision(state, decision)
state = record_handoff(state, run)
state = record_resume(state, approved_decision)If your team wants policy definitions in Git instead of hand-edited JSON:
from agentpay import Policy, apply_policy
class ResearchPolicy(Policy):
daily_limit = 50
require_approval_above = 15
allowlist = ["openai", "anthropic"]
apply_policy(wallet, ResearchPolicy)NORNR now captures a redacted execution context for guarded calls, including the calling function, file, line, and sanitized inputs. Sensitive fields such as tokens, passwords, and auth headers are redacted by default.
Wrapped provider calls now attach standardized NORNR business context including:
providerproviderApioperationKindrequestedModelstreamtoolCounttoolNamesmessageCountinputCount
All first-class agent tool adapters now expose the same governed operator surface:
nornr_spendnornr_approvenornr_balancenornr_pending_approvalsnornr_finance_packetnornr_weekly_reviewnornr_anomaly_inboxnornr_review_bundle
from agents import Agent
from agentpay import NornrWallet, create_openai_agents_tools
wallet = NornrWallet.create(
owner="openai-agents-researcher",
daily_limit=100,
require_approval_above=25,
whitelist=["openai"],
base_url="https://nornr.com",
)
agent = Agent(
name="Research agent",
instructions="Use NORNR before any tool or vendor purchase. If NORNR queues a spend, ask for review instead of continuing.",
tools=create_openai_agents_tools(wallet),
)from agentpay import NornrWallet, create_langchain_tools
wallet = NornrWallet.create(
owner="langchain-ops-agent",
daily_limit=100,
require_approval_above=25,
whitelist=["openai", "anthropic"],
base_url="https://nornr.com",
)
tools = create_langchain_tools(wallet)from agentpay import NornrWallet, create_pydanticai_tools
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
tools = create_pydanticai_tools(
wallet,
agent_name="incident-copilot",
model_name="gpt-4.1-mini",
business_context={"workflow": "incident-response"},
)If you already keep NORNR defaults in NornrDeps, use create_pydanticai_tools_for(deps, ...) instead of rebuilding the context each time.
from agentpay import NornrWallet, create_crewai_tools
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
tools = create_crewai_tools(
wallet,
crew_id="crew_1",
task_id="task_7",
role="researcher",
business_context={"mission": "vendor-research"},
)If your CrewAI integration already has a task config object, use create_crewai_task_tools(wallet, CrewTaskConfig(...)).
If you want provider-native entrypoints instead of the generic wrappers:
from agentpay import (
create_spend_aware_anthropic_client,
create_spend_aware_openai_client,
wrap_anthropic_client,
wrap_openai_client,
)create_spend_aware_openai_client(...)is the thinnest NORNR ingress for existing OpenAI-style code when the immediate need is a max-spend wrapper with a clean upgrade pathcreate_spend_aware_anthropic_client(...)does the same for Anthropic-stylemessageswrap_openai_client(...)is the shortest path for OpenAI-styleresponsesandchat.completionswrap_anthropic_client(...)is the shortest path for Anthropic-stylemessageswrap(...)andwrap_async(...)still exist for mixed or custom provider SDKs
Use AgentPayClient when you want direct API access.
from agentpay import AgentPayClient
public_client = AgentPayClient(base_url="https://nornr.com")
onboarding = public_client.onboard(
{
"workspaceName": "Atlas Agents",
"agentName": "research-agent",
"dailyLimitUsd": 50,
"requireApprovalOverUsd": 20,
}
)
client = public_client.with_api_key(onboarding["apiKey"]["key"])
client.create_budget_cap(
{
"dimension": "team",
"value": "growth",
"limitUsd": 500,
"action": "queue",
}
)The SDK raises typed errors for the most common failure classes:
AuthenticationErrorValidationErrorRateLimitErrorTransportErrorAgentPayError
from agentpay import AuthenticationError, RateLimitError, TransportError- relying on provider caps alone without a pre-run decision layer
- treating
queuedthe same asapproved - forgetting checkpointing for long ML or compute runs
- skipping budget tags, which makes finance review much weaker later
See examples/python/README.md for the curated list.
Strong starting points:
- wallet_quickstart.py
- modal_gpu_budget.py
- batch_inference_with_approval.py
- checkpoint_safe_run.py
- weekly_finance_review.py
- dry_run_preview.py
- fastapi_dependency.py
- langgraph_state.py
- context_aware_spend.py
- policy_check.py
- wrap_openai_client.py
- pydanticai_agent.py
- openai_agents_sdk_wallet.py
- langchain_wallet_tools.py
- mcp_server.py
- browser_checkout_guard.py
- controller_agent.py
- accounting_bridge.py
The package now exposes a tiny nornr CLI for bootstrapping local env files:
nornr login --api-key agpk_live_123 --path .env
nornr init --owner research-agent --path .env.nornr
nornr estimate-cost --model gpt-4.1-mini --prompt "hello"NORNR can attach spend metadata to your logs or current OpenTelemetry span:
import logging
from agentpay import NornrWallet, annotate_current_span, bind_logger
wallet = NornrWallet.connect(api_key="replace-with-nornr-api-key", base_url="https://nornr.com")
decision = wallet.pay(amount=3.5, to="openai", purpose="traceable completion")
logger = bind_logger(logging.getLogger("agent"), decision)
logger.info("Completion approved")
annotate_current_span(decision)MIT