Documentation
VasperaMesh sits between your agents and their model provider. Every generation becomes one immutable row in a hash-chained audit ledger you — or a regulator — can verify offline, without trusting us.
Quickstart
- Sign up with your invite code (or join the waitlist).
- Mint a mesh key on the onboarding page — it walks you to your first ledger row in under five minutes.
- Point your SDK at the shadow proxy (snippets below). No other code changes.
- Draft a policy in the Policy Studio, get it approved by a second member, and activate it — that first activation is your chain's genesis.
Fastest path: set up with Claude
Don't want to hunt for where your base URL lives? Mint your key on the onboarding page (the Set up with Claude tab pre-fills it), then paste this into Claude Code, Cursor, or any AI coding agent inside your project — it finds your model client and wires up Mesh for you.
Set up VasperaMesh audit logging in this project.
VasperaMesh is a drop-in proxy: it records every AI model call to a tamper-evident
audit ledger. My provider API key is unchanged and passes straight through — this only
changes the client's base URL and adds two headers. It is observe-only and fail-open.
Do this:
1. Find where this repo creates its LLM client. Search for: "new Anthropic(",
"new OpenAI(", "createAnthropic(", "createOpenAI(".
2. Add these to that client, WITHOUT removing the existing provider API key:
• Anthropic SDK: baseURL: "https://www.vasperamesh.com/api/proxy"
• OpenAI / AI SDK: baseURL: "https://www.vasperamesh.com/api/proxy/v1"
• headers: { "x-mesh-key": process.env.MESH_KEY, "x-mesh-agent": "my-agent" }
3. Add MESH_KEY to my .env / secrets: MESH_KEY=<your-mesh-key>
4. Change nothing else — no model names, prompts, or other logic.
5. Make one small test model call, then check https://www.vasperamesh.com/dashboard.Or mint a key from your terminal
The CLI authenticates via your browser and writes MESH_KEY to your .env. The key is issued only after you approve the request in a signed-in session, so it always belongs to your account.
npx vasperamesh login # approve in the browser, MESH_KEY written to .env npx vasperamesh init # login, then print the "set up with Claude" prompt
The shadow proxy
Change the baseURL, add two headers, done. Your provider API key passes through untouched; Mesh stores only content digests, never your prompts or completions. The proxy fails open: if Mesh is ever unreachable, your calls still go through — you lose rows, not uptime.
Anthropic SDK
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://www.vasperamesh.com/api/proxy", // SDK appends /v1/messages
defaultHeaders: {
"x-mesh-key": "<your-mesh-key>",
"x-mesh-agent": "my-agent", // how rows are attributed
},
});
// Keep your own ANTHROPIC_API_KEY — Mesh passes it through untouched.OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://www.vasperamesh.com/api/proxy/v1", // SDK appends /chat/completions
defaultHeaders: {
"x-mesh-key": "<your-mesh-key>",
"x-mesh-agent": "my-agent",
},
});
// Keep your own OPENAI_API_KEY — Mesh passes it through untouched.Vercel AI SDK
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({
baseURL: "https://www.vasperamesh.com/api/proxy/v1", // AI SDK appends /messages
headers: {
"x-mesh-key": "<your-mesh-key>",
"x-mesh-agent": "my-agent",
},
});curl
curl https://www.vasperamesh.com/api/proxy/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "x-mesh-key: <your-mesh-key>" \
-H "x-mesh-agent: my-agent" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":50,
"messages":[{"role":"user","content":"Hello from Mesh"}]}'Each response includes an X-Mesh-Ledger header with the row that recorded it.
Policies & the gate
A policy is a plain-English constitution plus a compiled, deterministic gate. Describe what your agent may and may not do; the AI assistant drafts both, and the worked examples you see are computed by running the compiled gate — not model-claimed.
The lifecycle enforces separation of duties: the author can never approve their own policy, approval freezes the content, and activation stamps the ledger. Every transition is itself a ledger row.
Every check passed and confidence cleared the floor. The action proceeds.
A check failed or confidence fell short. The action waits for review.
A bound you marked as escalating was crossed. A human must decide.
Gate checks are declarative — required sources, grounding, numeric bounds, required disclosures, freshness, categorical decisions, and a lending credit-decision check that derives ECOA / Reg B principal reason codes — and they fail closed: a check that cannot run counts against the action, never for it. See Lending for the credit-decision gate and adverse-action notices.
Enforcement: observe → block
An organization runs in one of three modes, and you graduate one workflow at a time:
Observe only — decisions are recorded, nothing is gated.
The gate verdict is recorded alongside the decision, but never blocks.
A hold or escalate is blocked before it executes and queued for a human.
Enforcement is inline in the request path. Assemble the decision and call /api/gate/enforce: under enforcing mode a hold or escalate returns 409 with an escalation to poll — not the action. The model-gateway proxy does the same for a structured decision in a model response (and, opt-in, for streamed responses via buffer-and-gate).
# Under enforcing mode, a blocked decision returns 409 — act only on 200.
curl -sS https://www.vasperamesh.com/api/gate/enforce \
-H "x-mesh-key: $MESH_KEY" -H "content-type: application/json" \
-d '{
"agent": "underwriting-agent", "action": "credit-decision",
"namedValues": { "dti_ratio": 0.31, "credit_score": 590, "ltv_ratio": 0.80 },
"namedStrings": { "credit_decision": "DENY" },
"sources": [{ "name": "bureau", "url": "https://…", "value": 590, "asOf": "2026-08-01T00:00:00Z" }],
"disclosures": ["Equal Credit Opportunity Act notice attached"]
}'
# 200 { "status": "allowed", "decision": "pass", "reasonCodes": ["SCORE_LOW"], "runId": 37 }
# → an explainable denial: proceed, then render the §1002.9 notice from runId.
# 409 { "status": "blocked", "decision": "hold", "escalationId": "…" }
# → an unexplained denial or out-of-policy approval: do NOT act; a human decides.Verify the ledger yourself
Each row commits to the previous row's hash, and your chain's identity is inside every hash — an operator cannot serve one customer's chain as another's. Chain heads are anchored to Bitcoin via OpenTimestamps, so even we cannot rewrite history after the fact.
The verifier is a single dependency-free script. Download verify-mesh-ledger.mjs and run it against the live API or an exported JSON file from /api/ledger/export:
node verify-mesh-ledger.mjs \ --base https://www.vasperamesh.com \ --chain <your-chain-id>
Your chain ID and this exact command are on your dashboard. Anyone holding an export can re-derive every hash offline — no VasperaMesh account, no trust in us required.
Questions
Open an issue on GitHub or use the contact page.