Quickstart

Five minutes to your first verdict.

Boot the OSS edition clavenar‑lite, install one of the SDKs, wrap your Anthropic or OpenAI client, and watch every inspected tool call get logged to the hash‑chained ledger. Defaults to observe mode so you can wire it in without breaking anything.

Docs verified for v1.119.1 Edit this page Spec source
Provider

Pick once. Install, wrap, and first-call examples update together.

Prerequisites

  • Docker, a downloaded clavenar-lite binary, or a Fly.io account.
  • Node 20+ with pnpm, or Python 3.11+ with pip.
  • An Anthropic or OpenAI API key already configured for your app.
  • Port 8088 free locally, or set another listen port in your lite config.
Environment

Keep provider credentials and the optional Clavenar bearer in one shell session while you work through the page.

# Anthropic path
export ANTHROPIC_API_KEY="sk-ant-..."
export ANTHROPIC_MODEL="claude-opus-4-7"
export CLAVENAR_LITE_MODE="observe"
export CLAVENAR_LITE_TOKEN=""   # optional bearer
# OpenAI path
export OPENAI_API_KEY="sk-..."
export OPENAI_MODEL="gpt-4-turbo"
export CLAVENAR_LITE_MODE="observe"
export CLAVENAR_LITE_TOKEN=""   # optional bearer
1 · Happy path

Copy, run, verify.

Use Docker + TypeScript for the shortest deterministic path. The first-call script below forces one tool call, captures the verdict callback, then prints the correlation ID you use for the audit lookup.

1

Start lite

docker run --rm -p 8088:8088 \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.anthropic.com \
  -e CLAVENAR_LITE_MODE=observe \
  ghcr.io/vanteguardlabs/warden-lite:0.4.1
2

Check health

curl http://localhost:8088/health
3

Install SDKs

pnpm add @clavenar/agent-sdk @anthropic-ai/sdk
4

Run the script

# Save the TypeScript script from step 5 as quickstart-anthropic.ts
pnpm dlx tsx quickstart-anthropic.ts
5

Verify proof

export CORRELATION_ID="paste-from-script"
curl http://localhost:8088/audit/correlation/$CORRELATION_ID
curl http://localhost:8088/verify
1

Start lite

docker run --rm -p 8088:8088 \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.openai.com \
  -e CLAVENAR_LITE_MODE=observe \
  ghcr.io/vanteguardlabs/warden-lite:0.4.1
2

Check health

curl http://localhost:8088/health
3

Install SDKs

pnpm add @clavenar/agent-sdk openai
4

Run the script

# Save the TypeScript script from step 5 as quickstart-openai.ts
pnpm dlx tsx quickstart-openai.ts
5

Verify proof

export CORRELATION_ID="paste-from-script"
curl http://localhost:8088/audit/correlation/$CORRELATION_ID
curl http://localhost:8088/verify
Success signal

The script prints CORRELATION_ID=..., and both audit commands return JSON without a transport error.

If it fails

Run the sections below in order. Each step has one health signal and one targeted failure check.

2 · Boot

Pick the shape that fits your stack.

Clavenar‑lite is one Rust binary that fronts your agent's MCP traffic. Run it locally for development; deploy on Fly.io for a shared environment; or move to the full multi‑service edition when you need Brain / Policy / HIL / Ledger / Identity split out.

# Docker + Anthropic upstream
docker run --rm -p 8088:8088 \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.anthropic.com \
  -e CLAVENAR_LITE_MODE=observe \
  ghcr.io/vanteguardlabs/warden-lite:0.4.1
# Docker + OpenAI upstream
docker run --rm -p 8088:8088 \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.openai.com \
  -e CLAVENAR_LITE_MODE=observe \
  ghcr.io/vanteguardlabs/warden-lite:0.4.1
# Host binary (musl, no deps)
curl -sSL https://github.com/clavenar/clavenar-lite/releases/latest/download/clavenar-lite-linux-x86_64 \
  -o clavenar-lite
chmod +x clavenar-lite
CLAVENAR_LITE_MODE=observe ./clavenar-lite
# Fly.io
fly launch --name my-clavenar-lite --no-deploy
fly secrets set CLAVENAR_LITE_MODE=observe
fly deploy
Health check
curl -s http://localhost:8088/health | jq .
Sample response
{
  "status": "ok",
  "mode": "observe",
  "ledger": "ready"
}
Success signal

clavenar-lite listening on :8088 appears in logs and /health returns status: ok.

If it fails

Check for a port conflict on 8088, a missing upstream URL, or a mismatched bearer token.

3 · Install

One SDK package, your existing client.

The SDKs are peer‑agnostic: install whichever provider client you already use. The wrapper auto‑detects shape from the method surface.

# TypeScript + Anthropic
pnpm add @clavenar/agent-sdk @anthropic-ai/sdk
# TypeScript + OpenAI
pnpm add @clavenar/agent-sdk openai
# Python + Anthropic
pip install clavenar-agent-sdk anthropic
# Python + OpenAI
pip install clavenar-agent-sdk openai
Success signal

Your lockfile adds the Clavenar wrapper plus exactly the provider SDK you already call in production.

If it fails

Confirm Node 20+ for TypeScript, Python 3.11+ for Python, and that your package manager is running inside the agent project.

4 · Wrap

Three lines around your client.

Every tool call the model emits is forwarded to clavenar‑lite for inspection before your tool‑execution loop sees it. Deny verdicts throw ClavenarDenied; yellow‑tier parks throw ClavenarPending.

import Anthropic from '@anthropic-ai/sdk';
import { clavenarWrap } from '@clavenar/agent-sdk';

const client = clavenarWrap(new Anthropic(), {
  endpoint: 'http://localhost:8088',
  token: process.env.CLAVENAR_LITE_TOKEN || undefined,
  mode: 'observe',
});
import OpenAI from 'openai';
import { clavenarWrap } from '@clavenar/agent-sdk';

const client = clavenarWrap(new OpenAI(), {
  endpoint: 'http://localhost:8088',
  token: process.env.CLAVENAR_LITE_TOKEN || undefined,
  mode: 'observe',
});
from anthropic import AsyncAnthropic
import os
from clavenar_agent_sdk import ClavenarOptions, clavenar_wrap

client = clavenar_wrap(
    AsyncAnthropic(),
    ClavenarOptions(
        endpoint="http://localhost:8088",
        token=os.environ.get("CLAVENAR_LITE_TOKEN") or None,
        mode="observe",
    ),
)
from openai import AsyncOpenAI
import os
from clavenar_agent_sdk import ClavenarOptions, clavenar_wrap

client = clavenar_wrap(
    AsyncOpenAI(),
    ClavenarOptions(
        endpoint="http://localhost:8088",
        token=os.environ.get("CLAVENAR_LITE_TOKEN") or None,
        mode="observe",
    ),
)
Success signal

Your app still calls client.messages.create or client.chat.completions.create; only construction changed.

If it fails

Confirm the wrapped client object is the one passed into your agent loop, not the original provider client.

5 · First call

Force one inspected tool call.

The examples below use the SDK verdict callback to capture the correlation ID in observe mode. Switch to enforce after the policy has been tuned against real traffic.

Mode Verdict behavior Use when
observe Provider response passes through; onVerdict records allow, would_deny, or pending. First integration, policy tuning, production shadow mode.
enforce Deny throws ClavenarDenied; yellow tier throws ClavenarPending until the operator decides. Policies are tuned and the agent can safely block side effects.
# quickstart-anthropic.ts
import Anthropic from '@anthropic-ai/sdk';
import { clavenarWrap, ClavenarDenied, ClavenarPending } from '@clavenar/agent-sdk';

let correlationId = '';

const client = clavenarWrap(new Anthropic(), {
  endpoint: 'http://localhost:8088',
  token: process.env.CLAVENAR_LITE_TOKEN || undefined,
  mode: 'observe',
  onVerdict: (verdict, ctx) => {
    correlationId = verdict.correlationId || correlationId;
    console.log(`[clavenar] ${verdict.kind} ${ctx.toolName} ${correlationId}`);
  },
});

try {
  await client.messages.create({
    model: process.env.ANTHROPIC_MODEL || 'claude-opus-4-7',
    max_tokens: 256,
    tools: [{
      name: 'lookup_account',
      description: 'Look up account risk for a customer account.',
      input_schema: {
        type: 'object',
        properties: { account_id: { type: 'string' } },
        required: ['account_id'],
      },
    }],
    tool_choice: { type: 'tool', name: 'lookup_account' },
    messages: [{ role: 'user', content: 'Use lookup_account for acct_123 and summarize risk.' }],
  });
} catch (e) {
  if (e instanceof ClavenarPending) correlationId = e.correlationId;
  else if (e instanceof ClavenarDenied) correlationId = e.correlationId || correlationId;
  else throw e;
}

console.log(`CORRELATION_ID=${correlationId}`);
# quickstart-openai.ts
import OpenAI from 'openai';
import { clavenarWrap, ClavenarDenied, ClavenarPending } from '@clavenar/agent-sdk';

let correlationId = '';

const client = clavenarWrap(new OpenAI(), {
  endpoint: 'http://localhost:8088',
  token: process.env.CLAVENAR_LITE_TOKEN || undefined,
  mode: 'observe',
  onVerdict: (verdict, ctx) => {
    correlationId = verdict.correlationId || correlationId;
    console.log(`[clavenar] ${verdict.kind} ${ctx.toolName} ${correlationId}`);
  },
});

try {
  await client.chat.completions.create({
    model: process.env.OPENAI_MODEL || 'gpt-4-turbo',
    messages: [{ role: 'user', content: 'Use lookup_account for acct_123 and summarize risk.' }],
    tools: [{
      type: 'function',
      function: {
        name: 'lookup_account',
        description: 'Look up account risk for a customer account.',
        parameters: {
          type: 'object',
          properties: { account_id: { type: 'string' } },
          required: ['account_id'],
        },
      },
    }],
    tool_choice: { type: 'function', function: { name: 'lookup_account' } },
  });
} catch (e) {
  if (e instanceof ClavenarPending) correlationId = e.correlationId;
  else if (e instanceof ClavenarDenied) correlationId = e.correlationId || correlationId;
  else throw e;
}

console.log(`CORRELATION_ID=${correlationId}`);
# quickstart_anthropic.py
import asyncio
import os
from anthropic import AsyncAnthropic
from clavenar_agent_sdk import ClavenarDenied, ClavenarOptions, ClavenarPending, clavenar_wrap

correlation_id = ""

async def on_verdict(verdict, ctx):
    global correlation_id
    correlation_id = getattr(verdict, "correlation_id", None) or correlation_id
    print(f"[clavenar] {verdict.kind} {ctx.tool_name} {correlation_id}")

async def main():
    global correlation_id
    client = clavenar_wrap(
        AsyncAnthropic(),
        ClavenarOptions(
            endpoint="http://localhost:8088",
            token=os.environ.get("CLAVENAR_LITE_TOKEN") or None,
            mode="observe",
            on_verdict=on_verdict,
        ),
    )
    try:
        await client.messages.create(
            model=os.environ.get("ANTHROPIC_MODEL", "claude-opus-4-7"),
            max_tokens=256,
            tools=[{
                "name": "lookup_account",
                "description": "Look up account risk for a customer account.",
                "input_schema": {
                    "type": "object",
                    "properties": {"account_id": {"type": "string"}},
                    "required": ["account_id"],
                },
            }],
            tool_choice={"type": "tool", "name": "lookup_account"},
            messages=[{"role": "user", "content": "Use lookup_account for acct_123 and summarize risk."}],
        )
    except ClavenarPending as e:
        correlation_id = e.correlation_id
    except ClavenarDenied as e:
        correlation_id = e.correlation_id or correlation_id
    print(f"CORRELATION_ID={correlation_id}")

asyncio.run(main())
# quickstart_openai.py
import asyncio
import os
from openai import AsyncOpenAI
from clavenar_agent_sdk import ClavenarDenied, ClavenarOptions, ClavenarPending, clavenar_wrap

correlation_id = ""

async def on_verdict(verdict, ctx):
    global correlation_id
    correlation_id = getattr(verdict, "correlation_id", None) or correlation_id
    print(f"[clavenar] {verdict.kind} {ctx.tool_name} {correlation_id}")

async def main():
    global correlation_id
    client = clavenar_wrap(
        AsyncOpenAI(),
        ClavenarOptions(
            endpoint="http://localhost:8088",
            token=os.environ.get("CLAVENAR_LITE_TOKEN") or None,
            mode="observe",
            on_verdict=on_verdict,
        ),
    )
    try:
        await client.chat.completions.create(
            model=os.environ.get("OPENAI_MODEL", "gpt-4-turbo"),
            messages=[{"role": "user", "content": "Use lookup_account for acct_123 and summarize risk."}],
            tools=[{
                "type": "function",
                "function": {
                    "name": "lookup_account",
                    "description": "Look up account risk for a customer account.",
                    "parameters": {
                        "type": "object",
                        "properties": {"account_id": {"type": "string"}},
                        "required": ["account_id"],
                    },
                },
            }],
            tool_choice={"type": "function", "function": {"name": "lookup_account"}},
        )
    except ClavenarPending as e:
        correlation_id = e.correlation_id
    except ClavenarDenied as e:
        correlation_id = e.correlation_id or correlation_id
    print(f"CORRELATION_ID={correlation_id}")

asyncio.run(main())
Expected output

A line like [clavenar] allow lookup_account 3ab2..., followed by CORRELATION_ID=3ab2.... In observe mode, would-deny signals still pass through so your agent keeps running.

Success signal

onVerdict or on_verdict fires before your tool loop handles the call.

If it fails

Confirm the model was forced to call lookup_account, and that the wrapped client is used for the provider call.

6 · Audit

Every call has a receipt.

Each request gets a UUID‑v4 correlation_id stamped by the proxy. The ledger writes one canonical JSON entry per terminal pipeline outcome, and each row's entry_hash chains SHA‑256(prev_hash || row_bytes) so tampering breaks the chain at the next /verify walk.

# Pull the row by correlation id
curl http://localhost:8088/audit/correlation/$CORRELATION_ID

# Walk the chain end-to-end
curl http://localhost:8088/verify
Sample audit row
{
  "correlation_id": "3ab2...",
  "agent_id": "quickstart-local",
  "tool_type": "lookup_account",
  "authorized": true,
  "entry_hash": "b89d...",
  "prev_hash": "4c21..."
}
Browser proof

Paste the row into the verifier.

The audit verifier recomputes receipt hashes in the browser, so an auditor can inspect the proof without trusting the server that served the row.

Open Audit verifier
Success signal

/audit/correlation/{id} returns at least one row with the same correlation ID, and /verify reports a valid chain.

If it fails

Search by the exact ID printed by the script. If the chain does not verify, open the browser verifier and inspect the first broken row.

The full receipts schema lives in the wire format spec.

7 · Troubleshooting

Fast checks when the first verdict does not appear.

Most first-run failures are environment mismatches: port already bound, token mismatch, upstream URL missing, provider credentials missing, or the app still calling the unwrapped provider client.

Port conflict

address already in use means another process owns 8088. Stop it or run lite on another port and pass that endpoint into the SDK wrapper.

Missing token

A 401 usually means the SDK has CLAVENAR_LITE_TOKEN but lite was started without the same token, or the reverse.

No audit rows

Confirm the app uses the wrapped client object, not the original provider client. Then search by the correlationId or correlation_id from the callback or exception.

No tool call

Use the forced tool_choice example once. If that works, remove the force and tune your prompt/tool descriptions.

Unexpected denies

Run CLAVENAR_LITE_MODE=observe, collect would_deny rows, then adjust policy before flipping to enforce.

Provider credentials

If the provider returns auth errors before Clavenar logs anything, verify ANTHROPIC_API_KEY or OPENAI_API_KEY in the same shell.

8 · Production next

Choose the next operating shape.

After the first local verdict, decide whether to keep lite embedded, deploy lite as a shared gateway, or move to the full stack for operator approvals, identity, and HA ledger storage.

Previous ← Overview
Next Concepts →