The agent guide
Introduction
Section titled “Introduction”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:
| Step | Tool | What it does |
|---|---|---|
| 1. Price it | piprail_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. Pay | piprail_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:
| Rail | What it does |
|---|---|
onchain-proof | PipRail’s default. The buyer broadcasts the payment itself and pays the network gas (the native coin — ETH/SOL/…). Works on every chain. |
exact | The 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. |
upto | The 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 / reasonCode | What the model is told to do |
|---|---|
declined + SESSION_EXPIRED | Terminal. Time budget over. Stop — retry no payment this process. |
declined + APPROVAL | A human or hook declined. Terminal for this pay; do not auto-retry. |
declined + OUTSIDE_WINDOW | Rolling rate-limit exhausted. Wait for it to free, then retry — don’t raise the amount. |
declined + POLICY / BUDGET | A spend cap or allowlist refused it. Pick a cheaper/allowed resource. |
INSUFFICIENT_FUNDS | Top up the wallet (token and/or native gas), then retry. |
PAYMENT_TIMEOUT / MAX_RETRIES_EXCEEDED / CONFIRMATION_TIMEOUT | The payment may already be on-chain — never re-pay (it would double-spend). |
NO_COMPATIBLE_ACCEPT / UNSUPPORTED_SCHEME | Not payable on this chain/scheme; explain says which. |
Knowing its leash — piprail_budget
Section titled “Knowing its leash — piprail_budget”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.
Two modes
Section titled “Two modes”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_budgetshows what’s left. - Mode B (supervised): the host may ask a human to approve each payment. A
decline/cancel/timeout comes back as
declined: truewithreasonCode: '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.
Hard facts it pins
Section titled “Hard facts it pins”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: truewith areasonCode—BUDGETcovers the lifetime, denom, and count caps;OUTSIDE_WINDOWcovers both the rolling money and rolling count windows.
How the MCP server serves it
Section titled “How the MCP server serves it”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:
| Surface | Identifier |
|---|---|
| Prompt | piprail_agent_guide |
| Resource | piprail://guide (text/markdown) |
| Resource | piprail://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.