paybondpaybond
Sign in

T21 · Tutorial

Protect first agent tool (production)

Create/fund a live intent, mint attach material, run production attach smoke, then deferred instrument + bind — not sandbox smoke.

~45 minIntermediateProductionproductLifecycle pipeline

Outcome: One paid product tool authorizes only after production attach on a funded intent; you can contrast that with sandbox smoke.

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

Mental model

create/fund → capability_token → attach (signing) → deferred instrument → per-session bind. Sandbox smoke skips live recognition proofs.

You will be able to

  • State sandbox smoke ≠ live attach / recognition
  • Obtain intent_id + capability_token via create (or create + fund / fundWithX402 / fundWithMpp*)
  • Mint an attach bundle under Agent middleware keys, or export APP_PAYEE_* and APP_AGENT_RECOGNITION_*
  • Run paybond agent production attach smoke against a funded intent
  • Instrument deferred; bind per user/session without sandbox: true
  • Guard at the tool executor; confirm auto-evidence after side-effecting success

Prerequisites

  • · Get production ready (live key, validated policy, settlement rail ready)
  • · Ability to create/fund a production intent (or reuse one with capability_token)
  • · Agent middleware keys access (attach bundle) or APP_PAYEE_* / APP_AGENT_RECOGNITION_* seeds

Production path (not sandbox smoke)

paybond agent sandbox smoke is not live attach. Sandbox smokes prove gateway-composed guardrails without agent recognition proofs on gated mutations. Production is create/fund → capability_token → attach signing → deferred instrument → bind per session.

  1. 01

    Create and fund

    paybond.intents.create; fundWithX402 / fundWithMpp* if no capability_token yet.

  2. 02

    Attach material

    Console Agent middleware keys bundle, or APP_PAYEE_* and APP_AGENT_RECOGNITION_*.

  3. 03

    Production attach smoke

    paybond agent production attach smoke with funded intent env.

  4. 04

    Instrument (deferred)

    Policy and tools at process start — no sandbox: true.

  5. 05

    Bind per session

    bind({ intentId, capabilityToken }) or attach: "env".

  6. 06

    Execute and evidence

    Product tool runs; evidence_preset signs completion on success.

Console steps (attach)

  • Settlement already ready from Get production ready — re-check if fund fails closed.
  • Machine access → Agent middleware keys — enter funded intent_id and capability_token; copy one-time env (PAYBOND_ATTACH_INTENT_ID, PAYBOND_CAPABILITY_TOKEN, PAYBOND_ATTACH_BUNDLE).
  • Treat PAYBOND_ATTACH_BUNDLE as a secret (never log, commit, or put in URLs).
  • Later inspection: Intents and Agent runs under Operations (next tutorial).

Env prep and live attach smoke

Requires partner Gateway and funded intent
  1. Step 1 of 3

    Export funded intent and signing material

    Use a funded intent that has not reached evidence_submitted. Set PAYBOND_API_KEY to the live service-account key.

    Goal: Production attach smoke needs capability verify and recognition/payee signing — sandbox smoke never required these.

    Run this

    export PAYBOND_API_KEY=paybond_sk_live_…
    export PAYBOND_ATTACH_INTENT_ID=<funded-intent-uuid>
    export PAYBOND_CAPABILITY_TOKEN=<capability-token>
    # Either attach bundle from Console…
    export PAYBOND_ATTACH_BUNDLE=ab1.<opaque>
    # …or payee and recognition seeds (64-hex seeds):
    export APP_PAYEE_DID=did:web:vendor.example
    export APP_PAYEE_SEED_HEX=<64-hex>
    export APP_AGENT_RECOGNITION_KEY_ID=<registered-key-id>
    export APP_AGENT_RECOGNITION_SEED_HEX=<64-hex>

    Example response

    # shell env ready (illustrative)
    PAYBOND_API_KEY=paybond_sk_live_…
    PAYBOND_ATTACH_INTENT_ID=…
    PAYBOND_CAPABILITY_TOKEN=…
    # plus PAYBOND_ATTACH_BUNDLE or APP_PAYEE_* / APP_AGENT_RECOGNITION_*

    You should see: All required vars set; intent is funded and unused for evidence.

    Note: Optional: PAYBOND_GATEWAY_URL for staging. Hosted default is https://api.paybond.ai.

  2. Step 2 of 3

    Production attach smoke (not sandbox smoke)

    End-to-end bind → guarded tool → auto-evidence through the intent/evidence path with recognition.

    Goal: Honest live-path smoke — sandbox CI commands do not substitute.

    Run this

    paybond agent production attach smoke \
      --attach-intent-id "$PAYBOND_ATTACH_INTENT_ID" \
      --capability-token "$PAYBOND_CAPABILITY_TOKEN" \
      --operation paid-tool \
      --requested-spend-cents 100 \
      --result-body '{"status":"ok","cost_cents":100}' \
      --format json

    Example response

    {
      "data": {
        "execute": {
          "authorization": { "allow": true },
          "evidence": { "submitted": true }
        }
      }
    }

    You should see: Exit 0; JSON shows data.execute.authorization.allow and data.execute.evidence.submitted true.

    Note: Capability may also come from --capability-token-file. Align --operation with policy. Staging make target: production-attach-staging-smoke.

  3. Step 3 of 3

    Contrast: sandbox smoke (evaluation only)

    Do not treat the following as production attach readiness — it skips live recognition on gated mutations.

    Goal: Keep this as evaluation-only so you never promote on a false green.

    Run this

    paybond agent sandbox smoke --operation paid-tool --requested-spend-cents 100 --evidence-preset cost_and_completion --result-body '{"status":"ok","cost_cents":100}' --format json

    Example response

    {
      "authorized": true,
      "operation": "paid-tool",
      "status": "released"
    }
    # evaluation only — no live recognition proofs

    You should see: You can name what this does not prove (SEC-003 recognition / production attach).

Create / fund (capability_token)

Your product backend creates the intent on the configured settlement rail, then funds when create does not return capability_token. Pass both into bind — the model never invents them. Deep rail table: Fund intents by rail; funding vs evidence: How intent funding works.

  • Immediate rails: read capability_token from create when present.
  • x402: prefer paybond.intents.fundWithX402 / fund_with_x402 (app-owned wallet signer).
  • stripe_mpp: fundWithMppCharge / fundWithMppSession (Payment Auth).
  • CLI mutations (paybond intents create|fund) also need recognition proofs — see CLI contract.

Immediate-rail sketch: create may return capability_token; delayed rails need an extra fund step.

create-fund.ts

TS
TypeScript: create → capability sketchSwipe to inspect long lines
// Immediate-fund rail sketch (e.g. stripe_connect).
// Principal/payee signing and recognitionProof required on live create.
// Helpers seed32FromHex / requiredJsonEnv match the agent-runtime tutorial.
const intentId = crypto.randomUUID();
const created = await paybond.intents.create({
  principalDid: process.env.APP_PRINCIPAL_DID!,
  principalSigningSeed: seed32FromHex("APP_PRINCIPAL_SEED_HEX"),
  payeeDid: process.env.APP_PAYEE_DID!,
  payeeSigningSeed: seed32FromHex("APP_PAYEE_SEED_HEX"),
  budget: { currency: "usd", max_spend_usd: 200 },
  predicate: { version: 1, root: { /* completion and budget_cap clauses */ } },
  currency: "usd",
  amountCents: 20_000,
  evidenceSchema: { type: "object" },
  deadlineRfc3339: "2030-12-31T23:59:59Z",
  allowedTools: ["travel.book_hotel"],
  recognitionProof: requiredJsonEnv("APP_INTENT_CREATE_RECOGNITION_PROOF_JSON"),
  settlementRail: "stripe_connect",
  intentId,
  idempotencyKey: `intent:${intentId}`,
});
const capabilityToken = String(created.capability_token ?? "");
if (!capabilityToken) {
  // Delayed rail: fund next (x402 / stripe_mpp)
  // await paybond.intents.fundWithX402({ intentId, recognitionProof, signPayment, issueRecognitionProof })
  // await paybond.intents.fundWithMppCharge({ intentId, recognitionProof, createPaymentCredential, issueRecognitionProof })
  throw new Error("fund before bind — no capability_token yet");
}
// Then bind / attach smoke with intentId and capabilityToken

Result: You hold intentId + capability_token ready for production bind (never take them from model tool args).

Product wiring (instrument and bind)

Kit sits on the tool executor, not on the model provider base URL. Same deferred instrument → per-session bind for OpenAI Agents and Claude Agent SDK. For signing without raw seeds in source, use attach: "env" after minting under Machine access → Agent middleware keys — see Production attach.

  • Omit sandbox: true / sandbox=True in production deploys.
  • Never share one bound runtime across concurrent users — bind per task.
  • Model tools receive business args only; tenant/intent stay server-bound.
  • Paid ops only through framework tools after bind (OpenAI invoke / Claude tool).
  • Stripe/Shopify charge tools: next tutorial Production Stripe & Shopify charges.

Deferred instrument at process start → bind per user/session with a funded intent.

openai-agents-instrument-bind.ts

TS
TypeScript: OpenAI Agents — instrument and bindSwipe to inspect long lines
import { tool, Agent, Runner } from "@openai/agents";
import { z } from "zod";
import { Paybond } from "@paybond/kit";

const paybond = await Paybond.open({
  apiKey: process.env.PAYBOND_API_KEY!, // paybond_sk_live_…
  expectedEnvironment: "live",
});

// OpenAI Agents SDK FunctionTool — paid work in execute only
const bookHotelTool = tool({
  name: "travel.book_hotel",
  description: "Book a hotel room",
  parameters: z.object({
    city: z.string(),
    estimatedPriceCents: z.number().int().nonnegative(),
  }),
  execute: async (args) => bookHotel(args),
});

// Process start: policy + framework tools only — deferred (no sandbox bootstrap)
const instrumented = await paybond.instrument({
  policy: "./paybond.policy.yaml",
  framework: "openai-agents",
  tools: [bookHotelTool],
});
// instrumented.binding.phase === "deferred"

// Per user session / agent task — intent from create/fund (server-owned):
const runtime = await instrumented.bind({
  intentId, // never from model tool args
  capabilityToken,
  // Or: attach: "env" after Machine access → Agent middleware keys mint
});
// Bound tools: spend verify before FunctionTool.invoke → execute → auto-evidence

const agent = new Agent({ name: "Travel", tools: runtime.tools });
await Runner.run(agent, "Book a hotel in Lisbon under $200.");
await paybond.aclose();

Result: Bound FunctionTools verify spend before execute, then auto-submit evidence.

Same deferred pattern for Claude Agent SDK custom tools + Paybond MCP packaging.

claude-agents-instrument-bind.ts

TS
TypeScript: Claude Agent SDK — instrument and bindSwipe to inspect long lines
import { tool, query } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
import { Paybond } from "@paybond/kit";
import { createPaybondClaudeAgentsConfig } from "@paybond/kit/claude-agents";

const paybond = await Paybond.open({
  apiKey: process.env.PAYBOND_API_KEY!, // paybond_sk_live_…
  expectedEnvironment: "live",
});

// Claude Agent SDK custom tool() — paid work only in custom handlers (not built-ins)
const sdkTools = [
  tool(
    "travel.book_hotel",
    "Book a hotel room",
    { city: z.string(), estimatedPriceCents: z.number() },
    async (args) => ({
      content: [{ type: "text", text: JSON.stringify(await bookHotel(args)) }],
      structuredContent: await bookHotel(args),
    }),
  ),
];

// Process start: policy + framework tools only — deferred (no sandbox bootstrap)
const instrumented = await paybond.instrument({
  policy: "./paybond.policy.yaml",
  framework: "claude-agents",
  tools: sdkTools,
});
// instrumented.binding.phase === "deferred"

// Per user session / agent task — intent from create/fund (server-owned):
const runtime = await instrumented.bind({
  intentId, // never from model tool args
  capabilityToken,
  // Or: attach: "env" after Machine access → Agent middleware keys mint
});
// Bound run and MCP packaging for query(): spend verify before handler → auto-evidence
const { mcpServer, allowedTools } = createPaybondClaudeAgentsConfig(
  runtime.run,
  sdkTools,
);

await query({
  prompt: "Book a hotel in Lisbon under budget.",
  options: {
    mcpServers: { paybond: mcpServer },
    allowedTools,
  },
});
await paybond.aclose();

Result: query() only runs allowed tools after spend verify; evidence submits on success.

Signing env reference also lives in Production attach · Capabilities · Agent middleware.

Verify before you continue

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

0/6

If something goes wrong

  • If you see

    Calling sandbox smoke green 'production ready'

    Do this

    Sandbox composes evidence at the gateway and skips live recognition proofs. Use paybond agent production attach smoke with PAYBOND_ATTACH_INTENT_ID, PAYBOND_CAPABILITY_TOKEN, and APP_* signing material.

  • If you see

    Leaving sandbox: true in production deploy

    Do this

    Remove sandbox bootstrap. Use instrumented.bind({ intentId, capabilityToken, … }) or attach: "env" with production evidence material.

  • If you see

    Sharing one bound runtime across concurrent requests

    Do this

    bind() returns an immutable runtime per task — do not hoist one bind across users.

  • If you see

    tenantId / intentId accepted from the model tool args

    Do this

    Inject session-bound intent and capability from your server session; model only sees business args.

  • If you see

    Attach smoke 409 or evidence already submitted

    Do this

    Use a fresh funded intent that has not reached evidence_submitted — production attach smoke advances state.

Self-check

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

Name the CLI that proves live attach versus the CLI that only proves sandbox guardrails — and where production intentId comes from.

Next steps

Pick a branch — not every path needs every tutorial.

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