Clavenar Lite quickstart

Five minutes to your first local verdict.

Boot the single-binary 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. This page contains no Kubernetes or full-cluster operator steps.

Docs verified for v1.250.5 Spec source
Provider

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

Prerequisites

  • A systemd-based x86_64 or arm64 Linux host for the native path, or Docker as an alternative.
  • 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.

This guide installs Clavenar Lite only. To install the multi-service customer stack and full admin Console on an existing Kubernetes or K3s cluster, use the separate Full cluster install.

This page is governed by clavenar.executable-documentation/v1; every listed SDK and quickstart path runs against both the staged candidate and immutable public release. Lite package, binary, and image publication is governed by clavenar.external-install/v1.

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 the checksum-verified native service + TypeScript for the shortest host 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

curl -fsSL https://github.com/clavenar/clavenar-lite/releases/download/v1.0.0/install.sh | \
  sudo sh -s -- --upstream https://api.anthropic.com
2

Check health

curl http://localhost:8088/health
3

Install SDKs

pnpm add @clavenar/agent-sdk@1.6.3 @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"
sudo -u clavenar-lite clavenar-lite audit \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db anonymous
sudo -u clavenar-lite clavenar-lite verify \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db
# Confirm the audit output contains $CORRELATION_ID.
1

Start lite

curl -fsSL https://github.com/clavenar/clavenar-lite/releases/download/v1.0.0/install.sh | \
  sudo sh -s -- --upstream https://api.openai.com
2

Check health

curl http://localhost:8088/health
3

Install SDKs

pnpm add @clavenar/agent-sdk@1.6.3 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"
sudo -u clavenar-lite clavenar-lite audit \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db anonymous
sudo -u clavenar-lite clavenar-lite verify \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db
# Confirm the audit output contains $CORRELATION_ID.
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 a local runtime.

Clavenar‑lite is one Rust binary that fronts your agent's MCP traffic. The native installer is the default host path: no Docker, Compose, Rust toolchain, OPA, or database server. Docker remains an isolated alternative. Hosted Lite deployment belongs in Recipes.

# Protected v1.0.0 · detects x86_64/aarch64 and verifies checksum
curl -fsSL https://github.com/clavenar/clavenar-lite/releases/download/v1.0.0/install.sh | \
  sudo sh -s -- --upstream https://mcp.your-company.com/rpc

systemctl status clavenar-lite
curl http://127.0.0.1:8088/health

The service runs as clavenar-lite, listens only on loopback, stores configuration in /etc/clavenar-lite/config.env, and persists its SQLite ledger under /var/lib/clavenar-lite. Rerun the installer for an in-place upgrade.

# Docker + Anthropic upstream
docker run --rm --name clavenar-lite -p 8088:8088 \
  -v clavenar-lite-data:/var/lib/clavenar-lite \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.anthropic.com \
  -e CLAVENAR_LITE_MODE=observe \
  -e CLAVENAR_LITE_LEDGER=/var/lib/clavenar-lite/ledger.db \
  ghcr.io/clavenar/clavenar-lite:1.0.0
# Docker + OpenAI upstream
docker run --rm --name clavenar-lite -p 8088:8088 \
  -v clavenar-lite-data:/var/lib/clavenar-lite \
  -e CLAVENAR_LITE_UPSTREAM_URL=https://api.openai.com \
  -e CLAVENAR_LITE_MODE=observe \
  -e CLAVENAR_LITE_LEDGER=/var/lib/clavenar-lite/ledger.db \
  ghcr.io/clavenar/clavenar-lite:1.0.0
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

Run journalctl -u clavenar-lite, then check for a port conflict on 8088, a malformed 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@1.6.3 @anthropic-ai/sdk
# TypeScript + OpenAI
pnpm add @clavenar/agent-sdk@1.6.3 openai
# Python + Anthropic
pip install clavenar-agent-sdk==1.5.3 anthropic
# Python + OpenAI
pip install clavenar-agent-sdk==1.5.3 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.

# Read the retained rows and confirm the printed correlation id.
sudo -u clavenar-lite clavenar-lite audit \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db anonymous

# Walk the chain end-to-end.
sudo -u clavenar-lite clavenar-lite verify \
  --ledger /var/lib/clavenar-lite/clavenar-lite.db
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

clavenar-lite audit anonymous returns at least one row with the same correlation ID, and clavenar-lite 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

Set CLAVENAR_LITE_MODE=observe in /etc/clavenar-lite/config.env, restart the service, 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.

Data-preserving uninstall
curl -fsSL https://github.com/clavenar/clavenar-lite/releases/download/v1.0.0/uninstall.sh | sudo sh
Explicit full purge
curl -fsSL https://github.com/clavenar/clavenar-lite/releases/download/v1.0.0/uninstall.sh | \
  sudo sh -s -- --purge
Previous ← Overview
Next Concepts →