/ docsGitHub ↗
Browse documentation
EXPERIMENTAL · LOCAL-FIRST

Evaluate carefully. Live-provider paths are experimental and are not money-grade production infrastructure.

Spend lifecycle#

Hubu is the spend control plane. It evaluates policy, reserves an agent budget, issues scoped authorization, tracks executor claims, and records final financial state. Provider execution remains outside Hubu, while direct payment rails and the ledger remain owned by hubu-wallet.

The end-to-end lifecycle is:

request
  -> policy decision
  -> budget reservation
  -> authorization
  -> payment or external executor claim
  -> settlement or release
  -> ledger and audit state

Request identity and retry safety#

Every spend operation has a stable, agent-scoped operation_key supplied by a trusted client platform or orchestrator. Hubu stores the authorization record under that key. An identical retry recovers the existing operation. A trusted direct Hubu client may change scope under that key only when Hubu explicitly returns reuse_operation_key after an entirely denied, side-effect-free history. The unified MCP agent surface does not expose or reuse the private key after denial: corrected work is a new tool call and logical operation. All other retries that change the account, amount, reason, lease profile, task correlation, or execution scope are rejected. Key generation, storage, and revision rules are defined in operation key generation and storage.

task_id is optional trusted business correlation. reason is descriptive, model-visible audit context. Neither field owns a budget or replaces the operation key.

Amounts are exact decimals in a currency (usd or usdc), accounted in micro-units (10⁻⁶ of the currency). Budgets are per currency: spend only counts against a budget in its own currency. See Amounts in the Hubu HTTP API.

Trusted execution scope#

Execution-scope schema version 1 replaces the overloaded merchant convention with four independently addressable identities:

  • provider: the API or service that performs the work;
  • executor: the trusted execution-plane adapter allowed to claim the token;
  • capability: the requested outcome independent of tool or provider name;
  • billing_merchant: the party expected to charge the governed account.

Each identity has a stable ID and a display name. Hubu resolves all selectors against one trusted catalog entry and snapshots the canonical entry in the spend decision:

{
  "schema_version": 1,
  "provider": "provider:google:gemini-developer",
  "executor": "executor:gongbu:image",
  "capability": "capability:image:generate",
  "billing_merchant": "merchant:google"
}

Policies address these stable identities directly. Payment and executor validation exact-match the complete canonical scope; caller-supplied display names never grant authority.

Legacy requests that contain only merchant remain readable and normalize to a version-1 scope whose provider, executor, and capability are legacy:unresolved. New callers send execution_scope and omit merchant. Supplying both is rejected.

Policy decision#

The policy engine returns one of three effects:

  • allow: continue to budget reservation;
  • deny: stop without reserving or moving funds;
  • needs_approval: persist a pending decision and wait for explicit human resolution.

Human approval operates on the immutable review snapshot. Approval makes the same operation eligible for continuation but does not itself invoke payment or provider work; the unified MCP flow resumes it separately by public operation handle. Denial ends it. Repeating the same resolution is idempotent, while a conflicting resolution is rejected.

Budgets and spending targets#

Supported budget creation, total-limit updates, and revocation enter through BudgetManager. Its private coordinator commits storage before publishing private state; transports retain authentication, ownership checks, and DTO mapping. See the budget administration boundary for transaction and lock ordering and failure/retry guarantees.

An agent budget is a hard spending limit. A user spending target is advisory: it helps a human compare aggregate allocations with a preferred amount but does not block budget creation or spend.

An agent budget has one owner, a stable logical ID, an immutable currency and time window, and a current immutable version containing its total limit and change provenance. Budgets are per currency (usd or usdc; POST /budgets and hubu_create_budget accept currency, defaulting to usd): a spend only reserves from an active budget in its own currency, with no conversion, and an agent may hold one budget per currency for the same period. Amounts travel as exact decimal strings and exact money objects; see api-amounts.md. Executors see the authorization's currency and may assert it; a mismatching assertion is rejected. Consumed and frozen usage belong to the logical budget; remaining is checked against the current-version limit. Every hold records both the logical budget ID and the version that authorized it.

Every current-budget HTTP projection includes the stable public budget_id, the public current_version_id, and current_revision. Limit changes append a version through POST /budgets/{budget_id}/versions with amount_limit_cents, expected_revision, and optional reason; the amount is the new cumulative total cap, not an added allowance. The immutable agent, currency, period, consumed amount, and frozen amount do not reset. The append atomically advances the version pointer and recalculates remaining as the new limit minus consumed and frozen usage.

The expected revision pins one logical intent. An exact retry of the same revision, amount, actor, source, and reason returns its previously applied successor even if the current head later advances; the response therefore reports applied_version separately from the authoritative current_budget and marks idempotent_replay. A changed request against a stale predecessor is a conflict. GET /budgets/{budget_id}/versions returns the current logical snapshot once and immutable versions in ascending revision order; version rows contain provenance but do not repeat mutable balance or lifecycle state.

The update boundary reconciles expired holds at one captured request instant before reading or mutating the budget. It rejects invalid input, unknown or not-owned IDs, revision conflicts, revoked or expired heads, and limits below committed consumed-plus-frozen usage with typed public errors. Storage and invariant failures use one generic error without internal detail. The CLI always reads and prints the current snapshot before confirmation, pins that reviewed revision unless the caller supplies one, and retries an ambiguous POST once with the exact same body without refreshing or rebasing the intent.

SQLite persists only the budget's administrative state: active or revoked. Hubu derives availability from one captured instant and the current balance, using this precedence:

revoked -> scheduled -> expired -> exhausted -> active

Periods are half-open: a budget is scheduled before starting_at, is eligible at starting_at, and is expired at ending_before. A zero or negative remaining balance is exhausted only while the period is otherwise live. Default budget listing includes scheduled, active, and exhausted budgets; requesting all budgets also includes expired and revoked budgets.

Only an effectively active budget may accept a new reservation. Existing holds remain attributable and may settle, release, expire, or enter reconciliation after the budget becomes exhausted, expired, or revoked. Those finalization paths update hold and balance state without changing the administrative state. All non-revoked periods participate in overlap prevention for the same agent and currency, including exhausted periods; revocation is the explicit way to remove a period from that constraint.

After policy allows a request, Hubu atomically reserves the authorized maximum as a budget hold. A hold moves through:

frozen -> settled
       -> released

Settlement preserves exact external cost and converts it to budget micro-units with one checked ceiling operation, so a USD 0.001 charge consumes exactly 0.001 and only digits beyond the sixth decimal place round up. A normal settlement consumes that budget_charge and releases any unused maximum. Failure, unused authorization, expiry, or a confirmed non-billable outcome releases the hold. Ambiguous provider outcomes and confirmed costs above the authorized maximum enter reconciliation instead of being released optimistically. The exact conversion rules are in precise external cost and budget conversion.

An executor cannot settle above its maximum. After the claim lease expires, a human reviewing provider evidence may confirm a legitimate billed overrun; Hubu then consumes the full charge and lets the remaining balance become negative. That is retrospective accounting, not new spending authority, and the exhausted budget rejects subsequent reservations. See expired claims.

Authorization paths#

Hubu exposes two related paths:

  • Authorize-only evaluates policy, freezes the budget, and issues a spend authorization token. It does not call a provider, submit payment, or write a ledger transaction.
  • Submit-spend performs the same controls and then invokes Hubu's configured payment orchestration path.

An authorization token points to the immutable spend decision and expires. It can be claimed only by the executor and scope authorized by that decision. For unified-MCP human approvals, the explicit public-handle resume must happen before that lease expires. An expired pre-resume authorization is terminal for that operation and requires a new logical operation; it never starts provider work. Detailed external-executor rules are defined by the spend executor contract.

External execution#

Gongbu and other external executors resolve the authorization without claiming it, validate it against their own operator-controlled target and price, persist their execution, claim before any irreversible provider work, and then settle with exact cost and frozen pricing evidence, route an overrun or ambiguous outcome to reconciliation, or release after confirmed non-billing. The normative step-by-step protocol is the spend executor contract flow; Gongbu's implementation is described in Gongbu execution, and integrators can follow use Hubu with your own executor.

Provider credentials, provider retries, artifacts, and provider pricing remain owned by the executor. Sharing a Rust workspace does not turn this wire boundary into an in-process or shared-database call.

Payment and ledger path#

hubu-wallet::payment accepts a payment request, validates it against the spend authorization through SpendAuthorizationValidator, executes a selected PaymentRail, and returns a payment response. The validator boundary keeps wallet orchestration decoupled from the core spend manager and its storage.

The initial MockPaymentRail supports synchronous fiat and stablecoin mock results. A real rail may add pending states, webhooks, settlement identifiers, on-chain transaction hashes, and reconciliation metadata without changing the control-plane ownership boundary.

Successful money movement writes one immutable, balanced, double-entry ledger transaction. The initial accounting shape is:

debit  AgentSpendExpense
credit UserWalletCash

SQLite stores accounts, transactions, and entries. Triggers reject updates and deletes for transactions and entries, and Rust validates balance before insert. Failed payment attempts are persisted but do not create successful ledger movement.

Failure and reconciliation invariants#

The executor-facing rules are normative in the contract's safety rules. At the lifecycle level:

  • Never execute provider work before a durable claim.
  • Never accept a normal executor settlement above the authorized maximum, and never discard exact cost or pricing evidence when a legitimate charge exceeds it; keep the hold claimed for human reconciliation.
  • Never convert external cost through floating point or round budget consumption down.
  • Never release a hold merely because a timeout made billing ambiguous.
  • Never retry an ambiguous provider mutation under a new operation key.
  • Keep Hubu and executor databases, credentials, artifacts, and failure domains separate.
  • Preserve enough immutable evidence to reconcile billed and non-billed outcomes without trusting caller assertions.

Legacy v4.3 cents receipts migrate to exact amount with scale 2. Hubu and Gongbu upgrade their own databases independently and preserve receipt, operation, execution, settlement, and provider-request identities. The detailed mapping, retry behavior, and rollback requirements are defined by the spend executor contract.

Core spend and budget behavior lives in crates/hubu-core, while payment behavior lives in crates/hubu-wallet.

Confirmed external-provider spends also create a balanced non-cash ledger posting in the same Hubu transaction as receipt and budget settlement. See ledger accounting for posting semantics, corrections, legacy coverage and the distinction from wallet cash accounting.

The first-class hubu-ledger domain owns canonical wallet and provider transactions and exact entries. Governed context links those transactions to agent budgets; budgets do not own the ledger.

Edit this page on GitHub ↗Canonical source: docs/spend-lifecycle.md