Skip to content

mppsol/opsrail

Repository files navigation

OpsRail — Policy And Execution Rail For AI-Driven Payments On Solana

A working demo and product foundation built on the x402 protocol on Solana devnet, implementing all 6 layers of the Agent Payments Stack.

OpsRail starts from the x402 Solana sample and extends it into a broader operations product for AI-driven payments, execution safety, and finance workflows.

Status — proof-of-concept / demo, not production. On-chain settlement is simulated (a mock transaction; real finality, retries, and idempotent settlement are not built). See docs/opsrail-framing.md"Honest current scope" for the full list of what is and isn't real. The end-to-end Agent Safety Stack demo — a trustless cross-VM agent payment (Probatio trust × OpsRail control × Veil privacy) on a QuickNode-native x402 rail — runs network-free via npm run demo:agent-safety; see docs/agent-safety-stack-demo.md.

Licensed under Apache-2.0 (see LICENSE / NOTICE); derived from the x402 sample (Apache-2.0).

The control plane is implemented across all three product tracks and driven from a purpose-built operator console (Vite + React) at /. The operator surface is now protected by a minimal session-based auth layer (viewer / operator / approver / admin), and any recorded operator action derives its actor from the authenticated server session rather than a client-supplied string. Multiple payout surfaces run the same spine — one-shot remittance, recurring payroll/AP, invoice-linked AP, and a lightweight keeper — through the same x402-paid gates, the same reliability preflight, the same M-of-N approval loop, and the same audit trail. Every operator view (decision feed, approvals, run explorer, budgets, treasury) is reconstructed from that one trail. For a scripted walkthrough see docs/opsrail-demo-script.md; the original sample-derived pitch demo is preserved at /classic/.

OpsRail Direction

This repository is the starting point for OpsRail.

OpsRail is the control plane between an AI agent and money movement on Solana: it decides what executes automatically, what pauses for a human, and records every step.

Before any spend or settlement, OpsRail runs three gates and writes every decision to an append-only audit trail:

  • Policy — is this spend authorized? (budget, per-tx limit, country allowlist)
  • Compliance — is the counterparty allowed? (sanctions / PEP screening)
  • Reliability preflight — is auto-execution safe, or does a human decide? (ok / retryable / manual_review)

Safe actions execute autonomously; risky ones pause for approval. It turns "an agent that can spend" into "an agent you can let spend."

The three product tracks are the same control plane sold to three buyers — a wedge, an enabler, and an expansion, not three peers:

  • Agent Treasurythe wedge. Delegated budgets and policy-bound autonomous agent spend.
  • Reliability Copilotthe enabler. The trust layer inside the wedge: preflight classification and safer execution that make autonomous spend acceptable.
  • Payroll / APthe expansion. The same gates + audit applied to recurring payouts, invoice-linked approvals, department budgets, and finance ops.

The current x402-derived demo proves the gates + audit spine end-to-end across all three tracks — a visible manual_review pause before settlement, a pending-approvals queue with M-of-N multi-approver resolution, department-budget gating, and an audit-derived treasury report — all reconstructed from the audit trail. See docs/opsrail-framing.md for the full product framing, including the milestone note now that all three tracks are implemented and cross-reviewed.

Collaboration

This repository is meant to be advanced collaboratively with both Claude Code and Codex.

  • The collaboration model follows a probatio-style pattern: cross-review first, with task ownership split by frame rather than permanent module boundaries.
  • Frame-thin work tends to start with Claude Code; frame-thick work tends to converge with Codex.
  • Whoever implements a change is not its final reviewer.

Shared guidance lives in AGENTS.md.

The 6-Layer Stack

Layer Role This Demo
L0 — Settlement Where transactions land Solana devnet + USDC (SPL Token)
L1 — Wallet Agent key management @solana/kit Ed25519 keypair signer
L2 — Routing Cross-rail optimization Multi-corridor: USDC/M-Pesa vs BVNK vs SWIFT vs Western Union
L3 — Protocol How the agent pays x402 — HTTP 402 micropayments (@x402/express + @x402/fetch)
L4 — Governance Spending authorization Policy engine: budget cap ($0.01), per-tx limit ($0.005), country allowlist
L5 — Application What the agent does Cross-border remittance: FX, compliance/PEP screening, routing, settlement

Demo Scenarios

The console's Config & Launcher screen (and the legacy /classic/ demo) can launch these scenarios — see docs/opsrail-demo-script.md for the scripted walkthrough:

Scenario Amount Recipient Result
Normal transfer $500 Mwangi Construction Ltd Success — all 6 layers pass, saves $40 vs SWIFT
High-value transfer $10,000 Mwangi Construction Ltd Paused by Reliability Copilot — preflight flags it for manual review before settlement
Large transfer $50,000 Mwangi Construction Ltd Blocked at L4 — enhanced due diligence fee ($0.006) exceeds per-tx limit ($0.005)
Flagged recipient $500 Ministry of Defence Kenya Blocked at L5 — compliance screening detects government/military entity (PEP)
Recurring payroll batch 3 cycle-runs 3 payees / schedules 1 auto-complete + 2 approval holds — same spine reused for known-payee, new-payee, and over-allowance cycles
Invoice-linked AP run 2 invoices + 1 duplicate attempt 2 vendors / obligations 1 auto-pay + 1 approval hold + 1 duplicate refused — durable invoice object, audit-derived paid state, same approval seam
Keeper tick Current due cycles active schedules Due cycles instantiate once — current cadence occurrence only, no silent backfill, no duplicate second tick

Architecture

┌─────────────────┐         ┌─────────────────┐         ┌──────────────┐
│   AI Agent      │  GET    │  API Server     │         │  x402.org    │
│   (L1 wallet)   │────────▶│  (L3 x402)      │         │  Facilitator │
│                 │         │                 │         │              │
│   L4 policy     │◀────────│  402 + payment  │         │              │
│   checks each   │         │  requirements   │         │              │
│   API call      │         │                 │         │              │
│                 │────────▶│  X-PAYMENT hdr  │────────▶│  settles     │
│   L5 app logic  │         │                 │         │  on-chain    │
│   decides next  │◀────────│  200 + data     │◀────────│  (L0)        │
│   step          │         │                 │         │              │
└─────────────────┘         └─────────────────┘         └──────────────┘
                                  │
                        ┌─────────┴─────────┐
                        │  L2 Routing       │
                        │  L5 FX / Comply   │
                        │  (paid endpoints) │
                        └───────────────────┘

API Endpoints

Endpoint Price Layer Description
GET /api/free Free Health check
GET /api/fx-rate?pair=USD/KES $0.001 L5 FX rates (USD/KES, USD/NGN, USD/GHS, USD/TZS, USD/ZAR, JPY/USD, EUR/USD)
GET /api/compliance?country=KE&name=... $0.001 L5 Sanctions + PEP screening (OFAC, UN, EU, PEP Registry)
GET /api/route?amount=500&dest=KE $0.002 L2 Optimal payment corridor (4 routes compared)
GET /api/settle?amount=500&dest=KE&route=... $0.001 L0 Execute settlement on Solana
GET /api/audit?runId=... Free OpsRail Read the durable audit trail for demo and product events
GET /api/runs Free OpsRail Read the run-level decision feed derived from the audit trail
GET /api/runs/:runId Free OpsRail Read a selected run summary plus ordered audit events
POST /api/runs/:runId/release Free OpsRail Release a stale started-only reservation so the payment identity can be retried safely
GET /api/approvals Free OpsRail Read the pending-approvals queue derived from the audit trail
POST /api/approvals/:runId/approve Free OpsRail Resume a paused run and continue settlement
POST /api/approvals/:runId/reject Free OpsRail Decline a paused run and close it
GET /api/invoices Free OpsRail Read the durable invoice registry with audit-derived payment state
POST /api/invoices Free OpsRail Define a durable invoice obligation linked to a payee
POST /api/invoices/:invoiceId/pay Free OpsRail Instantiate an invoice payment as a normal run on the existing spine
GET /api/payroll/schedules Free OpsRail List recurring schedules, payees, and current-period history
POST /api/payroll/payees Free OpsRail Define a durable payable entity for recurring payouts
POST /api/payroll/schedules Free OpsRail Define a recurring payout schedule with allowance
POST /api/payroll/schedules/:scheduleId/run Free OpsRail Instantiate one payout cycle as a normal run on the existing spine
`GET /api/reports/treasury?groupBy=period&format=json csv` Free OpsRail
GET /api/keeper/status Free OpsRail Inspect in-process keeper status, due count, and surfaced missed cycles
POST /api/keeper/tick Free OpsRail Manually trigger one keeper tick for demo / operator observation

Frontend

The default surface is the rebuilt operator console (Vite + React), served at GET / (which redirects to /console/). It is a purpose-built, workflow-shaped console with six screens, each reconstructed from the audit trail:

  1. Decision Feed — live run stream across all surfaces, color-coded by control-plane state
  2. Approvals Inbox — paused runs with plain-language reasons, M-of-N multi-approver backed by authenticated approver identity, approve / reject
  3. Run Explorer — a run's ordered audit-event timeline (the evidence / "why it paused")
  4. Budgets & Departments — allocation vs committed (settled + reserved), over-allocation, linked payees
  5. Treasury & Reports — settled / reserved / released / blocked rollup by period / department / payee / surface, with spreadsheet-safe CSV / JSON export
  6. Config & Launcher — create departments / payees / schedules / invoices, keeper status/tick, and buttons that launch the demo scenarios

For a scripted walkthrough (the ~2.5-minute winning run), see docs/opsrail-demo-script.md.

Legacy pitch demo: the original sample-derived 4-page narrative (The Problem / The Solution / Architecture / Live Demo, EN/JA) is preserved at GET /classic/ as a rollback-safe path and a story-first pitch surface. It is no longer the default.

Authentication posture in this branch:

  • GET /login serves the console login shell
  • GET /console/ and GET /classic/ redirect unauthenticated users to login
  • read APIs require viewer+
  • config / launcher / keeper mutation routes require operator+
  • approval / rejection / reservation release require approver+
  • approval identity is server-derived from the authenticated session, so one person cannot satisfy a 2-of-N rule by spoofing req.body.actor

Honest scope: auth gates the operator surface and the audit-derived APIs, but per-field PII redaction, IdP/SSO, MFA, and brute-force protection remain future work.

The console consumes the existing API/SSE only (no backend logic lives in the UI); the reliability preflight still distinguishes ok / retryable / manual_review before settlement, and the operator loop (decision feed, approvals, explorer, recovery, resume) is exactly the audit-derived control plane described below.

Recurring payouts are modeled as:

  • durable payees and schedules stored locally
  • on-demand cycle instantiation with a fresh runId per cycle
  • recurring-specific approval reasons layered onto the existing manual_review seam
  • cadence-aware cycle identity (cycleKey) distinct from the monthly allowance window (periodKey)

Invoice-linked AP is modeled as:

  • a durable invoice registry linked to existing payees
  • audit-derived payment state (open / in_flight / paid / void) rather than a mutable paid flag
  • same-invoiceId duplicate prevention via the same reservation discipline used for keeper cycle dedup
  • invoice-specific approval reasons layered onto the existing manual_review seam

The keeper layer now adds a lightweight time-instantiation path:

  • an optional in-process timer, gated by OPSRAIL_KEEPER_ENABLED=true
  • current-cycle-only instantiation for active schedules
  • additive provenance on run.started via trigger: "keeper" | "operator"
  • no silent backfill after downtime: missed cycles are surfaced, not auto-paid
  • demo-safe visibility through POST /api/keeper/tick and the keeper status panel

The control plane now keeps a lightweight durable audit log by default:

  • persisted to .opsrail/audit-log.jsonl unless OPSRAIL_AUDIT_FILE overrides it
  • bounded by OPSRAIL_AUDIT_MAX_EVENTS with a default retention of 500 events
  • replayed on boot so the decision feed, pending approvals, and resumable paused runs survive restart
  • persisted with data minimization: raw API bodies are not kept wholesale, and recipient fields are explicitly tagged as PII

Treasury reporting is now modeled as a pure projection:

  • every rollup is rebuilt from auditStore.list() at read time
  • one canonical run classifier splits payout principal into settled / reserved / released / blocked
  • grouping is one dimension at a time in v1: period, department, payee, or surface
  • JSON and CSV exports are available from the same endpoint, with CSV escaping and spreadsheet-formula neutralization

Reservation recovery is now modeled explicitly:

  • run.started remains the reservation point for duplicate prevention
  • a started-only run that never reaches run.paused or run.completed becomes operator-visible as recoverable
  • operator release appends a terminal run.completed failure with recoveryAction: "stale_reservation_released"
  • keeper cycle dedup and invoice same-id dedup both honor that released state on replay

Setup

# Install dependencies
npm install

# Generate Solana keypairs
npm run keygen

# Copy output to .env (see .env.example)

# Create a git-ignored operator registry with hashed passwords at .opsrail/operators.json
# (or point OPSRAIL_OPERATORS_FILE at another local path)

# Fund client wallet with devnet SOL + USDC
solana airdrop 2 <CLIENT_WALLET_ADDRESS> --url devnet
# Get devnet USDC from https://faucet.circle.com/

# Start the server (serves the API + the pre-built operator console)
npm run server

# Open http://localhost:4021  →  redirects to the authenticated operator console at /console/

Note: The demo works even without devnet USDC — it falls back to internal API calls while still showing the full x402 flow in the UI.

Console note: the operator console is pre-built into public/console/, so npm run server serves it as-is — no build step needed to run the demo. To develop the console, use npm run console:dev (Vite dev server); to rebuild the committed assets, npm run console:build. (Vite requires Node 20+.) The legacy pitch demo stays at /classic/.

Clean demo state: stop the server, rm -rf .opsrail, then restart to reset the audit log / registries to fixtures (recommended before a scripted run — see the demo script).

Persistence note: server runs now persist the audit log locally. Use OPSRAIL_AUDIT_FILE to change the file path and OPSRAIL_AUDIT_MAX_EVENTS to change the retention bound.

Keeper note: set OPSRAIL_KEEPER_ENABLED=true to run the in-process timer automatically. The demo UI also exposes a tick now path so keeper behavior is observable without waiting for real cadence boundaries.

Project Structure

opsrail/
├── console/            # Operator console source (Vite + React + TS) — the default UI
│   └── src/
├── public/
│   ├── console/        # Built console assets, served at /console/ (and default / redirects here)
│   └── classic/        # Legacy sample-derived 4-page pitch demo, served at /classic/
├── src/
│   ├── server.ts       # Express server: x402 middleware, paid APIs, OpsRail control-plane APIs, SSE flows, / -> /console redirect
│   ├── lib/            # audit / policy / reliability / approvals / reservations / payroll / invoices / keeper / departments / reporting (+ tests)
│   ├── client.ts       # CLI agent (standalone x402 payment client)
│   └── keygen.ts       # Solana keypair generator
├── docs/               # task briefs, framing, roadmap, demo script
├── reviews/            # cross-review log
├── .env.example
├── package.json        # server (tsx) + console (vite) scripts
└── tsconfig.json

Known Limitations & Production Roadmap

This is a PoC demo. The following gaps exist between this implementation and a production-grade agent payment system.

L4 Governance: Static Thresholds

The current policy engine uses fixed values (budget cap, per-tx limit, country allowlist). Production requires:

  • Dynamic policy engine — Authorization logic that factors in transaction history, destination trust scores, and market volatility (e.g., FX rate spikes increasing corridor fees beyond the budget mid-flow)
  • Intent monitoring — The current per-tx limit cannot prevent "salami attacks" where a compromised agent drains assets through thousands of individually-valid micro-transfers. A sliding-window anomaly detector is needed alongside budget caps
  • Rate limiting on paid APIs — An attacker can force the agent to exhaust its budget by repeatedly triggering expensive compliance checks (e.g., submitting PEP-flagged entity names). Cached results and per-endpoint rate limits would mitigate this

L0 Settlement: No Error Recovery

The demo assumes settlement always succeeds. Production requires:

  • Error recovery state machine — Defined refund processes for L0 settlement failures, retry logic with exponential backoff for L3 paid API calls, and idempotency keys to prevent double-settlement
  • Finality awareness — The fallback mode ("works without devnet USDC") masks real on-chain behaviors like finality delays and transaction failures that must be handled explicitly

OpsRail Control Plane: Local Persistence Only

The current persistence layer is intentionally lightweight: a local JSONL audit log plus replay-based rehydration of paused runs. It is enough to survive a single-process restart and keep the demo honest, but production still requires:

  • Durable multi-node storage — Shared persistence instead of a single local file
  • Authenticated operator access — The decision feed and approval APIs are still unauthenticated demo surfaces
  • PII redaction/export policy — We now minimize what persists, but not every consumer-facing view is redacted for production use
  • Treasury reports cover the retained audit window, not all history — Run-aware retention can trim old completed runs, so past periods eventually need snapshot/archive support for long-horizon finance reporting

Keeper / Scheduling: Single-Process And Safety-First

Task 008 adds a lightweight in-process keeper, but it is intentionally conservative. It only instantiates the current due cycle, never bursts historical payouts after downtime, and uses the audit trail as the source of truth for duplicate suppression. Production still requires:

  • Multi-node scheduling semantics — No leader election or distributed coordination yet
  • Richer cadence / timezone handling — The demo uses a small UTC cadence model (daily / weekly / monthly)
  • Explicit backfill tooling — Missed cycles are surfaced, not auto-paid; any catch-up flow would need operator-visible policy
  • Department budgets are still flat monthly allocations — Department spend now includes reserved commitments from the audit trail, but hierarchy and non-monthly allocations are still deferred
  • Approval identity is still actor-string best effort — Multi-approver can dedupe by actor, but true separation-of-duties still needs authenticated operator identity
  • Treasury reporting is payout-principal only — x402 API micro-fee reporting, multi-currency normalization, and GL/ERP formats are still deferred
  • Pay-in-full only for invoices — Invoice-linked AP currently treats one successful run as the full settlement of one invoice; partial payments and installment ledgers are not modeled yet
  • Invoice / vendor system integration — No OCR, invoice matching, or ERP connectivity yet

L1 Wallet: Keypair in Process Memory

The demo stores the agent's private key as a base58 string in .env, loaded into process memory. Production requires:

  • TEE (Trusted Execution Environment) — Manage private keys within hardware-isolated enclaves (e.g., AWS Nitro Enclaves) with governance policies bound to the key, so the key cannot be used outside approved policy contexts
  • MPC or smart contract wallets — Multi-party computation or on-chain policy enforcement (e.g., Safe) as alternatives to single-keypair signing

Protocol-Level (Outside Demo Scope)

  • x402 Facilitator trust model — The demo relies on x402.org/facilitator as a trusted third party. The trust assumptions and decentralization roadmap of the Facilitator are defined by the x402 protocol, not this demo
  • Replay attack resistance — Handled by x402's SettlementCache (120s TTL) at the protocol level. Not demonstrated in the UI but present in the middleware
  • Regulatory compliance (VASP) — The demo performs PEP screening at L5, but the question of who bears legal liability for an AI agent's transactions remains an open regulatory question industry-wide

Tech Stack

  • x402: @x402/core, @x402/svm, @x402/express, @x402/fetch
  • Solana: @solana/kit (v5)
  • Server: Express 5 + TypeScript
  • Operator console: Vite + React + TypeScript (built to public/console/, served static)
  • Control-plane lib: audit / policy / reliability / approvals / reservations / payroll / invoices / keeper / departments / reporting — pure, audit-derived, unit-tested (node --test)
  • Runtime: tsx (Node.js)

About

Policy And Execution Rail For AI-Driven Payments On Solana

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors