For the complete documentation index, see llms.txt. This page is also available as Markdown.

Payment Intents

A PaymentIntent is an agent-proposed financial action that lives as a row in the Ledger. It is the only path to financial execution in Brain. There is no shortcut.

Property
Value

Layer

Ledger row, lifecycle owned by Agent layer

Created by

Internal or external agents

Executes through

Provider rails (ACH via Plaid Transfer, NetSuite SuiteTalk, BrainSmartAccount on-chain)

Gates

Policy decision plus the pre-execution gate (13 numbered checks + 10 hardening additions = 23 entries)

PaymentIntents are the second of two controlled write paths into the Ledger. The first is Raw extraction. PaymentIntents are the only Ledger write that doesn't originate from a Raw artifact, by design.

Why PaymentIntents Are a Ledger Entity

A proposed payment is itself a financial fact. Treating it as a row in the Ledger has three consequences:

Property
Effect

Queryable like any other row

The Wiki, Policy, and other agents can read PaymentIntents the same way they read transactions or obligations

Provenance carries through

Every state transition becomes an audit event linked to the row

Policy reads it directly

Policy evaluators read PaymentIntent fields and the live Ledger together; no shadow data model

The ledger_payment_intents Row

ledger_payment_intents (
  id,
  owner_id,
  created_by_agent_id,
  action_type,            -- ach_outbound | ach_inbound | wire | onchain_transfer | erp_writeback | card_payment | x402_settle | escrow_release | other
  source_account_id,
  destination_counterparty_id,
  amount,
  currency,
  obligation_id,          -- optional
  invoice_id,             -- optional
  status,                 -- proposed | pending_approval | awaiting_second_approval | approved | paused | dispatching | rejected | executed | failed | cancelled
  policy_decision_id,
  approval_ids[],
  execution_receipt_ids[],
  evidence_ids[],
  created_at,
  updated_at
)

Lifecycle

Rails

A gate-passed intent is handed to a durable outbox in the same database transaction that moves the intent from approved to dispatching. For ledger-account payments, that transaction also locks the source account, locks the latest balance snapshot, rechecks available balance net of active reservations, creates a balance reservation, and stores its reservation_id on the outbox row. The outbox worker later consumes the reservation when the intent reaches executed, or releases it when a deterministic rail rejection moves the intent to failed.

Two rails are real:

Rail
Implementation
Settlement

bank_ach

Plaid Transfer (authorization β†’ create)

Async. dispatch returns pending; a Plaid TRANSFER_EVENTS_UPDATE webhook settles or fails the outbox row

onchain_base

BrainSmartAccount.executeViaSessionKey

On-chain receipt; the rail threads the per-holder session-key nonce and signs via Azure Key Vault

Both are idempotency-keyed by the outbox row so a crash-retry never moves money twice. erp_writeback (NetSuite) remains a fail-closed stub. The M2M settlement action types x402_settle / escrow_release map to the x402_base / escrow_base rails (USDC on Base / BrainEscrow). They do not create ledger balance reservations because their spend is enforced by the settlement wallet or locked escrow state rather than by an off-chain ledger account hold. The live SDK wiring (Plaid / viem+KMS) and the sandbox/anvil round-trips are a follow-up; see services/execution/README.md.

For on-chain money movement, policy allow is not always enough to dispatch. onchain_transfer and escrow_release require at least one recorded human approval before execution. x402_settle may stay autonomous only when the signed policy rule permits on-chain settlement and sets an x402_autonomous_max_amount cap that covers the amount.

State Transitions

From
To
Trigger

proposed

pending_approval

Policy returned confirm; approvers required

proposed

approved

Policy returned auto; no human in the loop

proposed

rejected

Policy returned reject

pending_approval

awaiting_second_approval

First approver signed; a distinct second approver is required

pending_approval

approved

All required approvers signed

pending_approval

rejected

Approver explicitly rejected

pending_approval

cancelled

Tenant cancelled before approval

awaiting_second_approval

approved

Distinct second approver signed

awaiting_second_approval

rejected

Approver explicitly rejected

approved

dispatching

Gate passed; outbox row and any balance reservation committed atomically

approved

paused

Tenant or halt-category kill-switch paused execution

paused

approved

Resume re-ran and passed the live gate

dispatching

executed

Outbox worker received and validated a successful rail receipt

dispatching

failed

Deterministic rail rejection where the worker can prove no money moved

Every transition emits an audit event. The full history of any PaymentIntent is reconstructable from audit_events ordered by created_at.

How Agents Create Them

Internal agents call PaymentIntentService.create(). External agents call the MCP payment_intent.propose tool. Both paths go through the same service method, so policy evaluation, validation, and audit emission are identical.

API Surface

PaymentIntents are a Ledger entity but their lifecycle endpoints live in the Agent group.

Method
Endpoint
Purpose

POST

/v1/payment-intents

Agent proposes; returns proposed PaymentIntent

GET

/v1/payment-intents/{id}

Detail with PolicyDecision and audit trail

POST

/v1/payment-intents/{id}/approve

Human approval for confirm intents

POST

/v1/payment-intents/{id}/reject

Reject

POST

/v1/payment-intents/{id}/execute

Execute approved intent through rail

The MCP equivalent: payment_intent.propose for creation. There is no payment_intent.execute on MCP. Execution is reserved for internal Brain workers running under tenant policy.

Reading Them Like Any Other Ledger Row

Because PaymentIntents are a real Ledger entity, the Wiki and other agents query them the same way they query transactions or obligations.

Or in the MCP:

A Wiki page about a vendor automatically includes their pending PaymentIntents in the Recent Activity section.

Idempotency

Every PaymentIntent creation requires an idempotencyKey. Brain stores it in a per-tenant index; retries with the same key return the existing PaymentIntent. This protects against double-proposal under network errors or agent retries. A second, proposal-layer key dedups equivalent proposals from the same agent run (collision β†’ 409 agent_proposal_duplicate).

Kill-Switch: The paused State

An approved PaymentIntent can be paused without a terminal transition: approved ⇄ paused, and paused β†’ cancelled. POST /v1/payment-intents/{id}/pause holds it; /resume re-runs the live Β§6 gate before re-entering approved (defending against Ledger state drift while paused). POST /v1/agents/{agent_id}/halt atomically pauses all of an agent's in-flight intents and quarantines the agent. The rail dispatcher re-reads state immediately before submission and aborts cleanly if the intent was paused.

What's Next

πŸšͺ Pre-execution Gate

The deterministic gate every payment must pass (13 numbered checks + 10 hardening additions = 23 entries).

πŸ€– Agents

How internal and external agents propose actions.

πŸ“‹ Policy and Permissioning

How Policy evaluates PaymentIntents.

Last updated