Skip to main content
Glama

Rein

The control plane for AI agent payments. Rein sets the rules, watches every payment, and scores every counterparty — so agents can transact at machine speed without machine-speed losses.

Rein is developer tooling and middleware for the agentic payments economy (the x402 / ERC-8004 stack). It is non-custodial: Rein governs an agent's authority to spend, never the funds themselves.

Status: v0.3 — all three phases have shipped their first cut, and a hosted engine runs an invited beta. Advisory SDK-mode + full observability, end to end — fully offline on mock rails, and live on real x402 rails on Base Sepolia (EIP-3009 USDC settled by the hosted x402.org facilitator — on both sides: the guarded agent and a @reinconsole/gate-monetized vendor). The session-key signer tier — the GA enforcement architecture, where the wallet key leaves the agent entirely — ships as @reinconsole/signer. The supply side ships as @reinconsole/gate, vendor monetization middleware (Phase 2). The stack is durable: @reinconsole/store persists the engine (agents, policies, spend, the signed decision chain), the reputation evidence, gate receipts + replay slots, and signer sessions across restarts. And @reinconsole/graph (Phase 3) turns the receipts both sides produce into explainable reputation scores that feed back into enforcement: vendorReputationLt policies on the agent side, payer screening at the vendor's door. Identity is on-chain: @reinconsole/erc8004 keys reputation by ratified ERC-8004 registrations — verified live against the real Base Sepolia Identity Registry.

Product phases

Phase

Name

What it does

1

Guard

Policy enforcement + observability for agent payments (demand side)

2

Gate

x402 monetization middleware for API vendors (supply side)

3

Graph

Reputation scoring over agents and vendors (the data moat)

Related MCP server: Budget Governor

What's in v0.3

A complete demand-side Guard loop, runnable two ways: fully offline on mock rails (no accounts, no Docker, no chain), or live on Base Sepolia over the real x402 stack:

  • @reinconsole/sdk — wrap your agent's fetch once; every x402 paywall is policy-checked, receipted, and observable before a cent moves.

  • @reinconsole/mcp — the guard as an MCP server. Point any MCP-capable harness (Claude Code, Codex-class agents) at it and its agent gets a spend-governed fetch plus read-only introspection of the rules it is under — no code changes. The authority boundary is the design: the client on the other end of the pipe is the agent, so no tool can widen the agent's own authority — no approving its own escalation, no editing a policy, no unfreezing itself, no minting a key. A test asserts the whole tool surface rather than a sample of it.

  • @reinconsole/policy-engine — a sandboxed declarative rule engine (deny > escalate > allow > default) behind a Fastify API. Every decision is ed25519-signed and sha256 hash-chained into a tamper-evident audit log.

  • @reinconsole/core — the canonical zod schemas: the single source of truth for DB rows, API payloads, and SDK types, with float-free decimal money math.

  • @reinconsole/mock-rails — a simulated payment world (x402 facilitator + on-chain ledger + indexer) that reconciles spend and flags shadow spend: payments that bypassed the guard.

  • @reinconsole/x402-rails — the real-world rails: an EIP-3009 payer (gasless for the agent — the facilitator submits the tx), a client for the hosted x402.org facilitator, a strict x402-v1 vendor, and an on-chain indexer that reconciles USDC transfers back to intents via the authorization nonce — and flags everything else as shadow spend.

  • @reinconsole/console — a live "mission control" web UI over the whole stack: decisions, vendor-gate quotes/receipts/refusals, signer releases, settlements, and shadow spends streaming in real time; kill switch, vendor revenue panel, the live reputation scoreboard (scores, confidence, and the evidence behind them), and the tamper-evident audit chain.

  • @reinconsole/signer — the custody tier. Wallet keys live in the signer, agents get capped, expiring session tokens, and every EIP-3009 signature is released only against an engine-signed allow voucher for the exact transfer being signed — verified offline, usable once. Where SDK mode detects bypass, this tier prevents it.

  • @reinconsole/gate — the supply side (Phase 2). Middleware a vendor drops in front of any Node HTTP API to monetize it over x402: price routes by glob, quote strict v1 402s, cross-check + screen + replay-protect incoming payments, settle through pluggable rails (mock or the real facilitator), and keep vendor-side receipts and revenue stats. Verified live on Base Sepolia against the hosted facilitator.

  • @reinconsole/store — persistence. Postgres-backed stores (embedded PGlite — no Docker, no daemon, upgradeable to hosted Postgres) behind every service's store ports: agents, the kill switch, policies in evaluation order, rolling spend history, the ed25519 signing key, and the hash-chained decision log all survive restarts — the chain resumes from the last persisted hash and verifies end to end across the seam. The reputation graph's evidence ledger persists here too (scores are never stored — they recompute byte-identically from rehydrated evidence), including in-flight intent correlations, so a settlement that lands after a restart is still attributed. So do the gate's receipts and replay slots (a pre-kill payment is refused as a replay post-restart) and the signer's session grants — spend against caps, revocations, and burned vouchers; wallet private keys deliberately never (KMS territory).

  • @reinconsole/graph — reputation (Phase 3). One graph observes every bus the stack already publishes — engine decisions, indexer settlements, gate receipts and refusals, signer events — and scores every vendor and payer it has evidence on: five explainable 0–100 components plus first-class confidence, recomputed from raw evidence on demand. Scores feed back into enforcement on both sides: syncVendors(engine.spend) makes vendorReputationLt policies fire, payerCheck(graph) plugs into gate screening. graph.link() merges identities across id spaces (an agent's engine ULID and its paying wallet, a vendor's host and its payTo address — the ERC-8004 story) so one party carries one history: an agent's engine-side sins follow its wallet to every gate's door.

  • @reinconsole/erc8004 — the on-chain identity source. Reads identity facts from the ratified ERC-8004 Identity Registry (an ERC-721; singleton deployments, Base Sepolia included) and turns them into link facts for the graph: a registered agent's reputation keys by its on-chain identity (eip155:{chainId}:{registry}/{tokenId}) with the local id and every wallet — ownerOf, the EIP-712-verified agentWallet — folded in as aliases; vendors stay host-keyed. Ships the write path too (registers agents on the real Base Sepolia registry) and an in-memory registry twin for offline work.

1111 tests passing (plus 17 live network tests gated behind RUN_LIVE=1). The mock end-to-end demo runs 5 scenarios in under 500ms; the gate demo runs the full two-sided loop over real local HTTP; the graph demo closes the reputation loop on both sides; the Sepolia demos settle real USDC — and the identity demo registers a real agent on the Base Sepolia ERC-8004 registry.

Install

The twelve library packages are published on npm under the @reinconsole scope (MIT, Node ≥22), with provenance, by the release workflow. latest is 0.5.0 — npx @reinconsole/init (a sandbox, a payment and a refusal in one command), expiring API keys, and the engine on Postgres. No breaking changes from 0.3.0; 0.3.0 broke from 0.2.0, so read CHANGELOG.md before upgrading from that:

npx @reinconsole/init          # a sandbox on the hosted engine, one paid call, one refused — no account
npx -y @reinconsole/mcp          # the guard as an MCP server — a governed fetch for any MCP harness
npm install @reinconsole/sdk     # agent-side guard — wrap your fetch
npm install @reinconsole/gate    # vendor-side x402 monetization middleware
npm install @reinconsole/graph   # explainable reputation scoring

Package

What it's for

@reinconsole/core

Canonical zod schemas — the single source of truth

@reinconsole/sdk

Agent-side guard; wraps the x402 client

@reinconsole/mcp

The guard as an MCP server: a spend-governed fetch for any MCP harness

@reinconsole/init

npx @reinconsole/init: a sandbox, a paid call and a refused one, no account

@reinconsole/policy-engine

Declarative rule engine + signed, hash-chained audit log

@reinconsole/gate

Vendor-side x402 monetization middleware

@reinconsole/graph

Reputation: evidence off every bus, explainable scores

@reinconsole/x402-rails

Real rails: EIP-3009 payer + x402.org facilitator client + indexer

@reinconsole/mock-rails

Offline x402 world: facilitator + ledger + indexer

@reinconsole/erc8004

On-chain identity: ERC-8004 registry reads/writes → link facts

@reinconsole/signer

Session-key custody: voucher-gated EIP-3009 signing, caps, kill switch

@reinconsole/store

Persistence: PGlite-backed stores; engine, graph, gate and signer state survive restarts

The custody tier (@reinconsole/signer) and persistence layer (@reinconsole/store) passed their security review and ship on npm as of 0.2.0.

Quickstart

npm install -g pnpm  # if you don't have pnpm — `corepack enable` works too, but needs an admin shell on Windows
pnpm install
pnpm build           # ~2 min
pnpm test            # 1111 tests, fully offline, ~5 min

# Watch the whole thing work — budgets, tx caps, kill switch, shadow-spend detection:
node apps/demo/dist/index.js

# Then watch the custody tier refuse every rogue path a stolen agent could try:
node apps/demo/dist/signer.js

# Then flip to the vendor side: price routes, screen payers, count revenue:
node apps/demo/dist/gate.js

# Then close the loop: reputation scores that change what both sides enforce:
node apps/demo/dist/graph.js

Console — live mission control

A real-time web UI for the whole stack at once: the real policy engine (over HTTP), a real @reinconsole/gate fronting the world's vendor API, the session-key signer holding a custodied wallet, the reputation graph observing every bus, and the mock rails standing in for the chain. Every bus — engine, indexer, gate, signer — is merged and pushed to the browser over Server-Sent Events.

pnpm --filter @reinconsole/console dev      # http://localhost:5173

What you see:

  • Live activity feed — decisions (allow / deny / escalate), x402 quotes, vendor receipts, payments turned away at the gate, EIP-3009 signatures released or refused by the signer, settlements, and shadow spends — streaming in as they happen.

  • Vendor gate — what the world's gated API is earning: revenue headline, per-route and per-payer breakdowns from real gate receipts.

  • Reputation — the live scoreboard from @reinconsole/graph: every vendor and payer the world has evidence on, with confidence, click-to-expand explanations (five components + the raw counts behind them), "→ engine" on vendor scores synced into policy, and "barred" on wallets the gate turns away. The graph re-syncs after every burst of evidence, so watch the world's own vendor cross the confidence floor as scenario runs accumulate.

  • Agents + kill switch — both custody tiers side by side (sdk and session-key), per-agent session spend, and a freeze/unfreeze toggle; hit Ping on a frozen agent and watch the call get denied.

  • Policies — the active rules in plain language.

  • Audit chain — the ed25519-signed, sha256-linked decision log with a live integrity check.

  • Shadow spends — unreconciled, guard-bypassing payments, flagged in red.

Click Run scenario to play the full two-sided story, paced so you can watch it unfold: a fresh SDK-tier agent makes four paid calls (quote → allow → vendor receipt → settle), trips the budget cap and the tx cap, then bypasses the guard (shadow spend); an unpaid crawler gets quoted; a replayed payment and a denylisted mule get turned away at the gate; then a session-key agent — wallet held by the signer — makes voucher-gated EIP-3009 purchases, a stolen voucher is replayed straight at the signer and refused, and the engine allows a payment the session cap still refuses: defense in depth, live. The finale closes the reputation loop on both sides: a procurement agent is denied at a sketchy vendor by the reputation-gate policy rule, served at a reputable one on the same policy, and a wallet that replayed payments at other vendors' gates two weeks ago presents a fresh, valid payment — and is turned away on reputation alone.

The session-key payments in this world are real EIP-3009 signatures, verified cryptographically (signature recovery against the quoted USDC contract domain) before the gate settles them — a forged or tampered authorization genuinely fails. For a production-style serve (built UI + API on one port): pnpm --filter @reinconsole/console build && pnpm --filter @reinconsole/console start.

Set REIN_CONSOLE_DATA_DIR to run the console world on @reinconsole/store: agents, policies, the kill switch, the decision chain, rolling budgets, and the reputation scoreboard all survive a restart (the boot seed runs once per data directory; the feed is telemetry and starts fresh). Kill the server mid-story, start it again, and run the scenario — the new agents pick up numbered names where the old ones left off, the chain extends the pre-restart hashes, and the door still turns away the offender on evidence recorded before the kill.

Run the policy engine standalone:

# PowerShell
$env:PORT="8787"; node services/policy-engine/dist/server.js
# bash
PORT=8787 node services/policy-engine/dist/server.js

With no REIN_ENGINE_API_KEY the engine binds 127.0.0.1 only, and refuses to start on a public interface — an unauthenticated engine cannot be exposed by accident. To expose it, set a key (REIN_ENGINE_API_KEY=rk_... plus HOST=0.0.0.0) and pass the same secret to the SDK as apiKey; REIN_ENGINE_AUTH=off is the deliberate override. The standalone console follows the same rule: without REIN_CONSOLE_API_KEY it serves the dashboard read-only on a public bind (REIN_CONSOLE_HOST=127.0.0.1 for local use with the controls live).

Persistence: an engine that survives restarts

The in-memory engine is great for demos; @reinconsole/store makes it durable. It implements the engine's store ports on embedded Postgres (PGlite — real Postgres compiled to WASM, running in-process against a data directory; no Docker, no daemon, and the SQL carries straight over to hosted Postgres later). Writes are awaited to disk before the engine acts on them; reads stay synchronous from a hydrated working set.

# The same HTTP API as the policy engine, but durable:
# PowerShell
$env:REIN_DATA_DIR=".rein-data"; node services/store/dist/server.js
# bash
REIN_DATA_DIR=.rein-data node services/store/dist/server.js

Kill it and start it again: agents, the kill switch, policies (in evaluation order), rolling budgets ("$0.60 of the daily $1.00 already spent — before the restart"), and the decision log all come back. The ed25519 signing key is persisted too, so the hash chain continues across restarts — the first post-restart decision links to the last pre-restart hash, and verifyDecisionChain validates the whole history under one key, no seam.

The reputation graph gets the same treatment — the same store persists its evidence ledger (and the in-flight intent correlation map, so a settlement that lands after a restart is still attributed to the agent and vendor behind it). Scores are never stored: they recompute from the rehydrated evidence, byte-identical under the same clock.

# The graph HTTP API, durable (use a DIFFERENT data dir than the engine —
# two processes can't share one PGlite directory):
# PowerShell
$env:REIN_GRAPH_DATA_DIR=".rein-graph-data"; node services/store/dist/graph-server.js
# bash
REIN_GRAPH_DATA_DIR=.rein-graph-data node services/store/dist/graph-server.js

The gate and the signer ride the same store: vendor receipts, revenue stats, and burned replay slots resume (a payment settled before a kill is refused as a replay after the restart), and session grants — token hashes, per-session spend against the cap, revocations, and the burned-voucher set — survive a signer restart, so an agent holding a token keeps paying while a revoked one stays dead. Wallet private keys are deliberately never persisted (custody keys at rest belong in a KMS/HSM); deployments re-register wallets at boot.

Composing it in code is one line per side — in a single process, one store backs all four:

import { PolicyEngine } from '@reinconsole/policy-engine';
import { ReputationGraph } from '@reinconsole/graph';
import { createGate } from '@reinconsole/gate';
import { SessionSigner } from '@reinconsole/signer';
import { openReinStore } from '@reinconsole/store';

const store = await openReinStore({ dir: '.rein-data' });
const engine = new PolicyEngine(store);
const graph = new ReputationGraph({ ledger: store.ledger, intents: store.intents });
const gate = createGate({ /* routes, rails, ... */ store: store.gate });
const signer = new SessionSigner({ enginePublicKeyPem: engine.publicKeyPem, store: store.sessions });

The hosted engine (invited beta)

Rein runs a hosted policy engine at https://engine.reinconsole.com with the public console at app.reinconsole.com reading from it, and a reference vendor at vendor.reinconsole.com that sells two testnet routes through @reinconsole/gate (/testnet/v1/ping at $0.001, /testnet/v1/scores/vendor/:host at $0.005). Its Base mainnet lane — /v1/ping at $0.01, /v1/scores/vendor/:host at $0.02 — is opt-in and answers 404 until it is armed. Access is by invitation while the beta is small: an invitee gets an org, an agent, a starter policy and an org-scoped API key narrowed to that agent, which is the whole blast radius of the secret.

{
  "mcpServers": {
    "rein": {
      "command": "npx",
      "args": ["-y", "@reinconsole/mcp"],
      "env": {
        "REIN_ENGINE_URL": "https://engine.reinconsole.com",
        "REIN_ENGINE_API_KEY": "rk_...",
        "REIN_AGENT_ID": "agt_01J...",
        "REIN_NETWORK_PROFILE": "testnet"
      }
    }
  }
}

Two things to know before the first call:

  • The network is a profile, not a policy. The engine maps Base and Base Sepolia onto the same chain, so policy alone cannot keep a testnet agent off mainnet. REIN_NETWORK_PROFILE (default testnet) is enforced in the guard and again in the payer: a testnet-profile install refuses a mainnet 402 before a signature exists, and a mainnet vendor is invisible to it rather than merely denied.

  • Advisory first, then funded. Without REIN_PAYER_PRIVATE_KEY every paywall answers ALLOWED_BUT_UNPAID: the decision is on the chain and on the console, and nothing moved. Fund a wallet with free testnet USDC from faucet.circle.com (Base Sepolia), add the key, and the same call settles and shows up as a receipt.

Real rails: Base and Base Sepolia

Every network Rein pays on is a NetworkProfile (@reinconsole/x402-rails): chain id, USDC contract, facilitator and the EIP-712 domain, verified live against the real contract rather than asserted. Testnet settles through the hosted x402.org facilitator, which lists only base-sepolia and charges nothing. The MAINNET profile (base) defaults to Coinbase's CDP facilitator, which needs CDP API credentials (createProfileFacilitator, cdpAuthHeaders). It is not the only way to settle on mainnet: the facilitator is just a URL, and the reference vendor's mainnet lane settles keyless through PayAI when no CDP credentials are set. A keyless facilitator bills the seller its gas plus a margin, so price mainnet routes well above a settlement's cost; the reference vendor's $0.01 floor is that reasoning.

The same guard loop on a real chain — a guarded $0.01 USDC payment settled on-chain by the hosted x402.org facilitator, then a rogue payment that bypasses the guard and gets caught:

pnpm --filter @reinconsole/demo demo:sepolia

The first run generates an agent wallet into .env and prints faucet instructions — fund it with free testnet USDC at faucet.circle.com (no ETH needed; the facilitator pays gas), then run again. A full run spends $0.02 of testnet USDC and ends with two BaseScan links:

  • payment.settled — the guarded payment. The payer derives the EIP-3009 authorization nonce as keccak256(intent.id), USDC emits it back in AuthorizationUsed on settlement, and the on-chain indexer reconciles the transfer to the exact intent the policy engine allowed — an on-chain memo, with no fuzzy matching.

  • shadow.spend — the rogue payment. The facilitator is not Rein-privileged, so it settles anyway — and the indexer flags the unreconciled spend.

From a real run: the settled payment · the shadow spend

And the vendor side on the same real rails — a @reinconsole/gate-priced Node API settling real USDC through the hosted facilitator while the paying agent stays under guard. One $0.01 payment, quoted, signed (EIP-3009), settled on-chain, receipted on both sides, reconciled by the on-chain indexer via the nonce memo — then the same payment replayed and burned at the door before the facilitator ever sees it:

pnpm --filter @reinconsole/demo demo:sepolia-gate   # reuses the demo:sepolia wallet

From a real run: the gate-settled payment

The live test suite (RUN_LIVE=1 pnpm --filter @reinconsole/x402-rails test) exercises the same path. Behind a TLS-intercepting proxy or antivirus, point Node at your local root CA first (NODE_EXTRA_CA_CERTS) — see env.example.

The signer tier: keys the agent never holds

SDK mode is honest about its limit: an agent that holds its own key can bypass the guard, and Rein catches it (shadow spend). @reinconsole/signer removes the limit by removing the key. The agent process gets a session token — capped, expiring, revocable — and the wallet lives in the signer, which releases an EIP-3009 signature only when every gate passes:

  1. A valid voucher. The engine binds each decision to the exact intent it judged (intentHash over amount, recipient, asset, chain), ed25519-signs it, and chains it into the audit log. The signer verifies the pair fully offline — a rogue agent can recompute every hash, but it cannot sign as the engine.

  2. An exact match. The 402 requirement being signed must equal what the engine judged: recipient, amount, asset, network. A real $0.01 voucher cannot authorize a $5.00 transfer.

  3. Once. One decision releases one signature; replays are refused — including two concurrent requests racing the same voucher.

  4. Within the session. Per-payment and cumulative caps, expiry, and revocation are enforced at the signing boundary, under whatever policy says.

Session lifetime is capped, not merely configurable

A session token is delegated authority over real money, so how long one can live is a protocol invariant rather than a setting: ten days, maximum (MAX_SESSION_LIFETIME_SECONDS, tunable down via maxSessionLifetimeSeconds, never off — a non-positive value is a construction error). A stolen token therefore stops working on its own, whether or not anyone remembers to revoke it.

Asking for more is refused at creation, never silently shortened — a caller that thinks it holds a 30-day grant would schedule its rotation on the wrong clock and meet the cap mid-payment as an unexplained session_expired. And because a grant's real expiry is derived (min(expiresAt, createdAt + cap)) rather than stored, the cap also binds records a durable store hydrates from an older deployment or a looser config — no migration, nothing to keep in sync. /health advertises the cap; every session the API returns carries the effectiveExpiresAt it will actually die at.

Every release and refusal is emitted on the event bus (signature.released / signature.refused). The kill switch stops being advisory: freeze the agent and there is no allow, no signature, no payment.

pnpm --filter @reinconsole/demo demo:signer   # seven scenarios, fully offline, every signature verified

Run it as a service (buildSignerServer) with the SDK's createRemoteSessionPayer, or in-process with sessionPayerFor. There is deliberately no HTTP endpoint that accepts a private key.

The service's session-admin routes require a bearer secret, and the default is no server at all:

buildSignerServer(signer, { adminToken: process.env.REIN_SIGNER_ADMIN_TOKEN });

POST /v1/sessions mints a grant with whatever cap it is asked for, against a wallet this process holds the key to — so an open admin surface is a wallet drain for anyone who can reach the port. Omitting both adminToken and the explicit adminAuth: 'off' opt-out is a construction error, thrown at build time rather than discovered in a log. POST /v1/sign stays open by design: the session token in the body is that route's credential, scoped and capped and revocable, which is the whole point of the tier.

That credential can also be a scoped API key instead of one static secret — read to list grants, admin to mint, revoke, or delete one, so a dashboard key can never create a session:

buildSignerServer(signer, { auth: new ApiKeyAuth({ store: reinStore.apiKeys }) });

Both forms may be passed together while a deployment rolls over. Back the key store with PgApiKeyStore (it is reinStore.apiKeys) rather than the in-memory default: keys are authority, and an in-memory store means a key you issued stops working at the next restart and a key you revoked comes back alive.

Gate: the vendor side of the wire

Everything above governs the agent spending. @reinconsole/gate is Phase 2 — the same loop from the vendor's seat. Price your routes once, and every x402 payment into your API is quoted, cross-checked, screened, settled, and receipted before your handler runs:

import { createGate, gateMiddleware, facilitatorClientRails } from '@reinconsole/gate';

const gate = createGate({
  routes: [
    { path: '/api/answer', price: '0.05', description: 'one research answer' },
    { path: '/api/premium/*', method: 'POST', price: '0.25' },
  ],
  rails: facilitatorClientRails(facilitator), // or mockFacilitatorRails(...) offline
  payTo: '0xYourTreasury…',
  network: 'base-sepolia',
  asset: USDC_ADDRESS,
  screen: { denyPayers: ['0xKnownMule…'] },
});

app.use(gateMiddleware(gate)); // Express, or wrap any node:http handler

What the gate does that a bare 402 snippet doesn't:

  • Quote consistency. A presented payment must match the gate's own quote — scheme, network, amount, recipient — before any facilitator round-trip. Underpayment is refused at the door.

  • Payer screening. Allow/deny lists on the paying wallet — plus a dynamic screen.check hook (reputation plugs in here) — checked before verify/settle, so a blocked payer costs you nothing.

  • Replay protection. Each payment settles once; the slot is burned before the async legs, so two concurrent copies can't both pass (on-chain nonce burning is a luxury the mock chain doesn't have — the gate doesn't care).

  • Receipts + revenue. Every settlement becomes a GateReceipt (grc_ ULID); gate.stats() aggregates revenue by asset, route, and payer; gate.quoted / gate.settled / gate.refused events stream on the bus.

  • Pluggable rails. The same gate runs against the mock facilitator (offline tests/demos) or the real hosted x402.org facilitator client — the rails are a two-method structural seam.

pnpm --filter @reinconsole/demo demo:gate          # six scenarios, offline: guarded agent pays a gated vendor over real local HTTP
pnpm --filter @reinconsole/demo demo:sepolia-gate  # the same gate on REAL rails: settles testnet USDC via the hosted facilitator

Graph: reputation closes the loop

Guard receipts say what agents tried to spend; gate receipts say what vendors actually earned. @reinconsole/graph (Phase 3) is the consumer of both — and the feedback path that turns observability into enforcement:

import { ReputationGraph, payerCheck } from '@reinconsole/graph';

const graph = new ReputationGraph().observe(engine).observe(indexer).observe(gate);

// Agent side: pushed scores make `vendorReputationLt` policies fire.
await graph.syncVendors(engine.spend);

// Vendor side: low-reputation wallets are turned away at the door.
createGate({ screen: { check: payerCheck(graph, { denyBelow: 40 }) }, ... });
  • Evidence, not vibes. The graph accumulates per-subject history straight off the event buses: settlements, refusals (replays weighted heaviest), shadow spends, settled-money edges between counterparties, and manual dispute/endorsement reports. Scores are recomputed from raw evidence on demand — GET /v1/scores/vendor/api.example.com returns the score and everything behind it.

  • Five components + confidence. Settlement reliability, dispute hygiene, volume, longevity, and one-hop counterparty quality (who you settle with marks you), blended 0–100. Confidence is first-class: a thin or brand-new history yields low confidence, not a fake number.

  • Unknown is not bad. The evaluator never fires vendorReputationLt without data, the sync withholds low-confidence scores, and payerCheck passes wallets it knows nothing about. A newcomer is served; a confidently bad actor is refused.

  • The network effect. Evidence from one vendor's gate protects every other gate sharing the graph — a mule that replayed payments elsewhere is refused here, before any facilitator round-trip.

  • One identity, one history. graph.link(canonical, alias) merges subjects across id spaces — evidence recorded under either id folds together (counters sum, settled-money edges re-key on both ends), all future evidence and lookups resolve to the canonical identity, and merges persist on the durable store. Links are derived facts (your agent registry knows its wallets; ERC-8004 ids are the on-chain source): re-assert them at boot, idempotently. The console world does exactly this — one scoreboard row per party, and payerCheck refuses a wallet for what its agent did on the engine side.

pnpm --filter @reinconsole/demo demo:graph   # five scenarios, offline: both feedback loops close live

ERC-8004: identity from the chain

@reinconsole/erc8004 makes the registry the source of link facts instead of local configuration. A registered agent becomes ERC-8004-canonical: its reputation row keys by eip155:{chainId}:{registry}/{tokenId}, and the engine ULID plus every wallet (ownerOf, the verified agentWallet) fold in as aliases — so two deployments claiming the same registration merge into one history, and key rotation never splits a score. Vendors stay host-canonical (hosts are what intents carry and vendorReputationLt matches); their identities and treasuries fold into the host row. Unregistered agents keep today's local linking — the fallback is byte-compatible.

pnpm --filter @reinconsole/demo demo:erc8004        # five scenarios, offline: one on-chain identity, one reputation
pnpm --filter @reinconsole/demo demo:sepolia-8004   # REAL registration on the Base Sepolia registry (one-time gas; re-runs read-only)

Run it as a service (buildGraphServer): remote producers POST /v1/events, anyone reads GET /v1/scores — or run the durable variant (services/store/dist/graph-server.js), where the evidence survives restarts (see Persistence). Or watch it live: the console world runs a graph over all four buses, re-syncs it into the engine after every burst of evidence, and renders the scoreboard with click-to-expand explanations.

The SDK one-liner

Wrap your agent's fetch, point it at a policy engine, and every x402 payment is governed:

import { createGuard } from '@reinconsole/sdk';

const guard = createGuard({
  engineUrl: 'http://localhost:8787',
  agentId: 'agt_01J...', // registered with the engine
  onReceipt: (r) => console.log(r.outcome, r.amount, r.vendorHost),
});

const fetch = guard.wrap(); // a drop-in fetch

// A 402 from the vendor is intercepted, the intent is evaluated, and a
// blocked payment never reaches the network. Allowed payments flow through
// and the settlement is captured back onto the receipt.
const res = await fetch('https://api.vendor.example/v1/search?q=...');

The guard layers underneath any x402 payment library: the first unpaid request surfaces the 402, the guard evaluates it and either blocks it (so the payment layer never sees the paywall) or releases it upward — and the X-PAYMENT retry flows back through to attach the settlement. Or pass a payer and the guard settles directly.

Framework examples, each runnable against a free sandbox from npx @reinconsole/init: Vercel AI SDK (a governed tool()) and Coinbase AgentKit (an action provider that pays with the AgentKit wallet).

The policy engine API

POST /v1/evaluate is the hot path (sub-millisecond, signed + chained). Also: register agents, manage policies, flip the kill switch, and read the audit log.

Method

Route

Purpose

GET

/health

Liveness + the engine's signing public key

POST

/v1/agents

Register an agent (returns a agt_ ULID)

GET

/v1/agents

List agents

POST

/v1/agents/:id/freeze

Kill switch on (deny everything)

POST

/v1/agents/:id/unfreeze

Kill switch off

POST

/v1/policies

Add a policy

GET

/v1/policies

List policies

POST

/v1/evaluate

Evaluate a payment intent → signed decision

GET

/v1/decisions

The hash-chained decision log

GET

/v1/chain/verify

The engine's verdict on its whole chain

GET

/v1/agents/:id/breakers

Where the agent's behavioral breakers stand

POST

/v1/settlements

Report that an allowed payment landed

GET

/v1/reconciliation

Allowances with no settlement behind them

PUT

/v1/agents/:id/liveness

Expect this agent to be active every interval

POST

/v1/agents/:id/heartbeat

"I am alive, I just have nothing to buy"

GET

/v1/liveness

Where every watched agent stands (worst first)

A policy is declarative — for example, a $0.50 per-transaction cap plus a rolling $0.04/hour budget, defaulting to allow:

{
  "policyId": "research-policy",
  "appliesTo": { "agents": ["agt_01J..."] },
  "rules": [
    { "id": "tx-cap", "deny": { "amountGt": "0.50" } },
    { "id": "hour-budget", "deny": { "rollingSum": { "window": "1h", "gt": "0.04" } } }
  ],
  "default": "allow"
}

Breakers and task budgets

Rules ask about the payment in front of them. A breaker asks whether the agent's behavior has left the envelope it was given — and once it has, every subsequent intent escalates for a signed approval. It never denies on its own: a silent deny at the wrong moment strands a running job with no path forward and nobody told.

{
  "policyId": "research-policy",
  "breakers": [
    { "id": "velocity", "window": "1h", "txCount": 60, "valueCap": "5.00" }
  ],
  "rules": [
    { "id": "task-cap", "escalate": { "taskBudget": { "gt": "1.00" } } },
    { "id": "untagged", "deny": { "taskIdMissing": true } }
  ],
  "default": "allow"
}
  • Tripwires are prospective and ORed: the payment that would carry the window past 60 transactions or past $5.00 is the one that escalates, rather than the innocent one behind it. At least one tripwire is required.

  • A breaker sits at the escalate precedence level, so an explicit deny still wins and no allow rule can wave a tripped breaker past.

  • It resets two ways, which are the same mechanism — a counting floor: the window rolling forward, or a signed approval moving the floor to now. GET /v1/agents/:id/breakers (or client.breakerStates(agentId)) reports where each one stands.

  • taskBudget caps cumulative spend on one unit of work rather than one window: the research run meant to cost a dollar cannot quietly cost fifty, however slowly. The guard's withTask({ taskId }) carries the attribution. An intent with no task id never triggers a budget — requiring attribution is the separate, deliberate taskIdMissing rule, because otherwise every untagged probe payment would trip every task budget in the policy.

Reconciliation: allowed but never settled

An allow authorizes a payment; it does not make one. In between is a gap where a payment can quietly fail — a facilitator that never broadcast, a vendor that never confirmed, an agent that crashed mid-flight — and nothing in the stack notices on its own. The decision chain says "allowed", the rolling budget has already been charged, and the money simply never moved.

Rein closes that loop by joining the allowance ledger against settlement facts:

await client.reportSettlement({ intentId, txHash, source: 'indexer', confirmedAt: new Date() });
const report = await client.reconciliation({ window: '24h', graceMs: 60_000 });
// → { allowed, settled, inFlight, unsettled, unsettledValue, overspent, overspentValue, settlementsSeen, gaps: [...] }

The guard reports its own settlements automatically (fire-and-forget — a failed report can never affect a payment that already succeeded); an indexer or facilitator webhook is the stronger reporter, and reportSettlement: false hands the job over to it.

  • A gap has an age, not a boolean. Under graceMs a missing settlement is a payment in flight, which is the normal state of every payment for its first seconds.

  • The unsettled allowance keeps its charge against the budget. Refunding it would be a self-service reset: don't settle, and the envelope refills.

  • The same join runs the other way: a settlement reported with an amount above the amount allowed is an overspent row, listed first, with overspentValue summing the excess. Equal is right and less is inside the ceiling; only more is a breach.

  • settlementsSeen is the honesty valve. The engine watches no chain — it is told when payments land — so zero reports means nobody is looking, and the gaps say more about the wiring than about the payments. The console renders that case differently.

  • Reconciliation is observability, never authority: it cannot deny, cannot alter a decision, and a settlement report authorizes nothing.

Dead man: the agent that simply stopped

Every control above answers "should this payment happen?". This one answers the question nothing else in the stack asks. An agent that dies raises no intent, breaks no budget and trips no breaker — it disappears, and a control plane watching only for bad payments reports a perfectly clean month while the work quietly stops.

await client.watchLiveness(agentId, { interval: '15m', note: 'polls the vendor feed' });
await client.heartbeat(agentId);        // only for an agent with nothing to buy
const states = await client.liveness(); // → [{ status: 'missing', silentMs, ... }]
  • An expectation is declared, never inferred. Most agents are episodic, so silence is only evidence about one somebody said should be periodic; an unwatched agent has no liveness state, and a heartbeat for one is refused rather than swallowed.

  • Any intent is a sighting, including a denied one. An agent hammering a wall is alive — that is a different alarm, with a different remedy — so a spending agent needs no heartbeat at all.

  • Silence has an age: alive -> late (inside the grace) -> missing. An alarm that cries at every wobble is one an operator learns to ignore, which loses the next agent.

  • The alarm fires once per silence, not once per sweep, and the bookkeeping is durable — a restart does not re-announce a death that has already been read.

  • The engine never alarms about silence it did not witness. An engine that was down cannot tell a dead agent from its own outage, so a silence older than the process reads unknown until it has been up long enough to certify it.

  • Like reconciliation, it carries no authority: an alarm is news for a human, never an input to a decision. Alarms ride the same one-way channels as approvals — and there is nothing to acknowledge, because only the agent being seen again ends one.

Design principles

  1. Non-custodial. Rein governs authority to spend, not the funds.

  2. Fail closed. If the policy service is unreachable, payments above a configured floor are denied, not allowed. Ungovernable x402 offers fail closed too.

  3. Rail-agnostic core, x402-first integration. The policy/ledger domain model knows nothing about x402 specifically; x402 (Base, Solana) is the first adapter.

  4. Every decision is auditable. Each allow/deny produces a signed, hash-chained decision record linked to the eventual on-chain transaction.

  5. Honest about its tier. The facilitator — mock or the real hosted one — is not Rein-privileged: in SDK mode a rogue payment still settles, and the indexer flags it as shadow spend (verified live on Base Sepolia). The session-key signer tier closes that gap: the key the rogue would need no longer exists in the agent.

Repository layout

packages/
  boot/          @reinconsole/boot           — container boot: chown the data volume, drop root, before the DB opens     [internal]
  core/          @reinconsole/core           — canonical zod schemas (single source of truth)                            [published]
  sdk/           @reinconsole/sdk            — agent-side guard; wraps the x402 client                                   [published]
  gate/          @reinconsole/gate           — vendor-side x402 monetization middleware                                  [published]
services/
  policy-engine/ @reinconsole/policy-engine  — Fastify policy evaluation service + audit log                             [published]
  mock-rails/    @reinconsole/mock-rails     — mock x402 facilitator + ledger + indexer                                  [published]
  x402-rails/    @reinconsole/x402-rails     — real rails: EIP-3009 payer, x402.org facilitator client, on-chain indexer  [published]
  graph/         @reinconsole/graph          — reputation: evidence off every bus, explainable scores, policy+gate feed  [published]
  erc8004/       @reinconsole/erc8004        — on-chain identity: ERC-8004 registry reads/writes → link facts            [published]
  signer/        @reinconsole/signer         — session-key custody: voucher-gated EIP-3009 signing, caps, kill switch    [published]
  store/         @reinconsole/store          — persistence: PGlite-backed engine + graph stores; state survives restarts [published]
apps/
  demo/          @reinconsole/demo           — end-to-end demos: mock (5 scenarios) + real Base Sepolia (guard + gate) + signer tier + gate + graph
  console/       @reinconsole/console        — live web UI: real-time feed, kill switch, audit chain, shadow-spend alerts
examples/
  vercel-ai-sdk/                             — spend limits for an AI SDK agent: a tool() that asks Rein before it pays
  coinbase-agentkit/                         — spend limits for an AgentKit agent: an action provider, the wallet signs

Tech

  • Language: TypeScript end-to-end (Node 22 LTS), strict mode.

  • Monorepo: pnpm workspaces + Turborepo.

  • Schemas: zod, in @reinconsole/core, as the single source of truth for DB rows, API payloads, and SDK types.

  • Build/test: tsup (esm + cjs + d.ts), vitest.

  • Console: Vite + React + TypeScript, live updates over Server-Sent Events (no extra services to run).

  • Chain: viem on Base Sepolia — EIP-712/EIP-3009 signing, getLogs indexing, the hosted x402.org facilitator for settlement.

  • Dev mode: mock x402 flows + in-memory stores behind ports, with @reinconsole/store (embedded PGlite Postgres) when you want state to survive restarts. No accounts or Docker required to run locally; hosted Postgres + Timescale + Redis + NATS wire in later behind the same ports.

Security

Found something that could move money, mint authority, or bypass a policy decision? Report it privately through GitHub's advisory flow rather than a public issue. SECURITY.md has the scope, the response times, and the list of behavior that looks alarming but is deliberate — SDK mode is advisory and bypassable by design, and knowing that saves everyone a round trip.

License

MIT

Available Tools

5 tools
rein_escalationsPayments parked awaiting a human approvalA
Read-only

Payments by this agent that policy escalated rather than allowed or denied. Each is waiting for a human to sign an approval and expires into a denial if nobody does. READ-ONLY: an approval is a cryptographic signature by a registered approver, and nothing callable here can grant, deny or extend one.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond readOnlyHint=true, it discloses the lifecycle rule that entries expire into a denial, that an approval is a cryptographic signature by a registered approver, and that no callable endpoint can grant, deny or extend one. These are behavioral facts an agent cannot get from the annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the resource definition followed by lifecycle and constraint, with zero redundant wording. Every sentence carries information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no parameters, no output schema and a single annotation, the description supplies the domain model, the expiry behavior and the action limit. It does not describe the fields of an escalated payment entry, which is a minor gap given no output schema exists to cover it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, and the schema confirms an empty object at 100% coverage, so there are no parameter semantics requiring description. Baseline 4 applies; nothing is missing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise resource and scope: payments by this agent that policy escalated rather than allowed or denied. The escalated-pending-approval framing clearly distinguishes it from siblings like rein_receipts and rein_status without needing to open any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description establishes when this view applies (escalated, awaiting human signature) and implicitly rules out action by noting nothing callable here can grant, deny or extend an approval. It never names a sibling tool as an alternative, so it stops short of explicit routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rein_fetchFetch a URL under Rein spend policyA

HTTP fetch with x402 payments governed by Rein. If the resource is behind a paywall, the payment is checked against policy BEFORE any money moves: allowed payments are settled and receipted, denied ones never happen, and payments past an envelope are escalated to a human for a signed approval. Use this instead of a plain fetch for any request that might be paid. Returns the response plus what Rein decided.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesAbsolute URL to fetch.
bodyNoRequest body, already serialized.
methodNoHTTP method. Default GET.
taskIdNoAttributes this spend to a unit of work, so per-task budgets can cap it. Use one id for every payment of one job.
headersNoExtra request headers.

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and openWorldHint=true; the description adds the material behavior: policy is checked BEFORE money moves, allowed payments settle and are receipted, denied payments never happen, and over-envelope payments escalate to a human for signed approval. That is exactly the pre-execution side-effect detail an agent needs and cannot get from annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with purpose, then the policy/gating behavior, then the return summary. Dense but each sentence carries distinct information; no filler or repetition of the name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description at least signals the return shape ('the response plus what Rein decided'), and it covers the escalation/approval path. It stops short of describing what the decision payload or escalation outcome looks like, but for a 5-param fetch wrapper whose schema is fully documented this is close to complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so url, body, method, taskId and headers are already documented in the schema (taskId even explains per-task budgeting). The description adds no parameter-level syntax or format detail beyond that, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('HTTP fetch with x402 payments governed by Rein') and immediately positions it against the generic alternative ('use this instead of a plain fetch'). An agent can distinguish it from the rein_* status/receipt/escalation siblings without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit selection rule: use this instead of a plain fetch for any request that might be paid. It does not state when *not* to use it (e.g. unpaid/known-free requests) or how it relates to rein_receipts or rein_escalations for follow-up, but the core routing condition is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rein_heartbeatReport this agent as aliveA
Idempotent

Tell Rein this agent is still working, for dead-man monitoring. Only needed when the agent is running but not buying anything -- every rein_fetch is already a sighting. Refused when no activity expectation is declared for this agent.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteNoWhat the agent is doing, for the operator.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and idempotentHint=true, and the description adds a genuine behavioral fact not in the annotations: the call is refused unless an activity expectation is declared. It does not spell out what the heartbeat actually resets on Rein's side (presumably a liveness timer) or what the caller observes on refusal, which is the only remaining gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with the purpose before the routing rule and the refusal condition. No filler and no repetition of the title or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-optional-parameter, no-output-schema tool with annotations present, the description covers purpose, alternatives, and the refusal precondition. Only the observable consequence of a successful heartbeat is unstated, which is minor at this complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single optional 'note' parameter is fully documented in the schema as operator-facing text. The description adds nothing about the parameter, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and target ('Tell Rein this agent is still working') plus the mechanism it feeds ('dead-man monitoring'). It also names a sibling, rein_fetch, and explains the relationship, so an agent can separate this from the other rein_* tools without reading their schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit when ('only needed when the agent is running but not buying anything'), an explicit when-not via the alternative ('every rein_fetch is already a sighting'), and a precondition ('refused when no activity expectation is declared'). All three routing questions are answered in the text.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rein_receiptsWhat this agent has paid, and whether it settledA
Read-only

This session's Rein receipts (every payment decision this server made, allowed or not) plus reconciliation: which allowed payments actually settled and which are still outstanding. Use it to check whether a payment really went through.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMost recent N receipts. Default 20.
windowNoReconciliation window, e.g. '24h'. Default 24h.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

readOnlyHint already tells the agent this is a safe read, so the bar is lower. The description still adds meaningful context beyond the annotation: it discloses that receipts include denied decisions, not just allowed ones, and that a reconciliation step distinguishes settled from outstanding payments. It does not describe truncation or pagination behavior tied to the limit parameter.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no filler, with the core scope (receipts plus reconciliation) front-loaded and the action-oriented use case trailing. Every clause carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully summarizes the return payload (payment decisions plus settlement reconciliation), and annotations cover safety. Both parameters are schema-documented. Only minor gaps remain, such as how results are ordered or capped.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both 'limit' and 'window' are already documented in the schema; baseline is 3. The description's mention of outstanding payments hints at the reconciliation window concept but adds no format or default detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a concrete verb and resource: it returns this session's Rein receipts (every payment decision, allowed or not) plus reconciliation of which allowed payments settled. That is far more specific than the title alone. It does not explicitly differentiate itself from siblings like rein_status or rein_fetch, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear use case: 'Use it to check whether a payment really went through.' That is real guidance for selection. However, it names no alternatives or when-not-to-use conditions relative to rein_status, rein_escalations, or rein_fetch.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rein_statusWhat governs this agent, and where it standsA
Read-only

The rules this agent is subject to and its standing against them: engine posture, agent status, the policies that apply, spend-breaker counters (how close this agent is to tripping an envelope), and liveness. Read it before a large or unusual payment, or after a denial, to find out what the limits actually are.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

readOnlyHint=true already establishes this is a safe, side-effect-free read, so the bar is lower. The description still adds real behavioral value: it discloses that the tool exposes spend-breaker counters showing how close the agent is to tripping an envelope, which directly informs a risk decision. No auth or rate-limit details, but none are needed for a zero-param read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the purpose and followed by the usage trigger. The contents list is dense and slightly jargon-heavy ('engine posture', 'liveness') but every item earns its place by telling the agent what it will get back.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no parameters, the description carries the burden of describing the return values, and it does so by enumerating the five categories of information returned. The enumeration is somewhat abstract, but combined with the usage guidance it is sufficient for an agent to know when and why to call it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the schema has nothing for the description to explain and the baseline of 4 applies. Nothing in the description conflicts with or duplicates the empty input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific and unusual purpose: the governing rules plus the agent's standing against them, and enumerates the returned content (engine posture, agent status, policies, spend-breaker counters, liveness). That enumeration distinguishes it from siblings like rein_heartbeat or rein_receipts, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use triggers: 'before a large or unusual payment, or after a denial, to find out what the limits actually are.' This is clear contextual guidance, but it never names an alternative tool or states when this is the wrong call.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedrein_escalations
    • First observedrein_fetch
    • First observedrein_heartbeat
    • First observedrein_receipts
    • First observedrein_status

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation4/5

Each tool targets a distinct concern: rein_fetch acts, rein_status reads rules/standing, rein_receipts reads decision history, rein_escalations reads the pending-approval queue, and rein_heartbeat signals liveness. The only mild overlap is that escalations are a subset of the payments tracked in receipts, but the descriptions explicitly separate the two.

Naming Consistency4/5

All five tools share a consistent 'rein_' prefix, giving a predictable namespace. The only deviation is rein_fetch, a verb-action tool among otherwise noun/resource-oriented names (escalations, heartbeat, status, receipts).

Tool Count4/5

Five tools is well-scoped for a focused payment-governance server; each maps to a real need (pay, check status, review receipts, review escalations, signal liveness). It sits at the low end, but nothing feels redundant.

Completeness4/5

The surface covers the core agent lifecycle: making governed payments, inspecting rules/standing, verifying settlement, tracking escalations, and heartbeat monitoring. Minor gaps exist — no callable approve/deny action (deliberately out of scope, since approvals are external signatures) and no tool to declare the activity expectation that heartbeat requires.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Budget & cost control for AI agents: hard per-agent spend caps, rate limits, idempotency, and human-in-the-loop approval — enforced before each LLM call, not after the invoice. One hosted MCP endpoint (no proxy or self-hosting), settled via x402 (USDC on Base).
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A budget-bound x402 payment wallet for AI agents: it autonomously pays HTTP 402 payment-gated URLs across every major chain (EVM, Solana, and many non-EVM families). Self-custodial and backendless, your key, your RPC, with spend caps enforced before any on-chain send.
    8
    9
    MIT