The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Vizier Guard listing page.
Check an AI agent's proposed action against a policy before it calls a tool that can change the world.
Vizier is for developers running agents that can call tools, send messages, change data, or spend money. Put its SDK or MCP proxy between the agent and the tool: it asks a deterministic policy service to ALLOW, BLOCK, or route for human REVIEW, and records a receipt. A response is not proof of user consent unless the authority behind it is verified; see signed delegation grants.
One-minute tour: open the live playground, choose an allowed action, then try BLOCK_AMOUNT and BLOCK_TARGET. The playground uses example policies and data; it does not connect to your bank or modify a real account. The point is to see where the stop occurs before wiring an agent to an external tool.
Try it in code: wrap an MCP server or use the Python SDK. Field reference · Threat model · OpenAPI.
What it does not do: Vizier does not execute the protected action for you. An unverified caller-supplied authority field is an assertion, not a signed grant. Integrate a guard at the tool boundary and check fail-closed behavior before relying on it. This is experimental v0.5.5, not an independent security certification. The @vizier npm scope is not currently published; install the release archives. The MCP proxy is a separate package, not a hosted MCP wrapper of every agent.
How it fits together: agent -> Python/TypeScript guard or MCP proxy -> deterministic policy service on Cloudflare Workers -> decision and receipt -> protected tool only on an authorized allow path. The architecture diagram and technical reference below retain the integration details.
License: MIT. Questions and integration feedback: GitHub issues.
The decision path is strictly deterministic — no non-deterministic LLMs in the critical decision loop. It checks delegated actions, principal identity, amount limits, targets, sensitive operations, and authenticated integration boundaries. Every response includes policy results and a tamper-proof SHA-256 canonical receipt hash.
Status: experimental v0.5.5, deployed on Cloudflare Workers edge. Since v0.3.0, authority can be proved rather than asserted: a principal signs a delegation grant, Vizier verifies it against a registered public key, and the receipt records authority provenance. Read the threat model before placing this service in an execution path.
io.github.vassiliylakhonin/viziervizier-guard)Zero external dependencies (Python standard library only):
Equip Claude Desktop or Cursor with deterministic guardrails (vizier_screen_action, vizier_verify_receipt, vizier_check_policy):
Wrap any local or remote MCP server with deterministic authorization:
@vizier/sdk)Infinite tool loops and runaway retry storms are among the most catastrophic failure modes of autonomous agents — in minutes, an agent stuck in a loop can exhaust external API rate limits, burn through thousands of dollars in LLM tokens, or flood production databases.
Vizier provides built-in circuit breakers across both Python and MCP environments:
CIRCUIT_TRIPPED:LOOP_DETECTED).CIRCUIT_TRIPPED:BUDGET_EXCEEDED).-32028 on tripped loops without invoking the upstream tool.By default the authority in a request is whatever the calling application says
it is. Vizier checks the action against it faithfully and signs the result — but
the receipt then attests to a decision, not to a delegation.
A delegation grant closes that gap. The principal signs a compact JWS that
binds one authority to one agent for a bounded window, the agent sends it as a
grant field, and Vizier verifies it against a public key registered for that
principal:
The public half is registered as VIZIER_PRINCIPAL_KEYS; the private half never
leaves the principal, and no endpoint would accept it. A verified grant makes the
receipt say so:
Two properties are worth stating plainly:
BLOCK, never a quiet fall back to the
caller-asserted path. Failing it open would make a forged grant strictly
better for an attacker than sending none.authority must match the signed one exactly. A genuine
grant carried beside an enlarged authority is BLOCK, not an allow at the
larger limit.Keys are registered out of band and never fetched at decision time, so the authorization kernel still makes no outbound request. Full contract, reason codes and limits: docs/DELEGATION_GRANTS.md.
POST /v1/verify accepts one proposed action and its delegated authority.
Malformed requests return a structured error and never produce ALLOW.
JSON bodies are capped at 1 MiB, 64 levels, and 50,000 aggregate values before
recursive schema validation.
When VIZIER_API_KEY is absent, the service is in evaluation mode: valid
requests can return REVIEW or BLOCK, never ALLOW. When the secret is
configured, REST and MCP verification calls require Authorization: Bearer <key>. A2A also accepts an anonymous evaluation call for discovery and
conformance checks, but that call can return only REVIEW or BLOCK.
principal must be present. Set it to null when the principal is unknown;
Vizier returns REVIEW. Omitting the field is a validation error.
Decision priority is BLOCK, then REVIEW, then ALLOW. The numeric risk
score explains accumulated risk but does not override policy results.
The v0.3.0 resources are additive; /v1/verify remains compatible.
POST /v1/covenants accepts a strict ActionCovenantDraft plus a
principal acceptance bound to the draft hash. A model may produce the draft,
but it cannot activate it by naming itself as the principal.POST /v1/authorizations checks covenant integrity and expiry, exact action
equality, evidence presence and freshness, shallow exact-match invalidation
signals, and the existing delegated-authority policies. It returns a compact
ES256 authorization JWS for every decision.ALLOW before the receipt expires.POST /v1/outcomes verifies the authorization receipt, binds the reported
execution outcome, checks exact forbidden-effect rules, and returns a compact
ES256 outcome JWS.All three resources require authenticated enforcement and
RECEIPT_SIGNING_KEY; there is no evaluation-only activation path. Covenants
remain caller-held immutable envelopes in this milestone. Vizier stores bounded
operational metadata and hashes asynchronously, but not full action parameters,
evidence, signals, outcome effects, JWS tokens, or signing material. It does not
retrieve evidence independently.
Call Vizier immediately before the external action. Treat timeout, invalid JSON,
REVIEW, and BLOCK as stop conditions.
The thin TypeScript client lives in packages/sdk:
vizier-guard)The Python SDK lives in packages/python-sdk with zero external dependencies:
Or protect LangChain / LangGraph tools:
Protect any existing local or remote MCP server with deterministic policy checks:
The API key belongs only in a controlled backend or orchestrator. Do not expose it to the action-taking agent. This key authenticates the integration; it does not prove that each supplied delegation was issued by the principal.
| Rule | Result |
|---|---|
| No authenticated integration credential is configured | REVIEW / AUTHORITY_SOURCE_UNTRUSTED |
Action is absent from allowed_actions | BLOCK / ACTION_NOT_DELEGATED |
Principal is null | REVIEW / PRINCIPAL_UNVERIFIED |
Amount exceeds max_amount | BLOCK / AUTHORITY_LIMIT_EXCEEDED |
| Amount or currency cannot be checked | REVIEW |
| Target is blocked or absent from an allowlist | BLOCK |
| Sensitive action lacks explicit sensitive authority | REVIEW / SENSITIVE_ACTION_REVIEW |
| Authority requires reversibility and the action is not declared reversible | REVIEW / IRREVERSIBLE_ACTION_REVIEW |
The default sensitive actions are transfer_funds, delete_data,
deploy_worker, execute_code, send_external_message,
modify_permissions, and sign_contract.
action.is_reversible is an integration-supplied assertion, not an independently
verified property. require_review_for_irreversible fails to REVIEW when that
assertion is absent or false; it cannot prove a true assertion is accurate.
GET /openapi.json and GET /.well-known/openapi.json return the same
OpenAPI 3.1 contract for /v1/verify, /v1/covenants,
/v1/authorizations, /v1/outcomes, and the authenticated
/v1/insights. Request schemas are emitted from the same Zod definitions
used at the runtime boundary.GET /.well-known/ai-catalog.json routes machines to the A2A Agent Card, the
OpenAPI contract, and the MCP server manifest.GET /.well-known/agent-card.json returns an A2A v1.0 Agent Card with a
canonical ES256 JWS in signatures[] when AGENT_CARD_SIGNING_KEY is
configured.GET /.well-known/jwks.json returns the matching public key. The JWS protected
header points to this endpoint through a same-origin jku.POST /a2a implements the A2A v1.0 JSON-RPC SendMessage method. Anonymous
requests run only in evaluation mode; a wrong supplied credential is rejected.POST /mcp implements MCP 2026-07-28 with server/discover, tools/list,
and tools/call for vizier_verify_action. The same endpoint also answers
the session handshake used by shipping clients: initialize,
notifications/initialized, ping, tools/list, and tools/call over
2025-06-18, 2025-03-26, or 2024-11-05. The request body selects the
profile: only 2026-07-28 carries its protocol version in params._meta.GET /.well-known/mcp.json returns the MCP server manifest, the same document
published to the MCP Registry from server.json at the repository root.The session profile exists because no off-the-shelf client speaks the stateless
profile yet. Measured 2026-09-02 against the deployed Worker: a standard
initialize was rejected with -32600, so the endpoint could not be connected
from any MCP client. The stateless contract is unchanged; the session profile is
additive and shares one verification path.
@vizier/mcp-proxy)packages/mcp-proxy is a standalone reverse proxy adapter that places any existing MCP server behind Vizier. The proxy:
mcp_tool_call verification;ALLOW;REVIEW, BLOCK, timeout, invalid Vizier output, or upstream error;Run directly via npx:
Or configure via environment variables:
Point the pilot MCP client at http://127.0.0.1:8790/mcp, use
VIZIER_PROXY_CLIENT_TOKEN as its Bearer credential, and remove its direct
access to the upstream URL and credential. The proxy is not an enforcement
boundary if the agent can still reach the upstream server, read either backend
credential, or use a shell with equivalent authority.
The credential is optional at connect time. An anonymous tools/call runs in
evaluation mode: the decision is real but the supplied authority is untrusted, so
it can never return ALLOW. The credential unlocks enforcement results, and a
credential that is supplied and wrong is rejected with -32001.
Anonymous calls share a budget of 60 requests per minute per client IP. Past it
the endpoint answers 429 with JSON-RPC error -32029 and a Retry-After
header. Attaching a wrong credential does not leave that budget; a valid one
does.
An anonymous call leaves no receipt, so the only record of it is a counter: one
row per UTC day per surface per outcome, bumped in place. It holds no client IP,
no arguments, and nothing else about the caller, and it is swept by the same
30-day retention as the rest of the audit store. GET /v1/insights reads it
back. Counts are best-effort instrumentation, not proof of adoption, and each
deploy adds exactly four to mcp / served: the live check below probes the
credential-free path on purpose.
Add the header once you hold a credential:
Any client that accepts a Streamable HTTP URL works the same way. Verify the handshake without a client:
server.json at the repository root is the MCP Registry entry for
io.github.vassiliylakhonin/vizier, published on 2026-09-02 and validated
against the 2025-12-11 server schema. It carries no repository block: the
source repository is private, and an entry pointing at a URL that answers 404 is
worse than no link at all. The namespace is claimed through the GitHub account,
not the repository.
The deploy workflow republishes it. scripts/publish-registry.mjs compares
server.json against the live entry and publishes only when the registry lacks
that version, authenticating through GitHub Actions OIDC so no registry token is
stored anywhere. The registry keys an entry on its version, so a manifest edit
rides along with a version bump; an edit without one is reported and skipped
rather than rejected by the registry.
Check the decision without making it, using an existing mcp-publisher login github session:
Publishing by hand still works and takes the same path:
Read the live entry back:
An unrelated io.github.pipeworx-io/vizier is listed in the same registry. The
namespace is what separates them, so a search by bare name returns both.
tests/discovery-contracts.test.ts holds server.json and the served
/.well-known/mcp.json to the same content, so a registry listing cannot drift
away from the endpoint the Worker serves.
Each successful verification returns a receipt ID, creation time, request hash, decision, risk score, rule IDs, and reason codes. Object keys are sorted before SHA-256 hashing. This canonicalization is documented and tested, but it is not an RFC 8785 claim.
Legacy /v1/verify receipts remain unsigned and caller-held for compatibility.
The SDK validates the complete response, checks decision-to-receipt consistency,
and recomputes the request hash before returning a decision.
Action Covenant authorization and outcome receipts are compact ES256 JWS values.
They use a dedicated receipt key and protected typ values for domain
separation. The SDK obtains the matching public key from
/.well-known/jwks.json, verifies the signature, and recomputes the covenant,
request, action, evidence, signal, authorization-token, and outcome bindings
before returning. Signed does not mean independently timestamped or
principal-issued. Vizier persists only selected receipt metadata and hashes; the
complete signed receipt and token remain caller-held.
GET /v1/insights exposes authenticated decision counts, lifecycle totals,
average legacy risk score, and reported failure/violation count. These are
best-effort operational aggregates: asynchronous audit writes can fail, and the
numbers are not proof of production adoption or complete execution history.
Metadata is retained for 30 days and pruned daily by a scheduled Worker handler.
npm run build compiles @vizier/sdk, compiles the private gated-deploy tool,
and runs a Cloudflare deployment dry run. It does not deploy the Worker.
Two checks describe production rather than a commit, so they are separate from
npm run check and need the network:
check:deployed compares the current bundle digest against the newest
deployment: is production running this code? check:live calls the deployed
Worker and asserts what it answers — health, the released version on the service
index and the MCP manifest, a signed agent card, both JWKS keys, the MCP session
handshake, tools/list, an anonymous tools/call that returns a receipt and
cannot grant ALLOW, and the stateless server/discover. A digest can match
while the endpoint is broken, which is why both exist. Point it elsewhere with
VIZIER_ORIGIN. The deploy workflow runs it after the deploy and before the
registry publish, so a Worker that stopped answering is never advertised as a
new version.
To prepare an enforcement deployment after reviewing the threat model:
AGENT_CARD_SIGNING_KEY and RECEIPT_SIGNING_KEY are separate private P-256
JWKs with distinct kid values, alg: "ES256", use: "sig", and stable key
identifiers. Wrangler stores them as secrets; they must not be committed. A
malformed configured key fails the affected signed surface instead of silently
downgrading it.
The public Worker completed its one-time v0.1-to-v0.2 bootstrap on 2026-08-24.
After the integration credential has been stored in macOS Keychain under service
com.vizier.gated-deploy and account VIZIER_API_KEY, normal deployments use:
The private tool accepts no command arguments. It drafts and accepts a five-minute
covenant for the current commit, the fixed deploy_worker action, and the fixed
worker:vizier target. A worktree snapshot is freshness evidence and a dirty
worktree is an invalidation signal. It runs wrangler deploy --strict only after
the SDK verifies a signed ALLOW receipt, then records a signed success or
failure outcome. If outcome recording fails after execution, the command returns
outcome_unrecorded and a non-zero exit code instead of reporting a complete lifecycle.
A new Worker name or fresh environment that does not expose the covenant endpoints needs one explicit bootstrap deployment through its existing v0.1 gate:
This bypass is only for the deployment that introduces the covenant endpoints
and receipt key. Do not set VIZIER_V0_2_BOOTSTRAP for normal deployments of the
public Vizier Worker; they use the covenant lifecycle.
This wrapper is an integration test, not an operating-system security boundary. An agent with unrestricted shell access and Cloudflare credentials can bypass it by invoking Wrangler directly. A production integration must expose only the wrapper capability and keep both Cloudflare and Vizier credentials outside the action-taking agent.
The deployment command is intentionally not part of npm run build. Without
the secret, a deployment remains evaluation-only and cannot return ALLOW.
The Worker uses D1 only for an asynchronous, metadata-only operational audit
trail. The authorization decision path does not depend on D1 availability. It
uses no KV, Durable Object, queue, AI model, or outbound fetch. npm run check
applies every D1 migration in order to an in-memory SQLite database and verifies
that legacy payload-bearing columns are removed.
The repository structure and protocol sources are documented in
docs/ARCHITECTURE.md,
docs/ADR-0001-ACTION-COVENANTS.md,
docs/PILOT.md,
docs/CLAIMS.md, and
docs/SECURITY_REVIEW.md.
Independent principal authentication, durable policy/evidence/full-receipt storage, principal-signed delegation and acceptance, billing, dashboards, reputation models, payment settlement, and LLM policy evaluation inside the privileged kernel remain outside v0.3.0. See FUTURE.md.
/reviews is the operator UI. The administrative API is /v1/reviews.
This is an explicit, single-operator workflow; tenant keys cannot access it.
Set a separate optional Worker secret VIZIER_REVIEWER_KEY for the human
reviewer. Never give it to the submitting agent. VIZIER_API_KEY can submit,
read and claim requests, but cannot approve them. The reviewer credential can
read and decide, but cannot submit or claim. Both secrets must be distinct.
Review endpoints fail closed unless D1 and RECEIPT_SIGNING_KEY are configured.
Apply migration 0006_human_reviews.sql before deploying. Queue payloads and
audit events are retained for seven days, then deleted by the daily cron;
reads exclude expired retention immediately. This is an explicit exception to
the metadata-only verification audit. Submit only necessary public evidence:
never credentials, private keys, seed phrases, prompts or confidential documents.
The UI uses memory only for credentials and renders submitted data as text.
POST /v1/reviews with the integration credential and
{audience, action, evidence, escalation_reason, expires_in_seconds}.
action and evidence are JSON objects; expiration is 60–3600 seconds,
default 1800. Canonical payload size is limited to 32 KiB. The response binds
the whole normalized submission to SHA-256 request_hash.GET /v1/reviews lists the latest 100 requests; GET /v1/reviews/{id}
returns the exact request and atomic audit trail. Evidence is submitter
supplied, not independently authenticated merely by inclusion here.POST /v1/reviews/{id}/decision
using the separate reviewer credential and
{request_hash, decision: "APPROVED" | "REJECTED", reason}.
One pending decision wins. Expired requests cannot be decided.VIZIER-HUMAN-REVIEW+JWS, issuer (service origin), audience, request hash,
review ID (jti), decision, reviewer role, reason, issue and expiry timestamps
(Unix seconds). Maximum lifetime is five minutes and never beyond the request
deadline. The existing /.well-known/jwks.json publishes the verification key.POST /v1/reviews/{id}/consume with {token, request_hash, audience}.
The service verifies the signature and bindings and atomically claims the
approval. Exactly one caller succeeds, including concurrent calls. A lost
response must be reconciled via GET; never interpret a retry conflict as a
new permission. Rejections cannot be consumed. Signing key rotation invalidates
unclaimed attestations signed with an earlier key.Always compute the expected hash from the actual intended action, compare the audience, issuer and expiry, and claim through the server. Offline signature verification alone does not prevent replay. Tokens are proofs of an operator credential's decision, not proof of a particular person's physical interaction. Use a trusted HTTPS service origin; do not follow untrusted receipt URLs.
For Base transfers include the chain ID, sender, recipient, token contract,
amount as an exact base-unit string, and full calldata in action.
A review does not supply missing ledger/sanctions evidence, change a Financial
Guard verdict, reserve funds, enforce a wallet, broadcast a transaction or sign
with Brave Wallet. Those checks and the final wallet confirmation remain separate.
Legacy KV quorum routes are unchanged; use this D1 queue for atomic claims.
The TypeScript SDK exposes submitHumanReview(request) and
claimHumanReview(request, id, token). Pass the original locally intended request
to the claim method: it recomputes the normalized hash, verifies the JWS using
JWKS from the configured service, checks issuer/audience/expiry and then performs
the atomic server claim. Neither method automatically approves or signs.
These methods are source additions; installing an older published SDK will not
provide them until its next package release.
Version 0.5.5 is distributed as GitHub Release archives. The @vizier npm scope
is not currently published; do not assume npm install @vizier/sdk succeeds.
For the proxy, install both archives together so its SDK dependency is satisfied:
Release assets include SHA256SUMS. Imports remain @vizier/sdk. Release CI
builds/tests both archives and publishes them to GitHub. npm and PyPI publication
require separately configured registry credentials; missing credentials produce
explicit warnings and summary entries, not a claim of successful registry publication.
Base native-USDC reviews require a reviewer-configured workflow budget. Concurrent requests reserve budget atomically; claimed holds persist until finalized Base reconciliation. No default limits are activated and wallet signing remains manual. See the contract and limitations.