paybondpaybond
Sign in

T05 · Tutorial

Agent runtime pattern

Shared TS/Python runtime path: open → create intent → capability_token → guard → execute → evidence.

~35 minAdvancedSandboxbuilderLearning modules

Outcome: Map the six lifecycle steps in either language track.

Learning view: objectives and extras expanded. Switch for commands only.

Mental model

Six explicit steps own the same authorize boundary middleware compresses. Tenant always from credentials.

You will be able to

  • Map session → intent → capability → guard → tool → evidence in order
  • Read capability_token only after funding succeeds
  • Submit signed evidence and name the predicate/settlement outcome

Prerequisites

  • · Middleware or multi-step tutorials recommended
  • · Sandbox API key and app-owned principal/payee signing material for full proofs

Shared runtime pattern

The agent can plan flexibly; the money-moving boundary stays explicit, verified, and replayable. This pattern is model-agnostic — OpenAI, Claude, Gemini, local models, MCP hosts, and app-owned runtimes share the same six modules.

Sandbox first: use First guarded spend or Agent quickstart before full production funding and recognition proofs.

  1. 1

    Module 1 — Session

    Open tenant-bound session

    Open a Kit session with the tenant’s service-account API key. Tenant scope is always from credentials — never free-form agent input.

  2. 2

    Module 2 — Intent & tools

    Create intent with allowed tools

    Create an intent whose allowed_tools / allowedTools exactly match the paid tool name(s) you will execute.

  3. 3

    Module 3 — Capability

    Read capability after funded

    When the intent reaches funded, read capability_token. Immediate funding returns it on create; x402_usdc_base continues through the funding handshake.

  4. 4

    Module 4 — Spend guard

    Verify before handler

    Build a spend guard for (tenant, intent_id, capability_token) and verify capability before the tool executes.

  5. 5

    Module 5 — Tool work

    Run the paid handler

    Execute the side-effecting work only after authorization. Keep merchant amounts server-resolved when catalog prices exist.

  6. 6

    Module 6 — Evidence

    Submit signed evidence

    Submit signed completion evidence and inspect the predicate result — release or refund follows deterministic rules.

Complete language track

Full TypeScript path: open session → create intent → read capability_token spendGuard / guardTool → execute → submitEvidence. Sandbox-first; production recognition proofs use the APP_* signer env vars below.

Open session → create intent → read capability_token → guard tool → execute → submit evidence.

runtime-guard.ts

TS
TypeScript: session → guard → execute → evidenceSwipe to inspect long lines
import { Buffer } from "node:buffer";
import { Paybond } from "@paybond/kit";

function seed32FromHex(envName: string): Uint8Array {
  const value = process.env[envName];
  if (!value) throw new Error(`missing env ${envName}`);
  const raw = Buffer.from(value.replace(/^0x/i, ""), "hex");
  if (raw.length !== 32) throw new Error(`${envName} must decode to 32 bytes`);
  return new Uint8Array(raw);
}

function requiredJsonEnv(envName: string): Record<string, unknown> {
  const raw = process.env[envName];
  if (!raw) throw new Error(`missing env ${envName}`);
  const parsed = JSON.parse(raw) as unknown;
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error(`${envName} must contain a JSON object`);
  }
  return parsed as Record<string, unknown>;
}

async function bookHotel(city: string, nightlyBudgetCents: number) {
  return {
    hotel: "Summit House",
    city,
    status: "confirmed" as const,
    price_cents: nightlyBudgetCents,
    confirmation: "HB-2049",
  };
}

// 1) Session — tenant from service-account API key only
const paybond = await Paybond.open({
  apiKey: process.env.PAYBOND_API_KEY!,
  expectedEnvironment: "sandbox",
});

try {
  const intentId = crypto.randomUUID();

  // 2–3) Intent and capability (funded create returns capability_token)
  const created = await paybond.intents.create({
    principalDid: process.env.APP_PRINCIPAL_DID!,
    principalSigningSeed: seed32FromHex("APP_PRINCIPAL_SEED_HEX"),
    payeeDid: process.env.APP_PAYEE_DID!,
    budget: { currency: "usd", max_spend_usd: 200 },
    predicate: {
      version: 1,
      root: {
        op: "and",
        clauses: [
          { op: "completion", path: ["reservation", "status"], value: "confirmed" },
          { op: "budget_cap", path: ["reservation", "price_cents"] },
        ],
      },
    },
    currency: "usd",
    amountCents: 20_000,
    evidenceSchema: {
      type: "object",
      properties: { reservation: { type: "object" } },
    },
    deadlineRfc3339: "2030-12-31T23:59:59Z",
    allowedTools: ["travel.book_hotel"],
    recognitionProof: requiredJsonEnv("APP_INTENT_CREATE_RECOGNITION_PROOF_JSON"),
    settlementRail:
      process.env.APP_SETTLEMENT_RAIL === "x402_usdc_base"
        ? "x402_usdc_base"
        : "stripe_connect",
    intentId,
    idempotencyKey: `intent:${intentId}`,
  });

  const capabilityToken = String(created.capability_token ?? "");
  if (!capabilityToken) {
    throw new Error("intent created without capability_token; ensure the intent is funded");
  }

  // 4) Spend guard bound to (tenant, intent, capability)
  const guard = paybond.spendGuard(intentId, capabilityToken);

  // 5) Tool work only after spend authorize
  const bookHotelTool = {
    name: "travel.book_hotel",
    execute: guard.guardTool(
      { operation: "travel.book_hotel", requestedSpendCents: 18_700 },
      bookHotel,
    ),
  };
  const reservation = await bookHotelTool.execute("Lisbon", 18_700);

  // 6) Signed evidence → release / refund follows predicate
  const submitted = await paybond.intents.submitEvidence({
    intentId,
    payeeDid: process.env.APP_PAYEE_DID!,
    payeeSigningSeed: seed32FromHex("APP_PAYEE_SEED_HEX"),
    payload: { reservation },
    artifactsBlake3Hex: [],
    recognitionProof: requiredJsonEnv("APP_EVIDENCE_RECOGNITION_PROOF_JSON"),
    idempotencyKey: `evidence:${intentId}`,
  });

  console.log({
    intentState: created.state,
    settlementState: submitted.state,
    predicatePassed: submitted.predicatePassed,
  });
} finally {
  await paybond.aclose();
}

Result: Paid tool only runs after authorize; signed cost_cents evidence lands on the intent.

Install Kit CLI / SDK for your language track, then sandbox login.

install.sh

TS
TypeScript: Install and sandbox loginSwipe to inspect long lines
npm install @paybond/kit
npx -p @paybond/kit paybond login

Result: .env.local holds PAYBOND_API_KEY (0600); tenant is still principal-bound.

Secrets and optional production recognition seeds — never put tenant or intent in tool args.

.env.example

TS
TypeScript: Required env (tenant from API key — never tool args)Swipe to inspect long lines
# PAYBOND_API_KEY from `paybond login` only.
# APP_* are app/signers you control — never free-form agent tool args for tenant IDs.
export APP_PRINCIPAL_DID="did:web:example.com#principal"
export APP_PRINCIPAL_SEED_HEX="..."
export APP_PAYEE_DID="did:web:example.com#hotel-booker"
export APP_PAYEE_SEED_HEX="..."
export APP_INTENT_CREATE_RECOGNITION_PROOF_JSON='{"key_id":"..."}'
export APP_EVIDENCE_RECOGNITION_PROOF_JSON='{"key_id":"..."}'
# optional stablecoin rail:
# export APP_SETTLEMENT_RAIL="x402_usdc_base"

Result: Sandbox proof works with API key only; live recognition needs APP_* or attach bundle later.

Narrative deep-dives and example repos remain under the overview and language MD pages (overview, TypeScript, Python) — the code above is complete enough to map every module without leaving this tutorial.

Prerequisites (production track)

  • Sandbox or live service-account key (tutorials default sandbox for sandbox keys).
  • Principal and payee Ed25519 identities when using full recognition-proof examples.
  • Environment that can fund the example intent (or continue x402 handshake).
  • Optional deeper narrative: Agent runtime tutorial (links and stablecoin rail notes).

Verify before you continue

Check these off against your terminal or timeline output — progress stays on this device.

0/3

If something goes wrong

  • If you see

    Missing capability_token on create

    Do this

    Intent must be funded; check settlement rail and bootstrap environment.

  • If you see

    Tenant IDs taken from tool args

    Do this

    Open with service-account key only; never accept tenant/intent from untrusted agent input.

Self-check

Answer without scrolling up — then reveal the model answer to compare.

Which three identifiers must stay session-bound (not agent-supplied) on every paid call?

Next steps

Pick a branch — not every path needs every tutorial.

Recipes are copy-paste production smokes — not repeated inside this tutorial.