Trust boundaries

Public ingress is intentionally tiny.

The proxy is the only data‑plane endpoint an agent should call directly. Brain, Policy, HIL, Ledger, and Identity stay private or operator‑gated, so auth failures fail closed before side effects reach an upstream tool.

Agent MCP client

Sends JSON‑RPC plus optional callback, grant, and attestation headers.

Data plane POST /mcp

Requires mTLS SVID or lite bearer auth; stamps the correlation id.

Private Brain + Policy

Service-to-service calls only; produces semantic and Rego verdict signals.

Operator HIL + Identity

Approvals and credential minting stay behind operator/session controls.

Proof Ledger export

Verification and correlation replay expose audit evidence, not write paths.

Matrix

Endpoint, auth, success, failure, proof row.

Use this table as the integration index before jumping into a per-service contract. The proof column tells you which ledger event should exist when the request finishes.

Filter by boundary or operational use. Rows keep the same wire contracts; filters only narrow the table.

Service Endpoint Auth Success Failure Proof row
Proxy Data plane POST /mcp mTLS SVID or lite bearer 200 upstream response, 202 pending 403 deny, 429 velocity, 503 dependency proxy.verdict, upstream.outcome
Brain Private POST /inspect Private service network InspectionResponse 503 model/provider unavailable brain.inspection
Policy Private POST /eval mTLS service caller allow, deny, or review 400 malformed input, 503 store unavailable policy.evaluated
HIL Operator POST /pending, POST /decide Proxy service token, operator session Pending created or decision accepted 401, 404, expiry closed hil.created, hil.approved, hil.denied
Ledger Proof POST /log, GET /verify mTLS service caller, read auth on console Row appended or chain valid 409 chain break, 503 store unavailable Canonical hash-chain row
Identity Operator POST /svid, POST /grant Admin token, OIDC token, SVID Certificate or delegation grant 401, attestation or envelope mismatch identity.svid_issued, identity.grant_issued
Envelope

JSON‑RPC 2.0 + correlation header.

Every MCP request is JSON‑RPC 2.0. The proxy stamps a UUID‑v4 correlation_id on ingress and threads it through every downstream call so a single ledger row joins the proxy / brain / policy / HIL events.

{
  "jsonrpc": "2.0",
  "id":      1,
  "method":  "call_tool",
  "params":  { "name": "send_email", "arguments": { /* … */ } }
}

Headers (proxy ingress):

  • X‑Clavenar‑Correlation‑Id — proxy‑stamped, propagates downstream + into the ledger
  • X‑Clavenar‑Demo‑Prefix — 8‑hex from the demo‑session JWT (console‑demo only)
  • X‑Clavenar‑Callback‑URL — async HIL: where to POST the operator decision
  • X‑Clavenar‑Grant — delegation grant from /grant (multi‑tenant)
  • X‑Clavenar‑Attestation — per‑request override of attestation claims
Runnable calls

Copy, set the host, and keep the correlation id.

These examples use placeholder hosts and credentials so they can be adapted to Lite, dev, or a private deployment. Copy buttons are added automatically by the docs runtime.

curl --http1.1 https://proxy.internal.example/mcp \
  --cert ./agent-svid.pem \
  --key ./agent-svid-key.pem \
  --cacert ./bundle.pem \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "call_tool",
    "params": {
      "name": "refund.issue",
      "arguments": { "amount": 8500, "currency": "USD" }
    }
  }'
curl https://localhost:8787/mcp \
  -H 'Authorization: Bearer '"$CLAVENAR_LITE_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'X-Clavenar-Callback-URL: https://agent.example/hil/callback' \
  -d '{
    "jsonrpc": "2.0",
    "id": "demo-1",
    "method": "call_tool",
    "params": {
      "name": "crm.update_customer",
      "arguments": { "customer_id": "cus_123", "tier": "enterprise" }
    }
  }'
CORRELATION_ID="3ab2e8c2-0000-4000-9000-8fb77a123456"

curl "https://console.internal.example/api/audit/correlation/${CORRELATION_ID}" \
  -H 'Authorization: Bearer '"$OPERATOR_TOKEN"

curl "https://ledger.internal.example/verify" \
  --cert ./service-svid.pem \
  --key ./service-svid-key.pem \
  --cacert ./bundle.pem
clavenar‑proxy

Data plane · POST /mcp

The agent's MCP transport target. mTLS client cert (SVID) or bearer token (lite) authenticates the agent. The proxy fans out to Brain → Policy → HIL → Ledger and returns a verdict that proxies the upstream response (allow) or fails closed (deny / pending).

  • Auth: mTLS (proxy) or Authorization: Bearer (lite)
  • Verdict on success: upstream MCP response, plus X‑Clavenar‑Correlation‑Id
  • Verdict on deny: 403 with JSON body {error, intent_category, reasoning, correlation_id}
  • Verdict on park: 202 with JSON body {status: "pending", correlation_id, ttl_secs}
  • Rate limit: 429 with {error, scope, key, retry_after_secs, correlation_id}
POST /mcp
X-Clavenar-Correlation-Id: auto
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "call_tool",
  "params": {
    "name": "refund.issue",
    "arguments": { "amount": 8500 }
  }
}
HTTP/1.1 200 OK
X-Clavenar-Correlation-Id: 3ab2...

{ "jsonrpc": "2.0", "id": 1, "result": { /* upstream */ } }
HTTP/1.1 403 Forbidden

{
  "error": "policy_denied",
  "intent_category": "Privileged Action",
  "reasoning": "refund.issue over threshold",
  "correlation_id": "3ab2..."
}
HTTP/1.1 202 Accepted

{
  "status": "pending",
  "correlation_id": "3ab2...",
  "ttl_secs": 300
}

Source: clavenar‑proxy README · clavenar‑lite README for the OSS variant.

clavenar‑brain

Semantic eval · POST /inspect

Returns InspectionResponse: classifier verdict + persona drift score + injection signal. The proxy folds these into the Policy input.

# request
{
  "agent_id":       "agent-001",
  "jsonrpc":        "2.0",
  "method":         "call_tool",
  "params":         { /* … */ },
  "id":             1,
  "correlation_id": "e3b0c44…"
}

# response
{
  "authorized":           true,
  "reason":               "Standard tool call",
  "intent_category":      "Direct Execution",
  "persona_drift_score":  0.12,
  "injection_detected":   false,
  "injection_confidence": 0.0
}

Full schema (cache layers, mock‑mode heuristics, model brokering) in the clavenar‑brain README and accuracy benchmark in BENCHMARK.md.

clavenar‑policy‑engine

Governance · POST /eval

Pure‑Rust Rego evaluator (regorus). Input is the merged {request, brain_signals, attestation, agent_envelope} object; output is one of allow / deny / review + a forensic event payload.

  • clavenar.authz.allow — must be true for the request to pass
  • clavenar.authz.deny set with reasons — fails closed
  • clavenar.authz.review set — yellow tier (HIL pending)
  • clavenar.forensic.event — written to ledger regardless of verdict

Starter policies under policies/templates/: pii_egress, prod_db_writes, money_moves, agent_impersonation, prompt_injection, off_hours_actions, rate_limit_review.

clavenar‑hil

Human approval · POST /pending + POST /decide

Yellow‑tier verdicts park here. Sandbox annotates predicted blast radius; the operator decides via the console, Slack deep‑link, or — async — via the SDK resolve() helper backed by the X‑Clavenar‑Callback‑URL contract.

  • POST /pending — proxy enqueues; returns {correlation_id, ttl_secs}
  • POST /decide — operator approves / denies; bearer‑token gated
  • GET /pending — list active (operator UI / agent poll)
  • GET /pending/{correlation_id} — single‑row state
POST /pending
{
  "correlation_id": "3ab2...",
  "method": "refund.issue",
  "risk_summary": "money movement over $5,000",
  "ttl_secs": 300
}
POST /decide
{
  "correlation_id": "3ab2...",
  "decision": "approve",
  "decider_note": "Customer support ticket verified"
}

Decision payload includes decided_by (NameID from console session), decider_note, decided_at. All four fields land in the ledger row.

clavenar‑ledger

Forensic store · /log · /verify · /audit/*

Append‑only. Each row contains the full request, verdict, policy version, operator NameID (if HIL), and a chain pointer SHA‑256(prev_hash · canonical_row_bytes).

  • POST /log — write a forensic event (proxy / policy / HIL caller)
  • GET /verify — walk the chain end‑to‑end; 200 with {height, ok: true} or 409 with the first broken row
  • GET /audit/correlation/{uuid} — pull all rows joined to a single proxy request
  • GET /audit/agents/{agent_id}/narrative?window=7d — the "story view"
  • GET /export/iceberg?from=&to= — Parquet cold tier (S3)
  • egress.streams.{splunk,datadog,generic} — push targets configured per‑sink
GET /verify

{
  "valid": true,
  "entries_checked": 630000,
  "head_hash": "a5d3..."
}
GET /audit/correlation/3ab2...

[
  { "method": "proxy.verdict", "authorized": false },
  { "method": "hil.approved", "decided_by": "saml:finance-oncall" },
  { "method": "upstream.outcome", "status": 200 }
]

Backend is pluggable: SqliteLedgerStore (default) or PostgresLedgerStore behind the postgres Cargo feature; the chain bytes are byte‑identical across both.

Trace

One call through every contract.

A single high‑risk tool call should be explainable from ingress to upstream outcome. Use the correlation id as the join key and expect these ledger rows in order.

1 Ingress

POST /mcp accepts JSON‑RPC and returns 202 when Policy asks for review.

proxy.verdict
2 Signals

Brain scores intent/drift/injection, then Policy evaluates the merged signal object.

brain.inspection + policy.evaluated
3 Review

POST /pending parks the call; POST /decide captures the operator NameID and note.

hil.created + hil.approved
4 Replay

GET /audit/correlation/{uuid} returns the joined rows for incident review.

upstream.outcome
5 Verify

GET /verify walks the hash chain and proves those rows were not rewritten later.

{ "valid": true }
clavenar‑identity

Trust roots · POST /svid + POST /grant + POST /actor-token

The only component that mints credentials. Every other service consumes; nothing else issues. Identity is a single failure domain on purpose — rotating the trust root is one place to reason about.

  • POST /svid — agent enrolment; mints an SVID (mTLS client cert) keyed on (tenant, agent_name)
  • POST /grant — exchange OIDC id_token + agent SVID → delegation grant; intersects with the agent's registered envelope
  • POST /sign — sign a JWT for a registered agent (delegation, attestation, etc.)
  • POST /actor-token + /actor-token/redeem — A2A federation across tenants
  • GET /.well-known/spiffe-bundle — SPIFFE bundle for federation peers
  • POST /agents + lifecycle — the WAO (agent onboarding) registry
All services

Uniform /health · /readyz · /metrics shape.

Every service hits the same trio with the same wire shape so Helm probes, dashboards, and Prometheus configs are one set of YAML. Metric naming is clavenar_<service>_<event>_<unit>.

  • GET /health — process up; 200 always
  • GET /readyz — dependencies reachable; 503 when not ready
  • GET /metrics — Prometheus text exposition format
Retries

Replay only when the contract says it is safe.

Use the correlation id as the idempotency key for client retries. Treat policy denials and HIL pendings as terminal from the caller's perspective until a decision or callback arrives.

Surface Retry behavior Idempotency rule Client action
POST /mcp Retry 503 and network timeouts with jitter; obey 429 retry_after_secs. Reuse X-Clavenar-Correlation-Id when retrying the same tool call. Stop on 403; park on 202 until callback or HIL poll resolves.
POST /pending Retry only when the proxy did not receive a response. One pending row per correlation id; duplicate creates should return the existing state. Keep the original TTL and callback URL; do not mint a new correlation id.
POST /decide Retry 503; do not retry 401, 403, or expired rows. The first accepted decision wins; later writes must fail closed. After success, replay the correlation id to confirm hil.approved or hil.denied.
GET /verify Safe to repeat; use normal dashboard polling cadence. Read-only. The response describes the current ledger head. Treat 409 as an incident and stop promotion until investigated.
Errors

Status codes you'll actually see.

Status code carries the type; the JSON body carries the reason and a stable error code for programmatic handling.

  • 200 — verdict allow (or would_deny in observe mode)
  • 202 — verdict park — pending operator decision; correlation id returned
  • 400 — malformed envelope / unsupported method
  • 401 / 403 — auth failure (no SVID, expired grant, scope outside envelope, attestation fail)
  • 403 deny — policy verdict; {error, intent_category, reasoning, correlation_id}
  • 409 — chain integrity broken (/verify only)
  • 429 — rate limit; {error, scope, key, retry_after_secs, correlation_id}
  • 503 — downstream not ready (Brain / Policy / Ledger unreachable)
Previous ← Concepts
Next Recipes →