Skip to content

The agent guide

PIPRAIL_AGENT_GUIDE is the PipRail contract written for the model: one tight Markdown string an LLM reads once and then drives the payment tools correctly with near-zero other docs. It covers the quote → plan → pay loop, the three settlement rails it can pay (onchain-proof, exact, upto), how to read a refusal without crashing or double-spending, and the difference between the two operating modes.

It’s a pure static constant — no imports, no I/O — so you can prepend it to any agent’s system prompt, MCP or not.

import { PIPRAIL_AGENT_GUIDE } from '@piprail/sdk'
const systemPrompt = `${PIPRAIL_AGENT_GUIDE}\n\n${yourTaskInstructions}`

agentGuide() is a function that returns the same string, for callers that prefer a call over a constant:

import { agentGuide } from '@piprail/sdk'
const guide = agentGuide()
// → '# Paying with PipRail — the agent contract\n…' (identical to PIPRAIL_AGENT_GUIDE)

The loop it teaches: quote → plan → pay

Section titled “The loop it teaches: quote → plan → pay”

The guide tells the model to always run three steps in order so it never commits to a payment it can’t finish:

StepToolWhat it does
1. Price itpiprail_quote_payment(url)Returns amount, token, chain, and whether it’s within policy (wraps quote()). No funds move.
2. Can I afford it now?piprail_plan_payment(url)Reads balance, gas, and recipient readiness across rails (wraps planPayment()) → { payable, best, fundingHint, session? }.
3. Paypiprail_pay_request(url, method?, body?)Pays — but only if the plan was payable — and returns the result.

The rule it states plainly: always plan before you pay. If payable is false, the model is told not to attempt the payment — fundingHint says exactly what to fix.

The rails it teaches: onchain-proof, exact, upto

Section titled “The rails it teaches: onchain-proof, exact, upto”

A 402 may offer up to three settlement rails. The model doesn’t pick one per payment — the client does, automatically — but the guide tells it what each rail costs and how to budget for it:

RailWhat it does
onchain-proofPipRail’s default. The buyer broadcasts the payment itself and pays the network gas (the native coin — ETH/SOL/…). Works on every chain.
exactThe ratified x402 rail (opt-in). The buyer only signs → the server (or a facilitator it chose) broadcasts, so the buyer pays zero gas and needs only the token, no native coin. EVM/Solana/Algorand/Aptos/NEAR — see Gasless payments.
uptoThe metered/variable x402 rail (opt-in, EVM-Permit2 only). The buyer signs a MAX; the server meters real usage and settles the actual max. The model budgets against the MAX — see Paying the upto rail.

The exact and upto schemes are operator opt-in (PIPRAIL_SCHEMES=onchain-proof,exact,upto). The model can’t enable them itself, but it can report a 402 that needs one — surfaced as UNSUPPORTED_SCHEME (see below).

Reading a refusal — never crash, never double-spend

Section titled “Reading a refusal — never crash, never double-spend”

The guide drills the single most important agent behaviour: a failed pay returns a structured object, never a thrown error. The model branches on code (always reliable) and on reasonCode when declined is true.

{ "ok": false, "code": "...", "reason": "...", "explain": "...", "ref": "...", "reasonCode": "...", "declined": true }

The guide enumerates how the model should respond to each case:

code / reasonCodeWhat the model is told to do
declined + SESSION_EXPIREDTerminal. Time budget over. Stop — retry no payment this process.
declined + APPROVALA human or hook declined. Terminal for this pay; do not auto-retry.
declined + OUTSIDE_WINDOWRolling rate-limit exhausted. Wait for it to free, then retry — don’t raise the amount.
declined + POLICY / BUDGETA spend cap or allowlist refused it. Pick a cheaper/allowed resource.
INSUFFICIENT_FUNDSTop up the wallet (token and/or native gas), then retry.
PAYMENT_TIMEOUT / MAX_RETRIES_EXCEEDED / CONFIRMATION_TIMEOUTThe payment may already be on-chain — never re-pay (it would double-spend).
NO_COMPATIBLE_ACCEPT / UNSUPPORTED_SCHEMENot payable on this chain/scheme; explain says which.

The guide points the model at piprail_budget to self-check before paying: it reports the per-(network, asset) budget remaining, the cross-token grand total remaining per denomination, the payment-count leash, the session time envelope, your spend so far, and the configured policy read back. Read-only — it moves no funds. See the agent tools for the full tool surface.

The contract spells out the two ways an agent runs, so the model knows whether the policy is the consent or whether a human is in the loop:

  • Mode A (headless, default): the agent runs free inside a pre-set budget + time envelope. The policy is the consent — there is no per-payment prompt. piprail_budget shows what’s left.
  • Mode B (supervised): the host may ask a human to approve each payment. A decline/cancel/timeout comes back as declined: true with reasonCode: 'APPROVAL' — the model is told not to retry it as if it were a transient error.

See Modes for how the MCP server selects A or B.

The guide closes with the caps and persistence facts the model must not get wrong:

  • Per-payment + per-(network, asset) caps always apply. On top of them an optional cross-token grand total per denomination (maxTotalPerDenom, e.g. “$20 across every USD stablecoin + chain”) sums tokens declared as one unit, each 1:1 — not a price oracle, and it never prices a volatile native coin. Payment-count caps (maxPayments / per-window) also span every chain + token. See Total budget.
  • The time envelope lives in-memory for this process and resets on restart. The money + count totals also reset on restart unless a durable spend store is configured — then they resume. See Persistence.
  • A refusal arrives as declined: true with a reasonCodeBUDGET covers the lifetime, denom, and count caps; OUTSIDE_WINDOW covers both the rolling money and rolling count windows.

When you run @piprail/mcp with the guide enabled (default on), the guide string is exposed two ways — as a prompt and a resource — alongside a live-budget resource, so any MCP client can pull them into context:

SurfaceIdentifier
Promptpiprail_agent_guide
Resourcepiprail://guide (text/markdown)
Resourcepiprail://budget (application/json) — also gated behind the guide/PIPRAIL_GUIDE flag, serving the live spend budget.

A headless, non-MCP agent doesn’t need either — import PIPRAIL_AGENT_GUIDE directly and prepend it to the system prompt.