Once agents spend money — on inference, on tool calls, on paid tasks from other agents — you need an internal accounting mechanism. A credit wallet is the usual shape: an organization buys credits, agents draw against them, and the platform meters and reserves.
This is a financial system, and financial systems have well-known correctness requirements. Agent workloads stress them harder than typical application traffic because of concurrency: hundreds of parallel calls against a shared limit, retried by queue infrastructure that guarantees at-least-once delivery.
The bugs that result are not subtle in effect — double billing, negative balances, unfunded work — but they are subtle in origin, and they usually pass functional testing because functional testing is sequential.
Ledger, Not Balance
The tempting implementation is a balance_cents column, incremented on top-up and decremented on spend. Do not do this.
An append-only ledger of entries, with the balance derived by summation, gives you properties that matter:
Explainability. Every balance is the sum of specific entries, each with a reason, a timestamp, and a reference to what caused it. "Why is my balance $340" is answerable.
Auditability. Entries are never modified. A correction is a new compensating entry, not an edit. The history is complete.
Reconstructability. If aggregation logic is wrong, you fix the logic and recompute. If you mutated a balance field, the information needed to recompute is gone.
Reconciliation. You can compare the ledger against external sources — provider invoices, payment processor records — line by line.
Performance is the usual objection. Summing all entries on every read does not scale, and the standard answer is a periodic snapshot: a checkpoint entry recording the balance at a point in time, with reads summing from the last checkpoint forward. That keeps derivation cheap without giving up the ledger.
Entry types worth modeling distinctly: purchase, refund, reservation, reservation release, reservation capture, spend, adjustment, and transfer. Each with a reference to the originating operation.
Reservations Are Not Spend
An agent about to perform work with uncertain cost needs the funds set aside before the work starts. That is a reservation, and it is a third state alongside available and spent.
The transitions:
Reserve. Funds move from available to reserved. Available balance decreases; total balance does not. Requires an atomic check that available funds cover the reservation.
Capture. The work completed and the actual cost is known. The reservation converts to spend, with any excess released back to available. Actual cost typically differs from the estimate, so capture must handle both directions — and a capture exceeding the reservation needs an explicit policy: allow with an overage entry, or fail. Pick one and document it.
Release. The work was cancelled or failed. The full reservation returns to available.
Expire. The reservation was never captured or released within its window. A background process reclaims it. Without expiry, abandoned reservations permanently reduce available balance, which surfaces to users as an unexplained shortfall rather than as an obvious failure.
For inference specifically, reserve at the conservative worst case — input tokens counted exactly, output tokens at the configured maximum, at the model's rate — then capture actual usage. Conservative reservation with reconciliation fails in the safe direction: a blocked call rather than an overspend. See denial of wallet.
Atomicity Under Concurrency
This is where implementations break.
The naive sequence reads the balance, checks it against the amount, and writes a reservation. Under concurrency, a hundred parallel agent calls each read a balance of $10, each conclude $0.50 fits, and collectively reserve $50 against $10.
The fix is that the check and the write must be one atomic operation. Options, in rough order of preference:
A conditional write. A single statement that inserts the reservation only if the derived available balance covers it, returning whether it succeeded. One round trip, no lock held across application logic.
Row-level locking. Select the wallet row for update, compute, write, commit. Correct and simple; contention becomes the limit under heavy parallel load on one wallet, which is exactly the agent-fleet scenario.
Optimistic concurrency with a version column. Read with version, write conditional on the version being unchanged, retry on conflict. Works well when contention is moderate.
What does not work: an application-level check followed by a write in a separate transaction, a cached balance, or a distributed lock without fencing. This is the same time-of-check-to-time-of-use class that appears in hard spend cap enforcement, and the test for it is a load test that fires N concurrent reservations against a wallet funded for N-1 and asserts exactly one failure.
Idempotency
Queue delivery is at-least-once. Network calls time out and get retried. Clients retry on ambiguous responses. Every value-moving operation will eventually be invoked twice with the same intent.
Every mutating operation takes an idempotency key supplied by the caller, unique per logical operation.
The key is stored with the resulting entry under a uniqueness constraint, so a duplicate insert fails at the database rather than relying on an application check.
A repeated call returns the original result rather than performing the operation again or erroring. The caller cannot distinguish the first from the second, which is the point.
Keys are retained long enough to cover the retry horizon of every component that might retry — which for a queue with a long visibility timeout and a retry policy can be hours.
Without this, a retried capture bills twice and a retried top-up credits twice. Both are real incidents, and the top-up direction is worse because it is a revenue loss that nobody reports.
Scoping and Limits
A wallet at the organization level is where funds live. Limits need to exist at finer granularity, because one runaway agent should not drain the organization's balance.
Layered ceilings — per agent, per connection, per team, per organization, per time window — each enforced with the same atomicity as the wallet itself. An agent's reservation must satisfy every applicable ceiling, and the composite check must remain atomic; checking each limit in a separate transaction reintroduces the race.
Attribution on every entry: which agent, which connection, which principal, which task, which tenant. This is what makes showback possible, and it must come from the authenticated session rather than from caller-supplied parameters. See spend attribution and showback per agent and per-connection usage and cost tracking.
Reconciliation
An internal ledger that is never compared against external reality drifts, and the drift is silent.
Against provider invoices. Your metered inference total should match the provider's invoice within a small margin. A gap means either your metering is wrong or calls are happening outside your path — and the second is a security finding, because it means a provider key is being used somewhere you do not control. See securing LLM provider API keys.
Against the payment processor. Purchases in your ledger should match completed payments. Webhook delivery is unreliable; a reconciliation job that pulls processor state and compares is what catches missed credits. See reliable billing with Stripe webhooks and dunning.
Internal invariants. Available plus reserved plus spent equals total credited minus total refunded. Assert it on a schedule and alert on any deviation, because a deviation means a bug that is currently corrupting data.
Negative balance alarm. A negative available balance should page. It means a reservation was created without a successful check, which is the concurrency bug in production.
Common questions
Should credits map one-to-one to currency?
Not necessarily, and a separate unit has advantages: pricing can change without repricing existing balances, promotional credits are distinguishable from purchased ones, and per-task costs stay integers rather than sub-cent fractions. The cost is that customers must reason about a conversion, so publish the rate clearly and show currency equivalents. Whichever you choose, store integers — never floating point for money.
How do I handle refunds and disputes?
As compensating entries, never as edits or deletions. A refund is a new entry referencing the original, with a reason. This keeps the history intact and makes the net position derivable. For disputed marketplace tasks, the resolution produces the entries — release, partial capture, or full refund — with the resolution recorded. See agent task marketplaces and escrow.
What happens when an agent hits a limit mid-task?
Decide and document it, because both behaviors are defensible. Failing the current step is safer and can leave work half-done. Allowing the current step to complete while blocking further ones is gentler and permits a bounded overage. The important part is that the behavior is deliberate, that the event is attributable and surfaced to an operator, and that "silently continue" is not one of the options.
Do I need this if agents only spend on inference?
You need the reservation and atomicity properties, yes — those are what make a spend ceiling real rather than advisory. You may not need a purchasable credit balance if inference is billed to the organization through a subscription. The ledger is still worth building, because attribution and reconciliation depend on it and retrofitting them onto a mutable balance is painful.
How Praesidia approaches wallets and spend
Praesidia meters spend per governed call and enforces ceilings at agent, connection, team, and organization level, and those ceilings hold under concurrent load rather than only sequentially. Reservations are distinct from captured spend, with explicit release and expiry, and value-moving operations are safe against duplicate delivery.
Credit wallets provide escrow for marketplace tasks, entries carry attribution taken from the authenticated session, and every transition is recorded in the tamper-evident audit trail. Metered totals are exposed for reconciliation against provider invoices. See credits and cost monitoring for agent spend and the Praesidia documentation.