
Proctor
An AI agent stops mid-payment and pays a live, verified human for permission to continue
Demo
About
Proctor is a human-oversight gate for AI agents, built on x402. When an autonomous agent is about to take a consequential action (releasing funds, executing a trade, anything past a policy threshold) it stops on an HTTP 402 instead of proceeding. The agent then pays a small fee to ask a real, live human for permission to continue. That human is a witness, deliberately distinct from the agent's operator. The witness answers on their own phone through a single-use link, proves liveness with World ID's Selfie Check (bound to that one decision via a signed nonce, so the proof can't be replayed elsewhere), and approves or refuses within a short deadline. No answer before the deadline is a hard refusal, not a default approval, the system fails closed. Every decision produces an attestation written to a Hedera Consensus Service topic with no admin key, so the record can't be edited or deleted after the fact, by us or anyone else. The attestation states exactly how the human was authenticated and whether their independence from the operator is cryptographically provable or only policy-asserted, so a third party auditing the log doesn't have to take our word for it. Anyone can verify the evidence directly on HashScan. The gate itself is x402-native: agents pay per-interrupt to escalate, then pay again per-second of actual human review time on release, settled over Hedera and Circle Gateway rails. Proctor ships as an installable MCP server (`proctor-mcp` on npm and in the official MCP Registry), so any MCP-capable agent, Claude Code, Claude Desktop, or a custom client, can add real human-in-the-loop oversight with one command, without building the escalation, identity-proofing, or evidence-logging infrastructure itself.
How it's made
Backend Bun and Fastify, with Prisma 7's new driver-adapter mode (`@prisma/adapter-pg`, no native query-engine binary, so the Docker image stays small and boots fast on Railway). The gate speaks x402 v2 (`@x402/core`, `@x402/fastify`, `@x402/hedera`) against the Blocky402 facilitator on Hedera testnet, with Circle Gateway (`GatewayEvmScheme`) as a second settlement rail and its own capability cache with retry/fallback so a facilitator outage degrades gracefully instead of failing every request. Every decision is a state machine (OPEN -> DISPATCHED -> APPROVED/REFUSED/EXPIRED) persisted in Postgres (Supabase), with a TTL sweeper that fails a decision closed the instant its deadline passes, evaluated inside the same SQL statement that performs the write so there's no check-then-act race window. Identity and liveness The witness proves they're a live, present human via World ID 4.0's Selfie Check, requested through IDKit and verified server-side with a signed RP (Relying Party) nonce minted fresh per decision and bound into the World signal, so a proof from one decision can never be replayed against another. Getting this genuinely working end-to-end meant independently re-implementing World's RP signature algorithm (secp256k1 + EIP-191, not ES256 as commonly assumed) to diagnose a stale signing key after a Portal key rotation, and excluding just `@worldcoin/idkit-core` (not its React wrapper) from Vite's dependency optimizer, since the wrapper's WASM binary was 404ing under default pre-bundling. Evidence Every resolved decision is attested to a Hedera Consensus Service topic created with no admin key, so nobody, including us, can alter or delete the log after the fact. The attestation body states plainly how the human was authenticated and whether their independence from the operator is cryptographic (a separate World ID nullifier scope) or only policy-asserted, so a skeptical third party can verify the actual guarantee on HashScan rather than trust the UI. Frontend TanStack Start (React 19, SSR), Tailwind 4, GSAP and Lenis for the landing page and floating glass-effect nav, and a live operator console that drives an on-camera countdown/meter over Server-Sent Events. That stream now has a polling fallback for resilience: a long-lived SSE connection is powerful for a live "on-camera" feel, but a production demo can't depend on one persistent connection behaving perfectly for its entire lifetime, so the same evidence record is also polled every two seconds as a safety net. Distribution proctor-mcp` is published to npm and registered in the official MCP Registry (`io.github.louissarvin/proctor`), so any MCP-capable agent gets real human-in-the-loop oversight with one `claude mcp add` command instead of building escalation, identity-proofing, and evidence-logging from scratch. Notable hacky bits, honestly stated The SSE stream writes straight to Fastify's raw response to control chunk timing precisely, which turned out to silently bypass `@fastify/cors`'s header injection, fixed by reflecting `Origin` manually before the raw `writeHead`. And more than once during deployment, Railway's GitHub webhook went stale after a force-push and needed an empty commit to re-trigger a deploy; Supabase's free-tier pooler needed a project-level restart (paired with a Railway app restart, since a stale Prisma pool otherwise reuses dead sockets) after connection-heavy testing exhausted its pool. Neither is glamorous, but both are the kind of infrastructure reality a real deployment surfaces that a purely local demo never would.