/ docsGitHub ↗
Browse documentation
EXPERIMENTAL · LOCAL-FIRST

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

Spend Executor Contract#

This v4 contract maps one authorization to one potentially billable executor call. Multi-call tasks use multiple independent v4 operations.

This contract lets an external work service use Hubu for spend control without turning Hubu into the service that performs the work:

Hubu controls spend; executors do work.

Gongbu can implement this contract for model calls, image generation, or other vendor-backed work. Hubu does not receive vendor credentials, provider payloads, prompts, or artifact contents. It does retain exact cost, the complete frozen pricing snapshot, and compact provider and artifact references needed for settlement auditability.

Protocol Version#

The current version is hubu-spend-executor-v4.5. Agents and executors can discover its machine-readable guidance from either public route:

GET /spend/executor/guidance
GET /.well-known/hubu-spend-executor.json

V4.5 moves executor amounts from whole cents to exact amounts with an explicit scale; see v4.5 exact amounts. Hubu keeps accepting v4.4 executor requests during the transition, and the guidance lists both in supported_protocol_versions. Every binary in the installation still reports and requires the same contract version.

V4.4 identifies executor claims by the spend_auth_token_id and finalization by the claim_id Hubu issued; see v4.4 executor request identity. It is intentionally startup-incompatible with v4.3, and a v4.3 executor must migrate its request shapes.

The precise-cost fields were added within hubu-spend-executor-v4.3 and remain in v4.4. They add a precise integer external-cost object, preserves the complete frozen pricing snapshot, and separates exact provider cost from the conservatively rounded number of cents charged to a Hubu budget. Normal and human-reconciled billed settlement use the same validation and rounding. Callers may still send the cents-only receipt field described below while they migrate to the precise object.

V4.3 renamed Hubu's workload_profile field to lease_profile, made the authorization TTL global, and limited each lease profile to its claim TTL. Gongbu workload types remain independent execution-plane identities and no longer have to equal a Hubu lease profile. The breaking payload and guidance shape makes v4.3 intentionally startup-incompatible with v4.2.

V4.2 introduced the read-only POST /spend/executor/resolve capability used for token-only executor admission.

This page is the single normative description of the executor protocol (authorize, resolve, claim, then settle or release). The spend lifecycle gives the concept overview, and Gongbu execution describes how the first-party executor implements it. To run your own executor behind MCP authorization, follow Use Hubu with your own executor.

Executors can check their lifecycle handling against the black-box executor conformance suite, which replays a versioned v4.5 fixture corpus, and the legacy v4.4 corpus, against a real hubu-server.

The unified MCP continuation binding remains independent of the v4.3 cost fields. It makes the existing auth_token_id / spend_auth_token_id the agent-visible continuation identifier for one private normalized operation. The private operation_key stays with the platform, the unified MCP router and Hubu. No executor ever receives it, Gongbu included: executor routes neither accept nor return it. Gongbu identifies an execution by Hubu's decision_id (one decision, one authorization, at most one provider call). That identity also seeds the vendor idempotency key. Gongbu claims and finalizes by the continuation token.

V4 retains V3's immutable, platform-provided operation_key from authorization through claim and finalization. Hubu stores authorization record state under (agent_id, operation_key). Retrying with the same key and scope returns that same authorization record. Two agents owned by the same user may use the same operation key; one agent may not reuse an operation key for different work. V2's separate executor_execution_id is no longer part of the public contract. V4 adds durable provider receipts and actual-cost settlement to V3's exclusive claims, owner-scoped lookup, and human-gated reconciliation. Settlement consumes the conservative budget charge derived from exact vendor cost and releases the remainder of the authorized maximum. POST /spend/executor/validate remains available for scope inspection, but validation alone does not authorize irreversible work.

Admission rejection before claim#

When Gongbu definitively rejects the initial execution admission with a permanent invalid_request, no claim exists. The trusted unified MCP router cancels the unclaimed reservation through a separate backend operation:

POST /spend/void
Authorization: Bearer <Hubu backend bearer>
Content-Type: application/json

{
  "spend_auth_token_id": "<bound authorization token>",
  "account_id": "<bound agent account>",
  "operation_key": "<router's private operation key>"
}

This route requires the authenticated owner, account, token and private operation key to match the same authorization. The router retains the random private key; it is never accepted as a model-supplied argument or exposed in a public operation result. There is no agent-facing void MCP tool. Gongbu keeps using the token-only executor routes and does not receive the private key.

Hubu atomically revokes an unused, unrevoked, unclaimed token and returns the entire frozen hold to its budget in one SQLite write transaction. Claim creation checks persisted token and hold state under the same write lock. If void wins, claim refuses the revoked token. If claim wins, void returns HTTP 409 with error_code: authorization_not_voidable and leaves the claimed hold unchanged. The same conflict also applies to already used or finalized authorizations. Repeating a completed void returns HTTP 200 with idempotent_replay: true and does not credit the budget twice. No provider charge, receipt or ledger posting is created.

Authorization history and hubu watch show status released and release_reason: executor_rejected_before_claim. The router durably records the original admission failure and pending cleanup together; it retries a lost void response after restart without redispatching Gongbu or re-authorizing the operation. Replaying the original call retains the same failed result. A corrected request needs a new call identity.

Timeouts, malformed replies, retry exhaustion, observation errors and other ambiguous outcomes keep the existing expiry and observation paths. A definitive rejection after an earlier ambiguous dispatch also keeps its hold. The router never infers that no claim exists from a missing execution ID alone. Ordinary executor release remains identified exclusively by claim_id.

V4.5 Exact Amounts#

Hubu accounts every budget, hold and settlement in micro-units: 10^-6 of the authorization's currency, usd or usdc. Budgets are per currency, so an authorization only ever reserves a budget in its own currency. See Amounts in the Hubu HTTP API.

A v4.5 executor names its contract and sends the exact authorized amount on POST /spend/executor/validate and POST /spend/executor/claim:

{
  "spend_auth_token_id": "00000000-0000-4000-8000-000000000123",
  "account_id": "aga_example",
  "contract_version": "hubu-spend-executor-v4.5",
  "amount": {"units": "5000", "scale": 6, "currency": "usd"}
}
  • units is an integer string of 10^-scale of currency. scale must be 6. Copy the amount that POST /spend/executor/resolve returned; its informational display member may be echoed and is ignored.
  • Hubu rejects a missing or different scale, an unknown contract_version, and a request that sends amount_cents as well as amount.
  • The amount and currency must equal the authorization exactly. A mismatch is rejected with the existing payment request does not match authorized spend error, so the sub-cent authorization above cannot be claimed as 5001 units or as usdc.

A request without contract_version is a v4.4 request. It sends amount_cents, the authorization rounded up to a whole cent, and Hubu checks that projection against the exact authorization. A v4.4 executor therefore keeps working against a v4.5 Hubu, but it cannot tell a 0.005 authorization from a 0.01 one. Gongbu speaks v4.5: it admits only an authorization equal to its exact price (or that price rounded up to a cent) and claims with the exact amount, so a provider can never charge more than a sub-cent reservation.

Executor responses carry both forms for every contract version: amount is the exact money object {units, scale, currency, display} and amount_cents is the conservative cent projection. Settlement receipts already carry the exact provider cost with its own scale (see below).

V4.4 Executor Request Identity#

An executor identifies its operation only by values Hubu gave it: it claims with the authorization continuation (spend_auth_token_id), and it settles or releases by the claim_id that claim returns. It never sends operation_key or agent_id on executor routes, and it does not need to read them from resolve.

Route Identity Rejected
POST /spend/executor/claim spend_auth_token_id, plus the account, amount and scope assertions operation_key, agent_id
POST /spend/executor/settle claim_id, plus receipt spend_auth_token_id, operation_key, agent_id
POST /spend/executor/release claim_id spend_auth_token_id, operation_key, agent_id, receipt
{"claim_id":"00000000-0000-4000-8000-000000000456","receipt":{"…":"…"}}

Rules:

  • Claim: Hubu derives the operation key, agent, task and reason from the stored decision. The account, amount and scope assertions remain, so the executor still proves it is claiming exactly the work it priced.
  • Settle and release: identified by claim_id only. An executor that lost the claim response replays the identical claim, using the same token, to recover it. A claim replay returns the existing claim, including its terminal state, even after the authorization expires.
    • Owner checks apply: another owner's claim is reported as unknown.
    • Sending spend_auth_token_id is rejected with that recovery guidance.
    • The retired v4.3 agent_id + operation_key identity is rejected, alone or mixed with claim_id.
  • Replay: an identical settlement or release returns the stored result. A changed receipt is rejected. Budget, holds and ledger postings are never applied twice.
  • Human reconciliation: unchanged. It uses claim_id, provider_reference and evidence with the separate capability. Adding executor identity fields to that shape is rejected.
  • Responses: no /spend/executor/* response carries operation_key. That covers resolve, validate, claim, settle, release, claim inspection and the reconciliation queue. Executors receive the token, claim_id, decision_id (the public authorization record ID) and the public agent_id/account_id. Owners who hold the private key can still look an authorization record up through GET /spend/authorizations/show.

Migrating a v4.3 executor#

  1. Persist the spend_auth_token_id before claiming, as v4.3 already requires.
  2. Stop sending operation_key on claim.
  3. Persist the claim_id from the claim response. If that response was lost, replay the identical claim to recover it.
  4. Settle or release with {"claim_id": …} instead of agent_id + operation_key.

Multiple Invocations In One Agent Task#

The agent platform orchestrates tasks that need more than one model or provider invocation. It creates a separate v4 spend operation for every call that may produce a distinct vendor charge:

agent task
├── operation A: authorize -> claim -> invoke -> settle or release
├── operation B: authorize -> claim -> invoke -> settle or release
└── operation C: authorize -> claim -> invoke -> settle or release

Each operation has a unique, durable operation_key and an immutable invocation scope consisting of its merchant, maximum amount, lease profile, and operation purpose. An HTTP retry of the same provider invocation reuses that operation. A retry or alternate model call that may create another charge uses a new operation key and requires a new authorization.

The platform may retain a shared task identifier to correlate operations, artifacts, and presentation. Hubu does not treat that correlation identifier as a pooled allocation: v4 enforces each operation maximum and the authoritative agent budget, not a separate task-level aggregate ceiling.

One authorization covering several charges under a shared maximum is outside v4 and requires a new protocol version.

Precise costs also remain operation-scoped. Two provider settlements keep two independent exact receipts, frozen pricing snapshots, ceiling conversions, holds, and idempotency identities. Hubu never combines sub-cent amounts across operations to change either operation's conservative budget charge.

Boundary#

Hubu is responsible for:

  • agent and owner identity
  • policy evaluation and spend authorization tokens
  • authoritative authorization record state keyed by agent and operation
  • one agent-budget hold per spend decision
  • exclusive executor claims and claim leases
  • exact-cost validation, conservative budget settlement, and release of unused authorization
  • durable provider request, complete frozen price/model snapshot, and artifact references
  • audit events for spend state transitions
  • durable provider references and evidence for human reconciliation decisions

Inside Hubu, hubu-core::app::ExecutorClaimService owns claim creation, owner-scoped lookup, reconciliation queue selection, executor settlement or release, and human reconciliation orchestration. The HTTP API remains the transport and authentication boundary, while the SQLite repository atomically commits receipt, claim, token, hold, and budget-balance transitions.

Agent platforms or orchestrators are responsible for:

  • supplying one stable, namespaced operation_key for each logical operation
  • reusing that key for every authorization retry
  • keeping operation identity outside the language model's conversational memory

Executors are responsible for:

  • storing vendor API keys and other execution secrets outside Hubu
  • persisting the spend_auth_token_id before claiming, claiming by it, and settling or releasing by the returned claim_id (replaying the identical claim recovers a lost claim_id); executors never send the private operation_key
  • claiming Hubu authorization before irreversible work
  • calling vendors or tools
  • reporting the exact vendor cost with a provider request ID, complete frozen price/model snapshot, and artifact reference
  • settling after billable work or releasing before billable work

Precise External Cost and Budget Conversion#

V4.3 represents an external vendor cost as an integer coefficient, a decimal scale, and an ISO currency code. The value is amount × 10^-scale currency units:

{
  "amount": 4000001,
  "scale": 6,
  "currency": "usd"
}

This example is exactly USD 4.000001. amount is a non-negative JSON integer that must fit Hubu's signed 64-bit checked integer representation; scale is an integer from 0 through 18. The currency is usd or usdc and must equal the hold's currency. Input accepts the code case-insensitively, and Hubu canonicalizes it to lowercase in persisted and response representations. Parsing, comparison, multiplication by powers of ten, and conversion must use checked integer arithmetic. Floating point is never permitted.

Hubu budgets account in micro-units (scale 6). For an exact non-negative cost in the authorized currency, Hubu computes budget_charge by rounding toward positive infinity exactly once:

scale > 6: ceil(amount / 10^(scale - 6))
scale = 6: amount
scale < 6: amount × 10^(6 - scale)

Every exponentiation, multiplication, addition, and division is checked for overflow. Costs at scale 6 or coarser are charged exactly; only digits beyond the sixth decimal round up, so settlement never understates governed consumption. budget_charge_cents is the legacy projection of that charge, rounded up to a whole cent. Hubu does not round individual pricing components and then add them; Gongbu preserves and evaluates its exact frozen pricing snapshot, and Hubu converts the final exact vendor cost once.

For USD, the boundary behavior is deterministic:

Exact actual_vendor_cost budget_charge budget_charge_cents Result against a 5.00 maximum
{amount: 0, scale: 6} 0.00 0 accepted
{amount: 1, scale: 6} 0.000001 1 accepted; exact micro-unit
{amount: 1, scale: 7} 0.000001 1 accepted; rounded up to a micro-unit
{amount: 10000, scale: 6} 0.01 1 accepted; exact one cent
{amount: 10001, scale: 6} 0.010001 2 accepted; just above one cent
{amount: 5000000, scale: 6} 5.00 500 accepted at the exact maximum
{amount: 5000001, scale: 6} 5.000001 501 normal settlement rejected; reconcile

The receipt stores the exact actual_vendor_cost amount and original scale, plus the canonical USD code and complete price_model_snapshot supplied by Gongbu. Hubu preserves the snapshot as a complete semantic JSON value; it does not promise to retain source whitespace or object-key order, and it does not project, truncate, or recompute the fields. The snapshot is evidence, not permission to change price after authorization. Its currency must agree with the exact cost and authorization, and an identical retry must supply the same JSON value. A numerically equal cost encoded with a different amount or scale is changed receipt data and conflicts after settlement.

For v4.3 input compatibility, a receipt may instead supply actual_vendor_cost_cents. Hubu maps that non-negative integer to actual_vendor_cost = {amount: actual_vendor_cost_cents, scale: 2, currency: usd} before validation and persistence. The frozen snapshot and authorization must also identify USD. A request must not supply both forms. New callers use actual_vendor_cost; v4.3 responses return the precise form and explicit budget accounting fields. They also retain actual_vendor_cost_cents as the legacy compatibility projection of budget_charge_cents; it is not a replacement for the exact cost object.

Lease Profiles#

The authorization request selects a lease_profile. Hubu snapshots that profile into the spend decision, so the executor cannot exchange a short job for a longer claim lease. The global authorization TTL controls how quickly any authorized operation must start; the selected profile controls how long the claimed execution may run.

Operators configure one authorization start window and a bounded set of lease profiles with HUBU_LEASE_CONFIG pointing to a YAML file:

authorization_ttl_seconds: 300
default_lease_profile: interactive
lease_profiles:
  interactive:
    claim_ttl_seconds: 900
  long_running:
    claim_ttl_seconds: 3600
  batch:
    claim_ttl_seconds: 14400

Operation Key Generation and Storage#

The agent platform or orchestrator—not the language model—owns the operation key lifecycle. It should use a durable platform operation ID with a namespace, for example codex:tool-call:01J.... If the platform has no suitable ID, its adapter should generate and persist an opaque key before the first attempt. Operation keys are compared case-sensitively after trimming whitespace.

Hubu is the authoritative store for authorization record state under the agent-scoped key. Replaying authorization with the same agent, operation key, and scope recovers the decision, token, hold, and current authorization record state. Claim and finalization use that same key.

Hubu journals immutable, monotonically numbered authorization attempts beneath the stable key. A changed scope is admitted only when every prior attempt has an explicit terminal denial and no token, pending approval, hold, claim, dispatch, payment, or settlement side effect exists. Admission uses an immediate SQLite transaction, so process-local locking is not the concurrency authority. Exact scope replay can recover any historical attempt, including a denial that precedes a later allowed attempt.

Do not derive the operation key from mutable request fields, generate it inside a retry loop, or ask the model to invent or remember it. A reusable skill can teach the protocol, but enforcement belongs in the platform adapter or SDK and Hubu's database constraints.

The unified Hubu MCP transport derives this key from supported trusted harness call metadata, persists the normalized identity in its own SQLite registry, and rejects identity collisions before backend access. Direct HTTP and diagnostic CLI clients still supply an explicit key; this contract defines the server-side invariant every adapter must satisfy.

Hubu rejects unknown profiles and non-positive durations during startup or authorization. The effective lease configuration is published in executor guidance.

Flow#

  1. An agent asks Hubu to authorize a scoped executor action:

    POST /spend/authorize
    
    {
      "operation_key": "codex:tool-call:01JABC123",
      "account_id": "aga_example",
      "amount": "5.00",
      "currency": "usd",
      "task_id": "release:artwork",
      "execution_scope": {
        "schema_version": 1,
        "provider": "provider:google:gemini-developer",
        "executor": "executor:gongbu:image",
        "capability": "capability:image:generate",
        "billing_merchant": "merchant:google"
      },
      "reason": "hubu-logo-demo",
      "lease_profile": "long_running"
    }
    

    Hubu returns the original operation_key, auth_token_id, decision, and frozen agent-budget hold. Retrying the same operation key and scope returns those same records. After a side-effect-free terminal denial, a corrected scope may append a new revision under the same key. Once any revision is pending approval, allowed, or side-effect-capable, changed scope is rejected. Another agent may independently use the same operation key.

    Every response includes revision, idempotent_replay, attempt_history, and retry_guidance. Retry guidance has one machine-readable action: reuse_operation_key, replay_exactly, or create_new_operation.

    When policy returns needs_approval, the response also includes a durable approval object:

    {
      "decision": "needs_approval",
      "auth_token_id": null,
      "approval": {
        "approval_request_id": "<spend decision id>",
        "status": "pending",
        "review": {
          "operation_key": "codex:tool-call:01JABC123",
          "account_id": "aga_example",
          "agent_id": "agt_example",
          "amount_cents": 500,
          "currency": "usd",
          "lease_profile": "long_running",
          "reason": "hubu-logo-demo",
          "policy_summary": "policy defaulted to needs_approval because no automatic-allow rule matched"
        }
      }
    }
    

    No token, hold, claim, payment, or provider work exists while approval is pending. The client shows the complete approval.review object to the human and waits for an explicit decision. It may recover the durable state with:

    GET /spend/approval?approval_request_id=<spend decision id>
    

    The owner-authenticated client then submits exactly one decision:

    POST /spend/approval/resolve
    X-Hubu-Approval-Capability: <owner-only capability>
    
    {
      "approval_request_id": "<spend decision id>",
      "decision": "approve"
    }
    

    The normal API bearer is insufficient for this route; the server also verifies the owner-only approval capability, which must not be shared with executors. decision is approve or deny. Approval reserves the original immutable maximum and returns the normal authorization token; it never invokes the provider. Denial is terminal for that immutable request. Repeating the same resolution or exact authorization request returns the stored result, while a conflicting resolution is rejected. The unified MCP router synchronizes a decision made through either MCP or the CLI into its public-handle status; approved governed work starts only when hubu_resume_operation separately resumes the stored immutable intent.

    operation_key, task_id, and reason are independent. The first is the trusted financial/idempotency identity, the second is an optional trusted external correlation, and the third is human-readable authorization and audit context. For compatibility, an absent task_id maps reason into the stored task ID; explicit null means no task correlation.

    {
      "operation_key": "codex:tool-call:01JABC123",
      "decision": "allow",
      "auth_token_id": "00000000-0000-4000-8000-000000000123"
    }
    
  2. The agent sends only spend_auth_token_id plus execution intent and target selection to canonical POST /v2/executions with schema_version: 2. New callers select one opaque target_id from GET /v2/execution-targets. Provider, adapter, model, and workload routing fields are not accepted from the caller. The request does not repeat account, operation key, money, typed scope, task ID, reason, lease profile, or expiry.

    The v1 execution-creation route is retired. Existing v1 GET observation routes remain available.

    On the unified MCP surface, the router first requires the continuation ID to match exactly one allowed normalized operation and binds the first canonical execution intent to it. It rejects changed intent or any model-authored operation identity, endpoint, credential, retry control, trusted task_id, or protected lifecycle state before the Gongbu request. The public operation handle remains visible for correlation but is not authority and is not an execution-creation input.

    Target discovery is read-only and installation-authenticated. Its compact execution-scope selector is safe to copy into Hubu authorization, while its exact price components and supported image-size selectors let the agent form a matching amount and runtime request. Discovery omits credentials, endpoints, headers, adapter settings, and configuration revisions.

  3. Before persistence, the executor performs a read-only resolution:

    POST /spend/executor/resolve
    
    {"spend_auth_token_id":"00000000-0000-4000-8000-000000000123"}
    

    Hubu returns the authoritative authorization snapshot without claiming it. For a new execution, its account and agent are the authoritative attribution; the executor caller credential contributes no execution identity. The executor independently derives its operator-controlled target, workload, typed scope, and catalog price. Before persisting that attribution snapshot, it requires the authorized typed scope to equal its derived scope and the authorized amount and currency to equal the catalog price. The executor never receives the private operation key; Gongbu's request schema rejects an operation_key field rather than comparing it.

    Before this resolution, the executor checks for a persisted execution by the same token. An exact immutable request replays locally even after the token was claimed or settled, without resolving Hubu again. A changed request conflicts. The persisted execution agent is used for claim settlement or release.

    Gongbu's HTTP response reports the spend_auth_token_id the execution consumed. The unified MCP router verifies it against the continuation it bound to the normalized operation, then persists Gongbu's execution ID and lifecycle state. Gongbu never learns the private operation key. As defence in depth, the router still removes any operation_key field or hubu:operation:v1: value from agent-facing content. A returned execution or continuation conflict fails closed. Exact replay can only recover the same Gongbu execution; payload-similarity inference and ambiguous provider retry remain out of scope.

  4. After persistence and before irreversible work, the durable executor claims the authorization:

    POST /spend/executor/claim
    
    {
      "spend_auth_token_id": "00000000-0000-4000-8000-000000000123",
      "account_id": "aga_example",
      "contract_version": "hubu-spend-executor-v4.5",
      "amount": {"units": "5000000", "scale": 6, "currency": "usd"},
      "execution_scope": {
        "schema_version": 1,
        "provider": {"id":"provider:google:gemini-developer","display_name":"Google Gemini Developer API"},
        "executor": {"id":"executor:gongbu:image","display_name":"Gongbu image executor"},
        "capability": {"id":"capability:image:generate","display_name":"Generate image"},
        "billing_merchant": {"id":"merchant:google","display_name":"Google"}
      }
    }
    

    Hubu accepts only if the token is unexpired, unused, unrevoked, unclaimed, matches the authorized operation and scope, and has a frozen agent-budget hold. Hubu resolves task ID and reason from the stored authorization rather than trusting executor input. A legacy executor may send task_id as a compatibility assertion, but a mismatch is rejected. A retry with the same token returns the existing claim, including its terminal state if it has already been settled or released. Hubu derives the operation key from the stored decision; sending operation_key or agent_id is rejected.

    The version-1 typed scope and legacy migration behavior are specified in trusted execution scope. New callers omit merchant.

    Claiming moves the hold to claimed and extends its expiry to claim_expires_at. The claim may remain active after the original authorization expires.

  5. The executor performs work using its own credentials.

  6. After irreversible billable work succeeds, the executor settles:

    POST /spend/executor/settle
    
    {
      "claim_id": "00000000-0000-4000-8000-000000000456",
      "receipt": {
        "actual_vendor_cost": {
          "amount": 4000001,
          "scale": 6,
          "currency": "usd"
        },
        "provider_request_id": "provider-request-abc123",
        "price_model_snapshot": {
          "schema_version": 2,
          "provider": "example-image-provider",
          "model": "image-model-v1",
          "catalog_version": "prices-2026-08-28",
          "catalog_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "pricing_rule_id": "image-model-v1",
          "components": [{
            "unit": "image",
            "rate_numerator_minor": 4000001,
            "rate_denominator": 10000,
            "quantity": 1
          }],
          "exact_estimate_numerator": "4000001",
          "exact_estimate_denominator": "10000",
          "estimated_amount_minor": 401,
          "currency": "USD"
        },
        "artifact_reference": "artifact://hubu-logo.png"
      }
    }
    

    Hubu persists the exact USD 4.000001 receipt and complete pricing snapshot, marks the claim settled and token used, consumes exactly 4.000001 (projected as 401 legacy cents), and releases the 0.999999 remainder in one SQLite write transaction. A normal executor settlement is accepted only when its checked budget_charge does not exceed the 5.00 authorized maximum. Hubu resolves the claim from the claim_id, so an identical retry returns the original settlement_id and receipt even if the caller lost the response. A retry with any changed exact-cost, currency, scale, snapshot, provider, or artifact data is rejected, and budget is not consumed twice.

    If a confirmed provider charge converts to more than the authorized maximum, Hubu rejects normal settlement without consuming or releasing the hold. Gongbu keeps the immutable exact provider-attempt cost, frozen pricing snapshot, provider reference, and attempt evidence, marks the execution reconciliation_required, and never repeats provider work merely because settlement was rejected. After the claim lease expires, a human can reconcile the legitimate billed overrun through the separately authorized path below.

  7. If no irreversible billable work occurred, the executor releases without a receipt:

    POST /spend/executor/release
    
    {
      "claim_id": "00000000-0000-4000-8000-000000000456"
    }
    

    Hubu atomically marks the claim released and token revoked while returning the held amount to the remaining budget. An identical release retry returns the stored terminal state without returning the budget twice.

Claim Response#

{
  "claim_id": "uuid",
  "lease_profile": "long_running",
  "status": "claimed",
  "claimed_at": "2026-07-20T12:04:00Z",
  "claim_expires_at": "2026-07-20T13:04:00Z",
  "finalized_at": null,
  "settlement_id": null,
  "reconciliation_required": false,
  "reconciliation_outcome": null,
  "provider_reference": null,
  "evidence": null,
  "reconciled_at": null,
  "reconciled_by_user_id": null,
  "spend": {
    "spend_auth_token_id": "uuid",
    "decision_id": "uuid",
    "account_id": "aga_...",
    "agent_id": "agt_...",
    "amount": {"units": "5000000", "scale": 6, "currency": "usd", "display": "5.00"},
    "amount_cents": 500,
    "currency": "usd",
    "merchant": null,
    "execution_scope": {
      "schema_version": 1,
      "provider": {"id":"provider:google:gemini-developer","display_name":"Google Gemini Developer API"},
      "executor": {"id":"executor:gongbu:image","display_name":"Gongbu image executor"},
      "capability": {"id":"capability:image:generate","display_name":"Generate image"},
      "billing_merchant": {"id":"merchant:google","display_name":"Google"}
    },
    "task_id": "hubu-logo-demo",
    "reason": "Generate the Project Hubu logo",
    "lease_profile": "long_running",
    "status": "claimed",
    "expires_at": "2026-07-20T12:05:00Z",
    "budget_hold": {
      "hold_id": "uuid",
      "budget_id": "bgt_...",
      "status": "claimed",
      "amount_cents": 500,
      "consumed_amount_cents": 0,
      "frozen_amount_cents": 500,
      "remaining_amount_cents": 0
    }
  }
}

spend.expires_at is the original authorization deadline; claim_expires_at is the separate execution lease.

The authenticated owner can inspect any claim, including terminal claims:

GET /spend/executor/claim?claim_id=CLAIM_ID

The response includes reconciliation_required, terminal settlement details, and any stored reconciliation outcome, provider reference, evidence, resolving user, and timestamp.

Settlement Response#

{
  "settlement_id": "uuid",
  "claim_id": "uuid",
  "status": "settled",
  "receipt": {
    "authorized_max_cents": 500,
    "actual_vendor_cost": {
      "amount": 4000001,
      "scale": 6,
      "currency": "usd"
    },
    "authorized_max": {"units": "5000000", "scale": 6, "currency": "usd", "display": "5.00"},
    "budget_charge": {"units": "4000001", "scale": 6, "currency": "usd", "display": "4.000001"},
    "released_amount": {"units": "999999", "scale": 6, "currency": "usd", "display": "0.999999"},
    "overrun_amount": {"units": "0", "scale": 6, "currency": "usd", "display": "0.00"},
    "actual_vendor_cost_cents": 401,
    "budget_charge_cents": 401,
    "released_amount_cents": 99,
    "overrun_amount_cents": 0,
    "currency": "usd",
    "provider_request_id": "provider-request-abc123",
    "price_model_snapshot": {
      "schema_version": 2,
      "provider": "example-image-provider",
      "model": "image-model-v1",
      "catalog_version": "prices-2026-08-28",
      "catalog_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "pricing_rule_id": "image-model-v1",
      "components": [{
        "unit": "image",
        "rate_numerator_minor": 4000001,
        "rate_denominator": 10000,
        "quantity": 1
      }],
      "exact_estimate_numerator": "4000001",
      "exact_estimate_denominator": "10000",
      "estimated_amount_minor": 401,
      "currency": "USD"
    },
    "artifact_reference": "artifact://hubu-logo.png",
    "created_at": "2026-07-20T12:05:00Z"
  }
}

Transactional Finalization#

Hubu captures one settlement_started_at value after parsing and authenticating the request. Settle and release each obtain an immediate SQLite write lock, check the claim against that timestamp, and atomically update:

  • the executor claim to settled with a stable settlement_id, or released
  • the spend authorization token to used, or revoked
  • the claimed budget hold to settled, or released
  • the immutable exact provider receipt and complete frozen pricing snapshot for settlement
  • the checked conservative budget_charge_cents, released remainder, and any human-confirmed overrun
  • the budget balance from frozen to conservative cost consumed plus the unused remainder returned, or the full hold returned for release

This removes the internal race where token and claim state could commit before the hold and balance. It also serializes settle against release so the first terminal transaction wins. If any update fails, SQLite rolls all five changes back. A claim that was active when the transaction began can complete even if wall-clock time passes its lease during the transaction; a claim at or past expiry when the transaction begins is rejected for reconciliation.

Persistence Migration and V4 Compatibility#

The precise receipt extension does not merge Hubu and Gongbu persistence. Each process upgrades only its own SQLite database during repository open and within its own failure domain:

  • Hubu maps every legacy actual_vendor_cost_cents receipt to exact {amount: legacy_cents, scale: 2, currency: stored_currency}. Its historical consumed amount becomes budget_charge_cents, its released amount is preserved, overrun_amount_cents is zero, and the stored price/model snapshot JSON remains unchanged.
  • Gongbu maps legacy provider-attempt and receipt minor-unit amounts to exact {amount: legacy_minor, scale: 2, currency: stored_currency}. For a legacy receipt it persists the exact old retry evidence: provider_request_id is the receipt ID and price_model_snapshot is the reduced v4.3 projection reconstructed from the unchanged frozen execution snapshot. New receipts persist the provider-reported reference and complete snapshot. Execution, attempt, receipt, settlement, catalog-version, and frozen execution-snapshot identity remain stable.

The migrations are idempotent: reopening an already upgraded database neither rewrites a precise receipt nor changes retry identity. Existing settlement IDs, claim IDs, Hubu operation keys, Gongbu execution IDs, and provider request IDs remain stable. A migrated receipt therefore compares equal to the canonical legacy request mapping and replays without another budget debit or provider call.

The cents-only v4.3 request remains accepted by an upgraded Hubu, but new clients should send the precise object. If a precise-capable Gongbu reaches an older v4.3 Hubu that rejects the additive object, it must keep its exact receipt and frozen snapshot and enter reconciliation; it must not retry the provider or downgrade away the exact evidence. Upgrade the unified four-binary installation before resolving that charge. Operators should take independent cold backups of Hubu and Gongbu state before upgrade. Rolling back to binaries that know only the old schema requires restoring the corresponding pre-upgrade backup rather than pointing an old binary at an upgraded database.

Expired Claims#

Hubu does not automatically release a claimed hold when its lease expires. The vendor may have completed work while the executor failed before settlement, so automatic release could make billed work disappear from governed consumption. An expired claim remains frozen and normal settle/release requests reject it as requiring reconciliation. The authenticated owner can list only their expired, still-claimed work:

GET /spend/executor/reconciliation

After checking provider billing, a human chooses one terminal outcome. If the vendor billed, the human settles the hold:

POST /spend/executor/settle
X-Hubu-Reconciliation-Capability: HUMAN_CAPABILITY
{
  "claim_id": "00000000-0000-4000-8000-000000000456",
  "provider_reference": "vendor-request-abc123",
  "evidence": "Provider usage export confirms the completed billed request.",
  "receipt": {
    "actual_vendor_cost": {
      "amount": 4000001,
      "scale": 6,
      "currency": "usd"
    },
    "provider_request_id": "vendor-request-abc123",
    "price_model_snapshot": {
      "schema_version": 2,
      "provider": "example-image-provider",
      "model": "image-model-v1",
      "catalog_version": "prices-2026-08-28",
      "catalog_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "pricing_rule_id": "image-model-v1",
      "components": [{
        "unit": "image",
        "rate_numerator_minor": 4000001,
        "rate_denominator": 10000,
        "quantity": 1
      }],
      "exact_estimate_numerator": "4000001",
      "exact_estimate_denominator": "10000",
      "estimated_amount_minor": 401,
      "currency": "USD"
    },
    "artifact_reference": "artifact://hubu-logo.png"
  }
}

If the vendor did not bill, the human releases the hold with the same request shape:

POST /spend/executor/release
X-Hubu-Reconciliation-Capability: HUMAN_CAPABILITY

The existing settle/release endpoints therefore accept one of two exclusive request shapes: the normal executor shape identified by claim_id, or the human reconciliation shape with claim_id, provider_reference, and evidence. Mixing the shapes is rejected. The vendor-billed shape also requires the provider receipt; vendor-did-not-bill rejects one. Reconciliation requires non-empty evidence fields, accepts only an expired claim owned by the active user, and atomically updates the receipt, claim, token, hold, and budget balance. A matching retry returns the stored outcome; a retry with different evidence or receipt is rejected. Reconciliation records the outcome, provider reference, evidence, resolving user, and timestamp. Evidence must not contain vendor credentials or sensitive provider payloads.

Human-confirmed billed reconciliation applies the same exact-cost validation and checked ceiling conversion as normal settlement. Unlike an executor, the human may confirm a legitimate vendor charge whose budget_charge_cents exceeds the authorization. Hubu then records and consumes the full conservative charge, sets released_amount_cents to zero, and records overrun_amount_cents = budget_charge_cents - authorized_max_cents. The budget remaining balance may become negative; this is retrospective accounting for a charge that already occurred, not new authorization. The budget is exhausted and later reservations fail. For example, an exact USD 5.000001 charge against a 500-cent maximum consumes 501 cents and records a one-cent overrun.

Normal executor settlement never gains this authority. An executor overrun must leave the hold claimed and route to human reconciliation with Gongbu's exact provider-attempt cost, frozen snapshot, and evidence intact. Hubu accepts that human resolution only after the claim lease expires.

The reconciliation capability is loaded separately from HUBU_RECONCILIATION_TOKEN or HUBU_RECONCILIATION_TOKEN_FILE. It must not equal or be distributed with the normal Hubu bearer token. Executors receive only the normal bearer, while human-facing CLI/MCP administration receives the reconciliation capability. The server validates both credentials before entering the reconciliation transaction.

The CLI is the direct operator surface:

hubu spend claim --claim-id CLAIM_ID
hubu spend reconcile list
hubu spend reconcile billed --claim-id CLAIM_ID \
  --provider-reference VENDOR_REFERENCE \
  --evidence "Provider usage export confirms billing" \
  --actual-vendor-cost-amount 4000001 \
  --actual-vendor-cost-scale 6 \
  --currency USD \
  --provider-request-id VENDOR_REQUEST_ID \
  --price-model-snapshot-json "$FROZEN_PRICING_SNAPSHOT_JSON" \
  --artifact-reference artifact://hubu-logo.png
hubu spend reconcile not-billed --claim-id CLAIM_ID \
  --provider-reference VENDOR_REFERENCE \
  --evidence "Provider billing search found no charge"

MCP exposes the same lookup and queue as read-only tools. Its billed and not-billed resolution tools are protected administrative tools: Hubu advertises prompt_before_call, and the MCP adapter refuses them unless it is configured to trust a client-side human approval gate. Even after that gate, the server requires the distinct reconciliation capability, so direct executor HTTP calls with only the normal bearer are rejected.

Safety Rules#

  • Executors must claim before irreversible work; validation alone is insufficient.
  • Agent platforms must supply one stable operation key and reuse it for every authorization retry. Executors claim by spend_auth_token_id, settle or release by claim_id, and never send the operation key.
  • Executors must settle after irreversible billable work succeeds.
  • Settlement must report exact vendor cost, currency, scale, the complete frozen pricing snapshot, and immutable provider receipt metadata.
  • Hubu converts exact provider cost to budget micro-units with one checked ceiling operation; normal executor settlement cannot exceed the authorized maximum.
  • A confirmed normal overrun stays claimed for human reconciliation. A human billed resolution records and consumes the full conservative charge and its overrun rather than hiding part of a legitimate vendor bill.
  • Executors must release only when no irreversible billable work occurred.
  • Agents and executors may retry any stage after an ambiguous response; Hubu returns stored authorization record state for the same operation key (authorization) or token/claim (claim and finalization) and rejects changed spend scope.
  • Executors must not resolve expired claims. A human must review provider billing and choose the reconciliation outcome.
  • Never distribute the human reconciliation capability to an executor.
  • Hubu never stores executor vendor secrets through this contract.
Edit this page on GitHub ↗Canonical source: docs/spend-executor-contract.md