Architecture

Agent Patterns

Consumer agents initiate payments on behalf of users. Fulfillment agents receive payments for delivered services. Evaluator agents attest completion for deferred work. Three composable patterns covering the full agentic commerce surface.

Two Core Agent Types

REAPP defines two composable runtime patterns. Consumer agents hold a delegated mandate and autonomously execute payments to acquire services. Fulfillment agents expose services behind x402 paywalls and receive payments from consumer agents. Both patterns ship as reference implementations in the SDK.

Consumer vs Fulfillment Agent Patterns
Consumer agents wrap fetch(). Fulfillment agents wrap Express middleware. Both patterns are covered by @reapp/sdk with minimal boilerplate.

Consumer Agent

Consumer Agent Setup (proposed API)
import { createReappAgent, loadIntentMandate } from '@reapp/sdk';
import { createKeypairSigner } from '@x402/fetch';

const mandate = await loadIntentMandate(mandateId);

const agent = createReappAgent({
  mandate,
  signer: createKeypairSigner(agentKeypair), // agent key, NOT user key
  mandateContract: MANDATE_CONTRACT_ADDRESS,
  network: 'stellar:pubnet',
});

// Transparently handles full 7-step x402 flow
const data = await agent.fetch('https://api.dataservice.com/premium/report');

Fulfillment Agent

Fulfillment Agent Middleware (proposed API)
import express from 'express';
import { reappMiddleware } from '@reapp/sdk/express';

const app = express();

app.use(reappMiddleware({
  payTo: merchantStellarAddress,
  facilitatorUrl: 'https://channels.openzeppelin.com/x402/testnet',
  routes: {
    'GET /api/data/*':    { price: '0.10', asset: 'USDC' },
    'POST /api/compute':  { price: '1.00', asset: 'USDC' },
  },
  requireMandate: { 'POST /api/compute': true },
  registry: MANDATE_CONTRACT_ADDRESS,
}));

The Evaluator Agent — When It's Needed

x402 works perfectly when delivery and payment are coupled — agent pays, server instantly returns data. But deferred or qualitative work breaks this model. Translate a document, write code, design a logo — payment happens now, delivery happens later. How do you know the work was actually done and done correctly?

API Payments vs Task Payments
The evaluator pattern is only required when delivery and payment are temporally decoupled. For standard API calls, x402 is self-evidencing — no evaluator needed.

Three Evaluator Tiers

REAPP ships three evaluator implementations ordered by trust level and value. The appropriate tier is selected at job creation based on the task value and whether correctness is objectively verifiable.

Evaluator Tier Selection
Tier selection is based on task value and verifiability. Low-value deterministic tasks use self-attestation. High-value qualitative tasks use the community registry with staked collateral.

Escrow + Evaluator Lifecycle

Money goes into Soroban escrow at payment time. The evaluator attests completion. Escrow releases to the provider on approval, refunds to the client on rejection or expiry. No funds move until the evaluator signs — and the evaluator's reputation score is updated on-chain based on dispute outcomes.

Escrow Job State Machine
Seven terminal states. The timeout path (Funded → Expired) ensures clients are never locked out if a provider disappears. Evaluator collateral is slashed on provably bad attestations.
Testnet MVP — escrow parameters

The escrow timeout window, dispute resolution flow, and evaluator collateral requirements shown here are testnet placeholders. Production parameters — including timeout durations, minimum collateral stakes, and the reputation update formula — require stress testing with real transaction volumes and adversarial conditions before mainnet deployment.

Evaluator Interface

Evaluator Interface
interface ReappEvaluator {
  // Called by fulfillment agent when work is submitted
  submitWork(jobId: string, deliverableHash: string): Promise<void>;

  // Called by evaluator to attest completion
  attestCompletion(
    jobId: string,
    approved: boolean,
    reason?: string
  ): Promise<TxHash>;

  // On-chain: escrow releases to provider on approval,
  //           refunds to client on rejection or expiry
}
Tier 1
Self-attestation
Low value · No third party

Deterministic outputs: API responses, file downloads. Consumer agent verifies delivery itself.

Tier 2
Reference evaluator
Medium value · REAPP-maintained

Qualitative tasks. REAPP-maintained ADK agent uses LLM judge to assess quality vs specification.

Tier 3
Community registry
High value · Staked + reputation

Any third party — DAO, multisig, specialized AI. Evaluators stake collateral, build reputation on-chain.

Testnet MVP defaults

The value thresholds shown (< $5 / $5–$100 / > $100), staking collateral amounts, reputation scoring formula, and slashing conditions are testnet defaults chosen for demo clarity. Production values will be determined through real-world evaluation of transaction patterns and adversarial testing. The tier boundaries, in particular, depend on observed agent behavior at scale — they may shift significantly once real payment volumes are measured.

Agent Framework — Google ADK TypeScript

The TypeScript ADK shipped in December 2025 at v0.4.0 — functional for prototyping but carrying a "Pre-GA" disclaimer. Python remains the mature path at v1.26.0. For REAPP's demo, ADK TypeScript is sufficient. The critical insight: FunctionTool.execute is arbitrary TypeScript — an x402 payment call fits naturally.

x402 Payment as ADK Tool
import {
  LlmAgent,         // Main LLM-powered agent
  FunctionTool,     // Custom function tool (Zod schema params)
  InMemoryRunner,   // In-memory runner for dev/testing
  SequentialAgent,  // Runs sub-agents in order
  ParallelAgent,    // Runs sub-agents concurrently
} from '@google/adk';

// x402 payment as an ADK tool
const makeX402Payment = new FunctionTool({
  name: 'pay_for_resource',
  description: 'Pay for API access via x402 on Stellar',
  parameters: z.object({
    resourceUrl: z.string(),
    maxAmountUSD: z.number(),
  }),
  execute: async ({ resourceUrl, maxAmountUSD }) => {
    const response = await x402Fetch(resourceUrl, { maxAmount: maxAmountUSD });
    return { status: response.status, data: await response.json() };
  },
});

Multi-Agent Delegation

ADK's subAgents on LlmAgent enables automatic delegation via the LLM's transfer_to_agent mechanism:

Multi-Agent Delegation
const fulfillmentAgent = new LlmAgent({
  name: 'fulfillment_agent',
  model: 'gemini-2.5-flash',
  description: 'Handles payments and order processing on Stellar',
  tools: [makeX402Payment, checkBalance, getTransactionStatus],
});

const consumerAgent = new LlmAgent({
  name: 'consumer_agent',
  model: 'gemini-2.5-flash',
  instruction: 'Help users shop. Delegate payment tasks to fulfillment_agent.',
  subAgents: [fulfillmentAgent],
});

Chat UI — Vercel AI SDK v6 + assistant-ui

For the consumer-facing demo, the optimal stack is Vercel AI SDK v6 (ai + @ai-sdk/react) for backend streaming and tool calling, paired with assistant-ui (@assistant-ui/react, 450K+ monthly npm downloads) for polished chat primitives. AI SDK v6 introduced ToolLoopAgent, parts-based message rendering for rich inline content, and human-in-the-loop tool approval.

Server-Side Streaming with Payment Tools
// app/api/chat/route.ts — Server-side streaming with payment tools
import { streamText, convertToModelMessages } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: 'openai/gpt-4o',
    messages: await convertToModelMessages(messages),
    tools: {
      sendPayment: {
        description: 'Send USDC payment on Stellar',
        parameters: z.object({
          recipient: z.string(),
          amount: z.number(),
          memo: z.string().optional(),
        }),
        execute: async ({ recipient, amount }) => {
          const tx = await stellarClient.transfer({
            to: recipient, amount: BigInt(amount * 1e7),
          });
          return { txHash: tx.hash, status: 'confirmed', amount };
        },
      },
    },
  });
  return result.toUIMessageStreamResponse();
}
Client-Side Payment Rendering
// Client-side — rendering payment confirmations inline
{message.parts.map((part, i) => {
  switch (part.type) {
    case 'text': return <p key={i}>{part.text}</p>;
    case 'tool-sendPayment':
      if (part.state === 'partial-call') return <PaymentPending key={i} />;
      if (part.state === 'result') return <PaymentCard key={i} result={part.result} />;
  }
})}