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

  1. Sign up with your invite code (or join the waitlist).
  2. Mint a mesh key on the onboarding page — it walks you to your first ledger row in under five minutes.
  3. Point your SDK at the shadow proxy (snippets below). No other code changes.
  4. 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.

pass

Every check passed and confidence cleared the floor. The action proceeds.

hold

A check failed or confidence fell short. The action waits for review.

escalate

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:

shadow

Observe only — decisions are recorded, nothing is gated.

advisory

The gate verdict is recorded alongside the decision, but never blocks.

enforcing

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.