T05 · Tutorial
Agent runtime pattern
Shared TS/Python runtime path: open → create intent → capability_token → guard → execute → evidence.
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
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
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
Module 1 — Session
Open tenant-bound sessionOpen a Kit session with the tenant’s service-account API key. Tenant scope is always from credentials — never free-form agent input.
- 2
Module 2 — Intent & tools
Create intent with allowed toolsCreate an intent whose allowed_tools / allowedTools exactly match the paid tool name(s) you will execute.
- 3
Module 3 — Capability
Read capability after fundedWhen the intent reaches funded, read capability_token. Immediate funding returns it on create; x402_usdc_base continues through the funding handshake.
- 4
Module 4 — Spend guard
Verify before handlerBuild a spend guard for (tenant, intent_id, capability_token) and verify capability before the tool executes.
- 5
Module 5 — Tool work
Run the paid handlerExecute the side-effecting work only after authorization. Keep merchant amounts server-resolved when catalog prices exist.
- 6
Module 6 — Evidence
Submit signed evidenceSubmit 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
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
npm install @paybond/kit
npx -p @paybond/kit paybond loginResult: .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
# 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.
If something goes wrong
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
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.
Related docs & recipes
Recipes are copy-paste production smokes — not repeated inside this tutorial.