usertrust
Concepts

Two-Phase Spend

How usertrust uses banking-style authorization holds for every LLM call.

Every governed LLM call in usertrust follows a two-phase spend lifecycle. This is the same pattern banks use for credit card authorization holds: reserve funds first, settle or release after the outcome is known.

No LLM call executes without a pending hold. No budget can be overspent.

The Banking Analogy

When you swipe a credit card at a restaurant, the bank does not immediately deduct the final amount. Instead:

  1. The terminal places an authorization hold for the estimated total
  2. The restaurant submits the final charge (which may differ slightly)
  3. The bank settles the hold into a real charge, or voids it if the transaction is cancelled

usertrust applies this identical pattern to every LLM API call. The "card" is your token budget. The "restaurant" is the LLM provider.

The Four Steps

Every call through a governed client executes this sequence inside govern.ts:

// 1. PENDING — reserve tokens atomically
await engine.spendPending({ transferId, amount: estimatedCost });
try {
  // 2. Forward to LLM SDK
  const response = await originalFn.apply(thisArg, args);
  // 3. POST — settle the hold
  await engine.postPendingSpend(transferId);
  return { response, governance: receipt };
} catch (err) {
  // 4. VOID — release the hold
  await engine.voidPendingSpend(transferId);
  throw err;
}
StepTigerBeetle OperationBudget Effect
PENDINGCreate pending transferTokens reserved (debited from available)
ForwardNone (pure LLM call)No change
POSTPost pending transferHold becomes permanent spend
VOIDVoid pending transferReserved tokens returned to available

Five Failure Modes

The two-phase lifecycle must handle every combination of success and failure across the LLM call, the ledger, and the audit chain. These are defined in Spec Section 15 and are exhaustively tested.

15.1 -- LLM Succeeds, POST Fails

The LLM returned a valid response, but the ledger failed to settle the hold. The response is still returned to the caller with settled: false on the receipt. A settlement_ambiguous event is written to the audit chain.

The hold remains pending in TigerBeetle and will eventually expire.

15.2 -- LLM Fails (Retryable)

The LLM provider returned an error (rate limit, server error, etc.). The pending hold is voided immediately, returning tokens to the available budget. The original error is propagated to the caller.

15.3 -- Audit Write Fails After POST

The LLM call and ledger settlement both succeeded, but the audit chain write failed. The response is returned with an auditDegraded flag. The failed audit event is written to the dead-letter queue.

usertrust never fails the user on audit degradation. If the LLM call succeeded and the ledger settled, the response is always returned. Audit failures are recoverable.

15.4 -- TigerBeetle Unreachable

The ledger is unreachable before the LLM call starts. usertrust throws a LedgerUnavailableError and does not forward the request to the provider. No tokens are at risk because no hold was placed.

Streaming Failure

The LLM stream starts successfully but errors mid-stream. The pending hold is voided, and the governance promise rejects. Partial stream content may have already been consumed by the caller.

The TigerBeetle Ledger

usertrust uses TigerBeetle for real double-entry accounting -- not a simple counter. Seven transfer codes model the full range of financial operations:

CodePurpose
PURCHASEInitial budget allocation (fund an account)
SPENDStandard governed LLM call cost
TRANSFERMove tokens between accounts
REFUNDReturn tokens after a voided or failed call
ALLOCATIONReserve tokens for a sub-budget or scope
TOOL_CALLCost attributed to an LLM tool/function call
A2A_DELEGATIONTokens delegated to an agent-to-agent call

Every transfer is a double-entry operation: one account is debited, another is credited. The ledger always balances.

Why This Matters

The two-phase lifecycle provides two guarantees that simple balance checks cannot:

  1. Atomic budget enforcement -- The PENDING hold reserves tokens before the LLM call starts. Even if multiple concurrent calls race against the same budget, TigerBeetle's transfer atomicity prevents over-spend.

  2. Failure-safe accounting -- Every failure path (provider error, ledger error, audit error, stream error) has a defined recovery. Tokens are never lost in limbo -- they are either settled or voided.

Without two-phase spend, a naive "check balance then call" approach is vulnerable to time-of-check-to-time-of-use (TOCTOU) races. Two concurrent calls could both pass the balance check and both succeed, spending more than the budget allows.