Launch an AI agent workflow

The whole loop, from "I have a task" to "my agent pays per call for a service on the network" by itself. From the app, the UX story: log in, fund, set a budget, deploy, while the agent finds its own services. On the local development network, the SDK path, step by step: discover a service over HTTP, open a gasless prepaid session through the facilitator, sign one voucher per call, call the tool, settle on-chain, read the money flow.

The other side, the node an agent pays: Run a node.


1. The UX story (what "launch a workflow" feels like)

A workflow is an agent that calls one or more services (an MCP tool server, a resold API, a model) and pays a per-call micropayment in USDC / USDT / ETH. ZAI is never spent here; it only secures the nodes you pay. From your side there are five moments, and only one asks for a signature.

You log in by connecting a wallet in the app, or with an ERC-4337 smart account (AIOUMAccount) so an agent acts under a budget you set once (§3). You fund it by moving USDC into the account. Settlement is gasless (EIP-3009 for USDC, Permit2 for any other token), so you never hold the chain's gas token; a self-run paymaster sponsors gas.

The agent discovers services for itself. The on-chain catalogue (the ServiceRegistry, read by the discovery indexer: an "app store" for agent services) lists each service with category, descriptorURI, pricePerCall and acceptedAssets; only nodes with active ZAI stake can list, so every result is backed by slashable collateral. An agent allowed to find its own tools searches it mid-run, calls what it finds within its budget, and after each settled session rates the operator on-chain from what it was served, so the next agent reads ratings from real results.

You set a budget by authorizing a session key with a spending cap ("this agent may spend up to 25 USDC on service #7"): the one human-in-the-loop moment (authorizeSessionKey, cap enforced on-chain; account abstraction). Then you deploy and run. The agent opens a prepaid session and, per call, signs a cumulative payment voucher off-chain and makes the call; the session key signs within its cap, no human does. When it is done the session settles: the node is paid for exactly the calls it served and the unspent deposit is refunded automatically. If the node never settles and the session idles past the inactivity timeout, you reclaim the unspent deposit from the owner wallet and the node is paid nothing.

What makes this safe to automate: the agent signs only a cumulative amount it authorized, and only for calls it received. The node can never redeem more than the agent signed; the agent's worst case in a dispute is one call's price. ARCHITECTURE.md §2 explains why the amount is trustworthy.


2. The SDK path, step by step

The quickest way to watch the whole loop is the local development network: Anvil, the non-custodial facilitator, the MCP-style node runtime, the client SDK, and the demo agent in offchain/src/demo/run.ts. The demo is a minimal agent workflow: everything in §1 from a plain wallet key, without the smart-account layer (account abstraction), so every message stays in plain sight.

2.1 Bring up the stack

Four processes make a network. Run each in its own terminal; all off-chain commands use cwd offchain/.

# Terminal 1 — local development chain (chainId 31337, RPC on :8545)
anvil

# Terminal 2 — deploy the full contract stack + wiring to Anvil (writes deployments.json)
cd offchain && npm run deploy

# Terminal 3 — node runtime on :8546 (operator = account #1; stakes, registers, serves tools)
cd offchain && npm run node

# Terminal 4 — non-custodial x402 facilitator on :8547 (relayer = account #3; pays gas only)
cd offchain && npm run facilitator

Ports (from config.ts): Anvil 8545, node runtime 8546, facilitator 8547. The node bootstrap (setup.ts) is idempotent: it stakes the operator's own ZAI if there is none yet (10,000 on the local development network, where the operator account holds it, so the node is live at once), registers an ERC-8004 identity, and lists a mcp:demo-tools service priced at 1 USDC per call with [USDC] as accepted assets.

2.2 Run the agent workflow

# Terminal 5 — the demo agent: discover → gasless session → 3 paid calls → settle
cd offchain && npm run demo

That one command runs the whole workflow; step by step, it is what your own agent does against the same SDK and HTTP APIs.

2.3 Step by step

The agent is account #2 (the client). It talks to three pieces:

PieceRole
Node runtime (:8546)Discovery (GET /services), serving calls (POST /mcp), settlement (POST /settle).
Facilitator (:8547)Opens the gasless session as a relayer (POST /open/eip3009); never holds funds.
SDK (offchain/src/sdk)loadDeployments(), getContract(...), signVoucher(...).

1. Discover the service over plain HTTP. The agent asks the node what it sells and what it costs:

curl -s http://127.0.0.1:8546/services | jq
# [ { "serviceId", "category", "descriptorURI", "pricePerCall", "acceptedAssets" } ]

In the agent, that is await httpGet(${NODE_URL}/services); it picks services[0] and reads serviceId and pricePerCall (1 USDC = 1_000_000, 6 decimals).

2. Open a gasless prepaid session through the facilitator. The agent builds an EIP-3009 receiveWithAuthorization for a 10 USDC deposit and posts it to POST /open/eip3009. The facilitator submits PaymentManager.openSessionWithAuthorization(...) as relayer (account #3): it pays gas, funds move straight from client to contract via the authorization (never through the facilitator), and the agent spends no ETH and signs nothing on-chain.

// POST http://127.0.0.1:8547/open/eip3009
{
  "serviceId": "<id>",
  "asset": "<deployments.usdc>",
  "amount": "10000000",            // 10 USDC (6 decimals)
  "from": "<clientAddr>",
  "validAfter": "0",
  "validBefore": "281474976710655", // 2^48 - 1 (max uint48)
  "nonce": "0x<32 random bytes>",
  "v": 0, "r": "0x00…00", "s": "0x00…00"  // the local MockUSDC ignores the sig
}
// → { "sessionId", "txHash" }   (parsed from the SessionOpened event)

On the AIOUM network the v/r/s are a real EIP-3009 signature. The local development network's MockUSDC ignores the signature, so the demo passes the stub v=0, r=s=0x0…0.

3. Make paid calls with cumulative vouchers. A session is prepaid; for call n the agent signs cumulativeAmount = pricePerCall * n (a monotonically increasing running total) with the SDK and includes it in the MCP tools/call:

import { signVoucher } from "../sdk/index.js";

const cumulativeAmount = pricePerCall * BigInt(n);        // 1, 2, 3 USDC for calls 1, 2, 3
const voucher = await signVoucher(
  client, CHAIN_ID, deployments.payment, sessionId, cumulativeAmount,
);

// POST http://127.0.0.1:8546/mcp  (JSON-RPC 2.0)
const rpcReq = {
  jsonrpc: "2.0", id: n, method: "tools/call",
  params: {
    name: "wordcount",
    arguments: { text: "the quick brown fox jumps over the lazy dog" },
    payment: {
      sessionId: sessionId.toString(),
      cumulativeAmount: cumulativeAmount.toString(),
      signature: voucher.signature,
    },
  },
};

The voucher is an EIP-712 PaymentVoucher(uint256 sessionId,uint256 cumulativeAmount), domain-separated by chainId + the PaymentManager address, so it cannot be replayed on another chain or contract. Before running the tool the node verifies on-chain that the session is Open, the recovered signer is the session's client, and storedSpent + pricePerCall <= cumulativeAmount <= session.deposited. If payment is missing or short, the node returns a JSON-RPC error with code: 402, the x402 signal that says exactly how much is required.

The demo makes 3 wordcount calls, signing a fresh cumulative voucher each time.

4. Settle on-chain. The agent triggers settlement by asking the node, whose settle loop redeems the voucher and retries until the transaction lands:

curl -s -X POST http://127.0.0.1:8546/settle \
  -H 'content-type: application/json' \
  -d '{"sessionId":"<id>"}' | jq
# → { "txHash", "cumulativeAmount" }

The node calls PaymentManager.settle(sessionId, cumulativeAmount, signature) with the latest voucher. This is the only on-chain write that moves the payment.

2.4 The money flow

After settle, the demo re-reads balances and prints the split. With a 10 USDC deposit and 3 calls at 1 USDC, 3 USDC gross is spent, and settlement splits the gross (PaymentManager.settle; both shares come off gross, not post-fee):

RecipientAmountRule
Treasury (DAO)protocolFeeprotocolFeeBps of gross — 5% at deploy.
StakingManager (node's delegators)delegatorSharedelegatorShareBps of gross — 10% by default, distributed via notifyReward.
Operator (node)operatorNetgross − protocolFee − delegatorShare.
Client (agent)refunddeposit − gross — the unused 7 USDC is returned automatically.

The demo pulls the exact numbers from the SessionSettled event, asserts conservation (protocolFee + delegatorShare + operatorNet + refund == deposit) and that on-chain balance deltas match the split: a gasless agent prepaid 10 USDC, consumed 3 metered calls, and got the unused 7 USDC back.


3. The "set a budget once" layer (ERC-4337 + session keys)

In the SDK path the agent signs one voucher per call with the client's key, and a human authorizes by running it. The account-abstraction layer turns that into the §1 experience, in three parts.

ERC-4337 smart accounts (src/aa/AIOUMAccount.sol, CREATE2 factory, e2e-tested through EntryPoint.handleOps) hold the agent's funds. Session keys with spending caps are how the owner delegates: authorize a key once with a cap (25 USDC on service #7, say), and the agent opens sessions and signs each per-call voucher with it, within the cap, with no further approval. "Set a budget once, let the agent run", with the cap enforced on-chain. A self-run paymaster sponsors gas so the agent never holds the chain's gas token, beside an in-repo bundler relay (npm run bundler). On the AIOUM network the signature-gated AIOUMVerifyingPaymaster sponsors ops; the local development network's AIOUMPaymaster sponsors every op (Funding).

Full flow: account-abstraction guide; off-chain components: ARCHITECTURE.md §7. The trust model (prepaid sessions, client-signed EIP-712 vouchers, automatic refund) is the same on both paths; the session key signs vouchers accepted via ERC-1271, and per-call and per-login signing friction is gone.


See also

  • offchain/SPEC.md — the off-chain integration spec (SDK exports, facilitator + node HTTP APIs, voucher semantics).
  • ARCHITECTURE.md — full design, end-to-end flow, decentralization posture, and the bug → fix map.
  • SECURITY.md — the trust model.
  • Run a node — the operator side of a workflow.

Next: Budgets and session keys — how the cap you set once is enforced by the smart account itself, so the agent pays alone and can never overspend.