5.5 KiB
PatchPass
A human approval layer for AI agents.
Review changes. Approve intent. Let agents proceed.
Agents submit structured change requests containing code diffs, configuration updates, or custom values. Humans review and approve them in the browser, then agents verify the platform-signed decision through the API before applying anything.
How it works
Agent PatchPass Human
│ create_request ───────────► │ │
│ (diffs/config/custom) │ normalize · hash · store │
│ ◄─── request_id + URL ────────│ ──── notify (WS) ──────────► │ reviews in browser
│ │ │ APPROVE / REJECT / REQUEST_CHANGES
│ get_request ──────────────► │ ◄──── signed decision ──────│
│ ◄─── state + receipt ─────────│ │
│ consume_approval ─────────► │ mark CONSUMED (single-use) │
│ ◄─── receipt (HMAC) ──────────│ │
│ apply changes │ │
- Decisions are platform-signed. Approvals and rejections carry an HMAC-SHA256 receipt bound to the exact reviewed content (
content_hash). If the agent changes anything after approval, the receipt no longer matches. - Approvals are single-use. An agent must
consumean approval before proceeding; it cannot be replayed. - Two ways in for agents: a REST API (
/v1/...) and an MCP server (/mcp), both authenticated with a per-agent API key and backed by the same service layer.
Request states
PENDING · CHANGES_REQUESTED · APPROVED · REJECTED · EXPIRED · CONSUMED · CANCELLED
No update is possible after approval, rejection, expiry, consumption, or cancellation. A REQUEST_CHANGES decision requires a comment and returns the request to the agent, which can update_request to resubmit (highlighted in the UI as UPDATED).
Limits
| Limit | Value |
|---|---|
| Requests per agent per hour | 15 |
| Agents per human | 5 |
| Simultaneous pending requests per agent | 1–10 (human-configured, default 5) |
| Request expiry | default 30 min, max 12 h |
Tech stack
| Layer | Technology |
|---|---|
| Backend | Node.js + TypeScript, rjweb-server |
| ORM | Prisma 7 (@prisma/adapter-pg) |
| Database | PostgreSQL |
| Frontend | React + TypeScript + Tailwind CSS (Vite), served by the backend |
| Realtime | rjweb WebSocket channels |
| Tests | Vitest |
Repository layout
Backend/ Node + TypeScript API (rjweb-server) + Prisma
src/
index.ts server bootstrap + static UI serving
lib/ service layer, auth, signing, limits, crons, MCP tools
routes/ REST (/api, /v1), MCP (/mcp), WebSocket (/api/ws)
tests/ Vitest integration tests
prisma/schema.prisma
UI/ React + Tailwind frontend (builds to UI/build)
Dockerfile multi-stage build (UI + backend)
docker-compose.yml backend + postgres
.gitea/workflows/ CI: build · lint · test · push image
Running locally
Prerequisites: Node 24+, pnpm, and Postgres (or Docker).
# 1. Start Postgres (Docker)
docker run -d --name patchpass-db \
-e POSTGRES_USER=patchpass -e POSTGRES_PASSWORD=patchpass -e POSTGRES_DB=patchpass \
-p 5433:5432 postgres:16-alpine
# 2. Backend
cd Backend
cp ../example.env .env # then edit DATABASE_URL / INSTANCE_SECRET
pnpm install
pnpm generate
pnpm migrate # apply migrations
pnpm dev # http://localhost:5000
# 3. UI (separate terminal, for hot-reload dev)
cd UI
pnpm install
pnpm dev # http://localhost:3000 (proxies /api to :5000)
For a production-style run, cd UI && pnpm build — the backend then serves UI/build at /.
Tests
cd Backend
pnpm test # spins up against the test database (see vitest.config.ts)
Deploying
cp example.env .env # set DATABASE_URL=...@db:5432/..., a strong INSTANCE_SECRET
docker compose up -d --build
The backend applies migrations on boot and serves the UI. The first account to register becomes the admin.
Connecting an agent
Create an agent in the UI, then use its API key.
OpenClaw — add to ~/.openclaw/openclaw.json:
{
"mcp": {
"servers": {
"patchpass": {
"transport": "streamable-http",
"url": "https://your-host/mcp",
"headers": { "x-api-key": "pp_agent_..." }
}
}
}
}
REST:
curl -X POST https://your-host/v1/change-requests \
-H "x-api-key: pp_agent_..." -H "Content-Type: application/json" \
-d '{"title":"Bump timeout","changes":[{"type":"config","path":"timeout","before":30,"after":60,"content_type":"integer"}]}'
Tell your agent: before taking any consequential action, call create_request, wait for approval, then consume_approval before proceeding.
Privacy & data
Humans can export all their data as JSON and delete their account (cascading to agents, requests, and notifications) from Settings. Agent icon URLs are fetched once for validation and never stored. See the in-app Privacy Policy and Terms of Service.
License
MIT-0