Agent Purchase Guardrails
An open, fail-closed specification for keeping an agent's spending inside a human's limits, and for the machine-readable receipt it emits for every charge. The receipt is a Nymrel Record — the same verifiable format behind Nymrel Verified.
The five guardrails
Before anything is charged, the human approves an itemized list — each item, its price, and the merchant. The approval is bound with a tamper-evident hash, so every later receipt can prove exactly which approved set it charged against.
The whole session has a ceiling. A charge that would cross it is blocked — not flagged, not logged-and-allowed — even if the item was on the approved list. The cap is a backstop the agent cannot talk its way past.
Each allowed charge emits a Purchase Receipt: a machine-readable record of what was bought, the authorization it charged against, and the budget that remained. The buyer and the merchant's own agent can both fetch it and check its hash.
Any single item at or above a threshold you set stops and asks for a fresh confirmation before it proceeds. Small, expected buys flow; the one large charge that would make you wince waits for a human.
If you send any message while the run is going, everything pauses. Nothing else is charged until you explicitly resume. Speaking up is a brake, not a suggestion.
Design principles
Every amount is an integer count of the currency's minor unit (cents for USD). Floating-point money is rejected at construction — a whole class of rounding bugs is refused entry.
The engine's default answer is to withhold a charge. An expired authorization, an unrecognized item, an altered price, a paused run — each blocks. Ambiguity never resolves to spending.
evaluateCharge() returns a decision — allow, block, require-reconfirmation, or paused — with a machine reason code. It has no side effects and touches no payment rail. Your integration applies an allowed charge and emits its receipt.
A Purchase Receipt reuses the Verified Claims Record envelope unchanged: the same issuer/subject/freshness/integrity shapes and the same four-value, fail-closed status vocabulary. It is a new record type, not a new format — and there is no fifth status.
A receipt never invents an outcome. What it cannot observe at charge time — delivery, fulfillment, a cryptographic countersignature — is listed in notMeasured[], not dressed up as a low-confidence claim.
v0.1 receipts carry a sha256 content hash anyone can re-derive, but no signature; they disclose signatureStatus: "unsigned-v0.1". Cryptographic signatures and a merchant countersignature are the v0.2 roadmap.
The Purchase Receipt
Every allowed charge emits one Purchase Receipt (recordType: "PurchaseReceipt"). It reuses the Verified Claims Record envelope unchanged and adds three blocks: what was purchased, the human authorization it charged against (by hash), and the guardrail decision that allowed it. Its claims[] are sourced; its notMeasured[] lists what it does not know; its integrity.contentHash is a sha256 any consumer — including the merchant's agent — can re-derive.
An agent consuming a receipt should: fetch it, validate it against the JSON Schema, check validUntil, re-derive the content hash, and trust only verified claims that carry a source.
Reference implementation
@nymrel/agent-purchase-guardrails— a zero-dependency TypeScript package (Node's built-in crypto only). Pure, deterministic, framework-agnostic. Open under the MIT license.
import {
createPolicy, createPreAuthorization, openRun,
evaluateCharge, applyCharge, buildPurchaseReceipt,
} from "@nymrel/agent-purchase-guardrails";
const policy = createPolicy({
currency: "USD",
sessionSpendCapMinor: 22000, // $220.00 hard cap
perItemReconfirmThresholdMinor: 6000, // $60.00 needs re-confirmation
});
const decision = evaluateCharge(policy, preAuth, run, request);
// decision.outcome: "allow" | "block" | "require-reconfirmation" | "paused"
// The engine decides; your code applies the charge and emits its receipt.Versioning & roadmap
- v0.1 is unsigned and discloses it (
signatureStatus: "unsigned-v0.1"). The content hash detects corruption; it does not authenticate the publisher. - v0.2 roadmap: cryptographic signatures, a merchant countersignature endpoint, and a hosted receipt validator.
- Breaking changes bump the minor version while pre-1.0; the namespace IRI carries the format version so versions can coexist.