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, 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.
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 how much
budget and time are left per (network, asset), plus spend so far. Read-only — it moves no
funds. See the 7 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 two facts the model must not get wrong:
- Spend caps are per
(network, asset). There is no single cross-token dollar cap — budgets aren’t summed across tokens, because there’s no price oracle. - Spend totals and the time envelope live in-memory for this process — they reset on restart. It’s a convenience, not a durable ledger.
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 same
string is exposed two ways, so any MCP client can pull it into context:
| Surface | Identifier |
|---|---|
| Prompt | piprail_agent_guide |
| Resource | piprail://guide (text/markdown) |
A headless, non-MCP agent doesn’t need either — import PIPRAIL_AGENT_GUIDE directly and prepend
it to the system prompt.