Skip to content
Warrantv0.1

Move from a spend cap to a warrant

A cap limits how much goes wrong. A warrant limits whether it goes wrong at all.

A spend cap bounds your losses after the fact. A warrant holds the money until the deliverable is checked, so the loss does not happen.

cap:      pay → hope → discover → dispute → maybe recover
warrant:  hold → check → release, part-release, or refund

What a cap actually gives you

Most agent platforms offer a monthly ceiling. It is worth having and it answers exactly one question: how bad can this month get?

It does not answer whether any individual payment should have been made. Money leaves on request, and everything after that is reconciliation — noticing, then arguing.

What changes

Spend capWarrant
When money leavesOn requestAfter the check passes
Bad deliverableAlready paidNever left held
RecourseDispute, invoice, chargebackRefund, instantly
Partial valueAll or nothingreleased_bps
RecordYour logsA trace the payee can read

Keep the cap as well

Warrants do not replace ceilings — sessions give you both, and the cap still does its job:

{
  "amount_ceiling": "50000000",
  "warrant_limit": 20,
  "failure_budget": 3,
  "expires_at": "2026-08-16T12:00:00.000Z"
}

The ceiling is cumulative across the session. failure_budget is the one a cap never gave you: three consecutive failures revoke the session, and a pattern across sessions pauses the agent. A cap lets an agent fail identically nine hundred times until the number runs out.

Migrating one call

Before — pay, then hope:

await pay(supplier, "8.40");
const data = await fetch(sourceUrl).then((r) => r.json());
// If data is stale, the money is already gone.

After — hold, then check:

import { Warrant, usdc } from "@gowarrant/sdk";
const warrant = new Warrant();

const w = await warrant.open({
  amount: usdc("8.40"),
  payee: supplier,
  spec: {
    clauses: [
      { kind: "http.status", equals: 200 },
      { kind: "data.freshness", maxAgeSeconds: 60 },
    ],
  },
  deadline: new Date(Date.now() + 3_600_000),
});

const after = await warrant.verify(w.id, { source_url: sourceUrl });
// released | partially_released | escalated | refunded

The spec is the part that carries thought. "What would make this payment wrong?" is a question a cap never made you answer, and the clauses are the answer written down.

Start strict

Use the default policy — only a clean pass pays, everything else escalates. Watch the queue for a week. The failures that turn out to be routine become bands; the ones that turn out to be real are the ones the cap was silently paying for.

Amounts

The one mechanical difference. Amounts are integer strings of the smallest unit, ^[0-9]+$ — not floats, not decimals.

"8400000"    8.40 USDC
8400000      rejected — a JSON number
"8.40"       rejected — not the smallest unit

usdc("8.40") in the SDK does the conversion and will not compile if you hand it a number.