Spend guardrail for AI agents

The budget that actually blocks.

Bridle sits in front of a payment: it reserves budget before paying, commits on settlement, releases on failure. It holds under real concurrency and fails closed — so an agent cannot overspend, even when twenty requests land at the same millisecond.

$ npm install @igarzatech/bridle
Published with npm provenance Apache-2.0 Non-custodial Node ≥ 20 · TypeScript
Live · the bug, and the fix

Watch the budget hold

One agent, a $50 window, twenty $4 payments fired at once. Flip between a naïve guardrail and Bridle, then fire.

● loading @igarzatech/bridle… budget $50.00  ·  tx $4.00  ·  concurrency 20
Reserved this window $0.00 / $50.00
$0limit $50$80
Idle — pick a mode and fire the payments.
Why it's hard

The failure isn't the limit. It's concurrency.

A naïve guardrail checks spent + amount ≤ limit, then inserts. Fire twenty parallel requests and every one reads the same remaining balance, every one passes, and the agent overspends — while the unit tests stay green, because a mock has no notion of two things happening at once. You can't SELECT sum(…) FOR UPDATE an aggregate, and row locks don't protect a brand-new agent with zero rows. Bridle locks the decision, not the data — and ships the proof.

01

Reserve → commit → release

Budget is reserved before the payment and committed on settlement. Decisions happen before money moves, not reconciled after.

02

Concurrency guarantee

Serialized per (agent, currency) with a Postgres advisory lock — even with no ledger row yet. A ≥20-way real-Postgres test fails CI if the DB is missing.

03

Declarative policy engine

Recipient allow/deny lists, per-category limits, and time windows — evaluated inside the same lock, with deterministic precedence.

04

Auditable decisions

Every allow and deny goes to a pluggable audit sink with a reason code. Best-effort and isolated — a failing sink can never break a valid payment.

05

Non-custodial, cross-rail

Bridle never holds keys and never moves funds — it only counts and decides. It sits above any wallet, rail, or x402 facilitator you bring.

06

Fail closed, by design

No policy, missing context, an unknown rule → deny with a typed error. A guardrail that fails open is not a guardrail.

Quickstart

Wrap a payment. Get the guarantee.

You bring the Postgres pool and the payment call; Bridle decides. Fail-safe by default — with no policy and no default budget, it denies.

agent-spend.ts
import { BridleGuard } from '@igarzatech/bridle';
import { PostgresStorageAdapter } from '@igarzatech/bridle/postgres';
import { Pool } from 'pg';

// You own the pool. Bridle never creates connections or holds funds.
const storage = new PostgresStorageAdapter(new Pool());
await storage.migrate();

const guard = new BridleGuard({ storage, config: { defaultBudget } });

// Reserve before paying. Under concurrency, exactly the budget gets through.
await guard.checkAndReserve({ agentAddress, currency: 'USDC', amount: '4.00', reservationId });
await pay();                 // your rail / x402 facilitator
await guard.commit(reservationId);  // or .release() on failure
Go deeper

Docs, source, and the story