Quick Start

VITNA produces Ed25519-signed evidence records that anyone can verify offline with a published public key and an open-source verifier, with no need to trust VITNA's servers. It is a cooperative guardrail with heuristic detection, and those limits are documented publicly. Its purpose is not prevention. It is independently verifiable proof that an AI agent's actions were checked and allowed.

In category terms: VITNA issues verifiable compliance receipts for agent actions. Each checked action produces a decision record, and those records are packaged into an Ed25519-signed agent action receipt that a third party can verify offline. See Verify Evidence for the two verification tiers, and What VITNA Sends for exactly what leaves your machine.

There are two ways in, and they suit different jobs. Pick one.

Start here for most people
Pre-action checks over MCP
Your agent asks VITNA before it does something dangerous and gets allow / block / flag back. No credentials to start — the first check creates a trial account by itself.
MCP Quick Start →
If you are logging your own events
REST event logging
You POST events to the API from your own code. Needs an account and an API key first. Use this when you are instrumenting something that is not an MCP agent.
REST Quick Start →

MCP Compliance Quick Start

Six steps, about five minutes, and you do not need an account or an API key to begin. You end up looking at a real decision your own agent made, on your own dashboard.

Package: @costrinity/vitna-compliance-mcp. Details: the package.

Steps 1–3 — add the server to your client

The config is the same everywhere. Only where you put it and how you reload differ:

json
{
  "mcpServers": {
    "vitna": {
      "command": "npx",
      "args": ["-y", "@costrinity/vitna-compliance-mcp"]
    }
  }
}

No env block, no API key, no account. That is the point.

Claude DesktopSTEPS NOT RUN END-TO-END
1. Config file. It does not exist on a fresh install. Easiest route: open Settings from the menu bar (not the in-window account settings) → Developer → Edit Config, which creates it for you. Or by hand:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
2. Paste the block above. If the file already has content, merge themcpServers key in rather than replacing the file.
3. Fully quit and reopen. Closing the window is not enough. On Windows, right-click the Claude icon in the system tray (it may be under “hidden icons”) and choose Quit. On macOS, Cmd+Q.
Confirm it loaded: click the + icon in the chat input, hover Connectors, and look for vitna.
Claude CodeSTEPS NOT RUN END-TO-END
1. One command, no file editing needed:
bash
claude mcp add vitna -- npx -y @costrinity/vitna-compliance-mcp
2. Or by file, if you would rather commit it for the whole team: put the JSON block above in .mcp.json at your repository root (project scope). Personal servers live in ~/.claude.json, which on Windows is %USERPROFILE%\.claude.json.
3. Restart the session if you edited a file by hand — both files are read at session start. The claude mcp add route does not need it.
Confirm it loaded: run claude mcp list and look for vitna with a Connected status.
CursorUNTESTED — NOT INSTALLED HERE
1. Config file. Per project: .cursor/mcp.json in the repo (commit it and your team gets the same tools). For every project: ~/.cursor/mcp.json in your home directory.
2. Paste the block above.
3. Restart Cursor. There is no documented reload button for MCP config.
Confirm it loaded: open Customize in the sidebar, then MCPs, and check that vitna is listed.
About those labels. The paths, restart rules and verification steps above are taken from each vendor’s current official documentation. What we have not done is sit at a fresh install of each client and walk all six steps ourselves, so we are not going to claim we did. If a step is wrong for your version, tell us and we will fix it: hello@costrinity.xyz.

Step 4 — make your first real check

Type this to your agent, exactly as written:

text
Use the vitna_preflight tool to check this action before running it: rm -rf /

The agent calls vitna_preflight with:

json
{ "action": "rm -rf /", "action_type": "shell" }

and VITNA returns this — the same answer every time, which is why we can print it here:

json
{
  "check": "action_preflight",
  "decision": "blocked",
  "effect": "block",
  "flagged": true,
  "trial": true
}

The full response also carries limits and disclaimer strings, both stating plainly that this is a heuristic pattern match and not a sandbox. trial: true means the account is not claimed yet, which is the next step.

An unclaimed trial key gets the decision label only. Once you claim (step 5) the same call also returns threat_category, severity and a reason naming the rules that matched — for this example, shell_dangerous at severity critical. The detail is withheld from trial keys because the pattern set is the part worth stealing.

What just happened. That call self-provisioned a restricted trial account for you. No signup, no key. It is capped and short-lived and it does not keep signed evidence — but the decision itself is already on a timeline waiting for you.

Step 5 — claim the trial

Ask your agent for the claim link. It has one and will hand it over:

text
Use the vitna_claim tool and show me the claim URL.

Open it, enter a real email, type the six-digit code. Claiming lifts the trial caps, stops the key expiring, and starts keeping the Ed25519-signed evidence records that make a decision provable to someone else later.

Step 6 — see the decision you just made

Claiming drops you straight onto your dashboard. The rm -rf / check from step 4 is already on the timeline, reading:

text
Pre-flight check (blocked): shell — shell_dangerous: rm -rf /
✓ That is the whole loop. Your agent asked before acting, VITNA answered, and the answer is now a record you can point at.

REST Quick Start

Get your agent sending events to VITNA in under 2 minutes. No SDK required, just one HTTP call. Unlike the MCP path above, this one needs an account and an API key before you start.

1
Create an account
Go to vitna.costrinity.xyz/dashboard and register with your email.
2
Create an agent
Click "New Agent", give it a name, and copy your API key.
3
Send your first event
Make a POST request from your agent:
bash
curl -X POST https://vitna.costrinity.xyz/api/ingest \
  -H "Authorization: Bearer vitna_your_key" \
  -H "Content-Type: application/json" \
  -d '{"action": "hello-world", "data": {"message": "VITNA connected!"}}'
✓ That's it. Open your dashboard and you'll see the event appear in real-time.

The package

One package, and every step above uses it. Your agent asks it before acting and it records the answer.

PackageWhat it doesWhen you want itInstall
@costrinity/vitna-compliance-mcpYour agent asks before it acts. Returns allow / block / flag on a proposed action and records a signed evidence record of the decision.You want a guardrail your agent calls, and provable receipts that it did.npx @costrinity/vitna-compliance-mcp
no credentials to start
A passive observer that records existing MCP traffic without deciding anything is built in this repo but is not published, so it is not documented here yet. Nothing on this page depends on it.

REST API

The VITNA API is a simple REST API. Every endpoint accepts JSON and returns JSON.

POST /api/ingest

Log a single event from your agent. Core endpoint, call this every time your agent does something.

Headers

HeaderValue
AuthorizationBearer vitna_your_key
Content-Typeapplication/json
javascript
await fetch('https://vitna.costrinity.xyz/api/ingest', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer vitna_your_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    action: 'db.query',
    data: { statement: 'SELECT count(*) FROM orders', rows: 12 },
    status: 'success'
  })
})

Response

json
{
  "ok": true,
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "storage_mode": "cloud"
}

🔒 Storage Mode

Every event request accepts a storage_mode field. When set to "local", VITNA validates your API key and enforces rate limits, but never writes to our database. Events are returned to the client and stored in your browser's IndexedDB. Nothing persists on COSTRINITY servers.

☁️ Cloud (default)
Events stored in your isolated VITNA account. Accessible from any device. 7–90 day history depending on plan.
💾 Local
Events stored in your browser's IndexedDB only. Zero server persistence. Data never leaves your machine.
javascript
// Cloud mode, stored in your VITNA account
body: JSON.stringify({ action: 'buy', storage_mode: 'cloud' })

// Local mode, VITNA never touches our DB
body: JSON.stringify({ action: 'buy', storage_mode: 'local' })
// → Server returns enriched event with ID + compliance tags
// → Your dashboard stores it in IndexedDB automatically

POST /api/ingest (batch)

Log multiple events in a single call. Up to 100 events per request.

javascript
await fetch('https://vitna.costrinity.xyz/api/ingest', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer vitna_your_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    events: [
      { action: 'search_orders', data: { matched: 847 } },
      { action: 'policy_check', data: { passed: 12 } },
      { action: 'email.send', data: { template: 'refund_confirmation' } }
    ]
  })
})

GET /api/graph

Returns the activity graph for an agent, nodes are actions (or individual events), edges are causal links inferred from parent_id / trace_id / run_id / temporal proximity. Used by the dashboard’s Activity Graph.

agent_id*UUIDRequired.
owner_id*UUIDRequired. Must own the agent.
group"action"|"event"Default 'action'. 'event' returns one node per event (raw timeline).
limitint 1–5000Default 2000. Caps the number of events scanned.
include_noise0|1Default 0. When 0, baseline events (heartbeat, ping, health-check, *-heartbeat, idle_*) are filtered server-side BEFORE the temporal-proximity edge calculation. The dashboard passes 1 to keep the count badge live; CLI consumers usually want the default.
include_system0|1Default 0. When 1, VITNA-internal cron events (heartbeat_missed, budget_warning, key_rotated, …) are included.
bash
# Default, actions only, clean edges
curl "https://vitna.costrinity.xyz/api/graph?agent_id=...&owner_id=..."

# Include baseline noise (what the dashboard does)
curl "https://vitna.costrinity.xyz/api/graph?agent_id=...&owner_id=...&include_noise=1"
json
{
  "nodes": [
    { "id": "buy", "label": "buy", "count": 47, "color": "#4ade80", "size": 7, ... }
  ],
  "edges": [
    { "from": "scan", "to": "buy", "weight": 12, "kind": "trace" }
  ],
  "stats": {
    "event_count": 1284,
    "action_count": 6,
    "edge_count": 14,
    "window_hours": 24.0,
    "noise_event_count": 847,    // events filtered out as baseline noise
    "noise_action_count": 1      // distinct noise action types (e.g. "griid-heartbeat")
  },
  "agent": { "id": "...", "name": "deploy-agent" }
}

TypeScript SDK

Not yet published. The typed SDK is planned and is not on npm, so there is nothing to install today. The shape below is a design preview and may change. The package we do publish is @costrinity/vitna-compliance-mcp (the MCP server, see the MCP section). Until the SDK ships, use the REST API directly, which needs no dependency at all.

Planned: an optional typed wrapper around the REST API adding retry logic, batching, and type safety.

typescript
// PLANNED API, not yet published on npm.
import { Vitna } from '@costrinity/vitna'

const vitna = new Vitna({
  apiKey: process.env.VITNA_API_KEY!,
  // Optional: set a default agent name
  // agent: 'support-copilot',
})

// Pre-flight: check a risky action before the agent runs it.
const decision = await vitna.preflight({
  action: 'db.query',
  target: 'DELETE FROM users WHERE id = 42',
  scope: 'in_scope',
})
if (decision.effect === 'block') {
  throw new Error(decision.reason)
}

// Log a completed action for the signed audit trail.
await vitna.log({
  action: 'email.send',
  data: { to: 'customer@example.com', template: 'refund_confirmation' },
  status: 'success',
})

// Log an error
await vitna.log({
  action: 'tool.call',
  data: { name: 'issue_refund', message: 'Amount exceeds policy cap', code: 'ERR_POLICY' },
  status: 'error',
})

// Batch log, efficient for high-frequency agents
await vitna.logBatch([
  { action: 'tool.call',  data: { name: 'search_orders' } },
  { action: 'tool.call',  data: { name: 'issue_refund' } },
  { action: 'file.write', data: { path: '/reports/q3.csv' } },
])

ElizaOS Plugin

Zero-config plugin for ElizaOS agents. Automatically logs every action your agent takes.

Not yet published. The ElizaOS plugin is not on npm yet, so the install below will not resolve today. It is shown as a design preview. Use the REST API or @costrinity/vitna-compliance-mcp in the meantime.
bash
# PLANNED, not yet published on npm npm install @costrinity/vitna-eliza-plugin
typescript
import vitnaPlugin from '@costrinity/vitna-eliza-plugin';

export const character = {
  name: 'SupportAgent',
  plugins: [
    vitnaPlugin({
      apiKey: process.env.VITNA_API_KEY,
      // Optional: custom agent name (defaults to character.name)
      agentName: 'my-elizaos-bot',
    }),
  ],
  // All agent actions are automatically captured:
  // - Tool calls and responses
  // - Decision reasoning
  // - Memory reads/writes
  // - Errors and retries
};
💡 The ElizaOS plugin hooks into the agent runtime automatically. You don't need to add any vitna.log() calls manually.

Event Schema

Full schema of all fields accepted by the ingest endpoint.

FieldTypeDescription
action*stringWhat the agent did. Free-form string: 'buy', 'error', 'decision', 'transfer', etc.
dataobjectAny additional context. JSON object, up to 64KB. Fully searchable in dashboard.
cost_solnumberOptional numeric cost recorded for this action, SOL-denominated for agents that track spend. Supports up to 9 decimal places.
statusstring'success' | 'error' | 'info' | 'warning'. Defaults to 'info'.
iduuidAuto-generated UUID for the event. Returned in response.
agent_iduuidResolved from your API key. You don't need to pass this.
timestamptimestamptzAuto-set to current UTC time. Cannot be overridden.

Alerts

Smart alerts watch your agents for you. Configure them from the dashboard, no code needed.

Silence DetectionMost Popular

Trigger when an agent hasn't sent an event in N minutes. Catches stuck, crashed, or looping agents.

Error Rate

Alert when error events exceed X% of total events in a rolling window.

SOL Spend ThresholdCritical

Alert when cost_sol total exceeds your limit in 1 hour. Protects against runaway spending.

Anomaly Risk

Alert when the behavioural risk score for an agent crosses your threshold.

Delivery channels

  • Email (all plans)
  • Webhook POST (Pro+) — works with Slack incoming webhooks

Webhooks

VITNA can POST to your endpoint when an alert fires. Available on Pro and above.

json
// Example webhook payload
{
  "event": "alert.fired",
  "alert_type": "silence",
  "agent_id": "agt_abc123",
  "agent_name": "support-bot-prod",
  "message": "Agent has been silent for 10 minutes",
  "fired_at": "2026-03-24T18:00:00Z",
  "dashboard_url": "https://vitna.costrinity.xyz/dashboard/agt_abc123"
}

Webhook requests include a X-VITNA-Signature header (HMAC-SHA256) so you can verify authenticity. Configure your endpoint URL in the Dashboard → Alerts → Settings.

Rate Limits

These limits apply to event ingest (POST /api/ingest), per API key. Exceeded requests return 429. They are not an account-wide API budget: other endpoints are governed by their own per-IP limits rather than by your plan.

PlanRate LimitEvents / MonthHistory
Free100 req/min~10k/month (guideline)7 days
Pro1,000 req/min~1M/month (guideline)90 days
Team2,000 req/min~1M/month (guideline)90 days
Enterprise6,000 req/minNegotiatedCustom
💡 Batch requests via the events[] array count as 1 request regardless of event count. Use batching to maximize throughput on high-frequency agents.

Verify VITNA evidence yourself

One minute, no account, no trust in VITNA's servers required. Download the open-source verifier and a real signed sample bundle, then check the signature offline with Node 18 or newer:

curl -sO https://raw.githubusercontent.com/COSTRINITY/vitna-compliance-mcp/main/verify-evidence.mjs
curl -sO https://vitna.costrinity.xyz/sample-evidence.json
node verify-evidence.mjs sample-evidence.json

How verification works, in two tiers

Tier 1, the evidence package. A detached Ed25519 signature covers the whole package. Anyone can verify it offline with our published public key, with no VITNA account and no VITNA secret. This is the tier that supports non-repudiation of issuance.

Tier 2, each individual record. Every decision record is committed by a sha256 hash over its canonical JSON, and that ordered list of hashes sits inside the Ed25519-signed package. A third party can therefore verify each record, not just the package: recompute the record's hash and confirm it matches at its position. The verifier does this automatically and reports PASS or FAIL per record.

Separately, each record is integrity protected at write time with HMAC-SHA256. That is an internal check, verifiable only by VITNA because HMAC is symmetric, so we never describe it as independently verifiable or as non-repudiation. Bundles exported before the per-record hashes shipped still verify at the package level, and the verifier says so.

A VALID result proves the package was issued by VITNA and has not been altered since export, and that every record matches its committed hash. It does not prove the actions were performed or that the records are factually true. Change any byte in any record and that record reports FAIL and the overall verdict is INVALID.

Recomputing payload_sha256 (the pfa-v2 scheme)

Each decision record carries a payload_sha256 and a canon_version of pfa-v2. Here is the exact scheme so you can recompute it yourself. It is a sha256 (hex) over twelve fields joined with the pipe character, in this order, UTF-8 encoded, with no whitespace and no trailing separator. Null or absent values become the empty string.

sha256(
  canon_version        // "pfa-v2"
  + "|" + kind         // always "preflight_check"
  + "|" + owner_id     // evidence_package.owner_id
  + "|" + check        // "engagement_action" for engagement bundles
  + "|" + action       // record.action, "" if null
  + "|" + category     // engagement: evidence_package.session_id
  + "|" + decision     // record.decision
  + "|" + flagged      // "1" if decision !== "allow", else "0"
  + "|" + reason       // record.reason, "" if null
  + "|" + principal_id // "" for engagement bundles
  + "|" + effect       // record.effect
  + "|" + signed_at    // record.signed_at
)

Worked example, taken verbatim from the published sample-evidence.json (record 0). Canonical string:

pfa-v2|preflight_check|f46ba5dc-b77b-4fe0-ae3d-55e6204e3d66|engagement_action|dns.read example.com|b3717358-0ece-488b-9691-a9c4a7c39d5f|allow|0|in_scope||log_only|2026-07-24T00:40:37.048Z

sha256 -> 2b0f0d22a1a3cb4980981a12e67fa72e778ca3b0d21d322ca90eac1f578e1b2f

That matches the payload_sha256 on record 0 of the published sample. Note the two consecutive pipes before log_only: that is the empty principal_id.

Being precise about what this does and does not give you. payload_sha256 is a digest, not a signature, so recomputing it proves the record fields are internally consistent, not that VITNA issued them. The per-record assurance that a third party can rely on is record_hashes, because those sit inside the Ed25519-signed package. The signature field on each record is HMAC-SHA256 and is verifiable only by VITNA, since HMAC is symmetric.

Key transparency

The signing key is Ed25519, key_id 01833acd46d06ab4. The public key is published in more than one place, including infrastructure we do not control, so the copies can be compared:

Because the verifier ships with the key embedded and the same key is published on GitHub and npm, a mismatch between any of those copies and our API would be publicly visible. We are stating the honest limit here too: this is multi-location publication, not a formal transparency log or a third-party notary.

What VITNA sends and stores

Connecting the MCP server sends data to VITNA's cloud. For a compliance product that has to be explicit, so here it is.

What is sent on a check

Whatever you pass to the tool. For action_preflight that is the proposed action text, an optional structured payload, and an optional short action-type label, plus your owner id and API key in headers. For the compliance checks it is the fields that check needs, for example purpose and data categories.

What is stored

Less than what is sent. The signed decision record stores the action type label, the threat category and the pattern name that matched, the verdict and reason, timestamps, and owner and session identifiers. The raw action text and raw payload are not stored. Data principals are referenced by UUID only, and detected secrets and PII are redacted before anything is written.

Retention by tier, against the six-month baseline

We cite the EU AI Act requirement to retain automatically generated logs for at least six months. Stated plainly, here is which tiers meet that on their own and which do not:

TierEvent payloadsIncident recordsMeets 6-month log baseline?
Free7 days30 daysNo
Pro90 days1 yearNo, not for event payloads
Team90 days1 yearNo, not for event payloads
EnterpriseCustom per MSACustom per MSAOnly if contracted at 6 months or more

No tier below Enterprise retains event payloads in VITNA for six months, so do not rely on our hosted retention alone to satisfy a six-month obligation. The way any tier satisfies it is by exporting the Ed25519-signed evidence package and keeping it yourself. Once exported it is a self-contained, independently verifiable artifact that does not depend on our retention or on our servers continuing to exist.

Trial keys, and the zero-cloud alternative

With no credentials, the first MCP tool call self-provisions a restricted trial key against our API and caches it at ~/.vitna/credentials.json. That creates an account on our side. Trial keys are capped, return label-only results, and do not persist signed evidence until the account is claimed.

If you do not want events leaving your machine, use local mode: with storage_mode=local events stay on the operator's device and VITNA stores nothing server-side. The desktop app is the packaged form of that. The tradeoff is honest: in local mode we cannot produce a server-signed evidence package for you, because signing happens where the key is.

FAQ

Is VITNA a verifiable compliance receipt system?

Yes, for the signed evidence package model. Each checked action produces a decision record, and those records are packaged into an Ed25519-signed evidence package that functions as a verifiable compliance receipt: a third party can confirm offline, with our published public key and an open-source verifier, that the receipt was issued by VITNA, has not been altered, and that each individual record matches its committed hash. The documented limits still apply. Detection is heuristic pattern matching, VITNA is a cooperative guardrail the agent calls rather than an enforcement sandbox, and a valid receipt proves an action was checked and what the verdict was, not that the action was actually performed or that the underlying records are factually true.

Can I verify a single decision record on its own?

Yes. Every record is committed by sha256 over its canonical JSON inside the Ed25519-signed package, so you recompute that hash and confirm it appears at the right position in the signed list. The bundled verifier does this for you and prints PASS or FAIL per record.

Does VITNA block dangerous actions?

No, and we will not claim otherwise. VITNA evaluates and records, and returns allow, block, or hold. Your system enforces the decision out of process. Its purpose is not prevention, it is independently verifiable proof that an action was checked and what was decided.