Overview
KEEL is a vendor-neutral runtime trust layer for AI agents. It sits between an agent and
the real world: every proposed action is verified before it executes, decided as
ALLOW / BLOCK / ESCALATE, and recorded as a signed,
tamper-proof certificate.
The core principle: the model proposes; it never adjudicates its own trustworthiness. Deterministic checks and calibrated evidence decide — an optional LLM reviewer may only lower a decision, never raise it. The system fails closed.
Install
KEEL is a self-contained Python package — no database or external services required to start.
pip install keel-trust # installs as keel-trust, imports as `keel`
keel serve # starts gateway + console + docs on :8347
keel serve --port 9000 --sandbox # with the demo worlds
keel version
Open http://localhost:8347 for the site, /app for the operator
console, and /docs for these docs. Data is stored under ~/.keel/data
by default (override with KEEL_DATA_DIR).
Quickstart
1 · Get your API key
Sign in at /app and open Account (last item in the left-hand nav). Copy the key shown at the top and put it in your environment — the SDK and every REST example below read it from there.
export KEEL_API_KEY=keel_ak_... # from /app → Account
2 · Point the SDK at your deployment
Use the URL you signed in to. If you run keel serve yourself, that is
http://localhost:8347 instead.
export KEEL_URL=https://keel.best # or http://localhost:8347 when self-hosting
3 · Put an agent behind the gateway
Three steps: register once, check before acting, report the outcome. With the Python SDK a decorator does all three.
import os
from keel.sdk import KeelGuard, GuardRejected
# api_key defaults to $KEEL_API_KEY; pass api_key="..." to override
guard = KeelGuard(os.environ["KEEL_URL"], agent_id="support-bot")
guard.register(name="Support Bot", action_classes={
"send_reply": {"risk": "low"},
"issue_refund": {"risk": "high", "budget_per_day": 500,
"requires_evidence": True}})
@guard.protect("issue_refund", cost=lambda amount, **kw: amount)
def issue_refund(customer_id, amount):
... # only runs on ALLOW; the wrapper reports success/failure automatically
try:
issue_refund("cus_123", 49.0)
except GuardRejected as e:
print("KEEL blocked it:", e.decision["reasons"])
shadow_mode=False at registration to enforce.Core concepts
| Term | Meaning |
|---|---|
| agent | Any AI system that takes actions. Registered once with the action classes it may attempt. |
| action class | A named capability (send_email,
run_command) with a risk level, optional budget, rate limit, and evidence
requirement. |
| risk | low / medium / high /
critical — sets the success floor and human-oversight requirement. |
| tripwire | An irreversible-catastrophe pattern (DB drop, funds transfer, recursive delete…) hard-blocked in every mode. |
| certificate | The signed, logged record of a single decision. |
| outcome | What actually happened after an allowed action. Feeds calibration. Only externally-verified outcomes earn autonomy. |
Decisions & verdicts
Every check returns one decision:
| Decision | Meaning |
|---|---|
| ALLOW | All checks pass and the agent has earned the required trust — proceed. |
| BLOCK | A tripwire, policy rule, or grounding check failed — refused. |
| ESCALATE | Plausible but not yet trusted (cold start, risky, or novel) — sent to the human approval queue. |
| SHADOW | Agent is in shadow mode — recorded and signed, but not enforced (except tripwires). |
Checks run cheapest-and-most-fatal first: tripwires → schema → policy/budget → citation integrity → consistency → destructive-intent → calibrated confidence. The deterministic path completes in roughly two milliseconds.
Earned autonomy
Autonomy is earned per (agent, action class) from verified outcomes — never granted by configuration. KEEL computes an anytime-valid lower bound on the success rate (a betting confidence sequence, valid under continuous monitoring), audits it for drift with a Page-Hinkley detector, and holds an expected-harm ceiling with conformal risk control that self-tightens the required floor when allowed actions cause harm.
| Tier | Behavior |
|---|---|
| T1 | Certify & recommend — a human executes / approves. |
| T2 | Auto-execute, notify — earned from ≥10 externally-verified successes at a calibrated floor ≥0.80. |
| T3 | Auto-execute, silent — earned from ≥30 verified successes at floor ≥0.95, zero harm. |
Certificates & ledger
Every decision is an Ed25519-signed certificate appended to a Merkle transparency log. Altering any historical certificate breaks the chain — which is what makes a KEEL record admissible in a postmortem, an audit, or a regulatory review. Fetch and verify any certificate, with its inclusion proof, via the API or the console's Ledger view.
The open certificate schema is published at
/api/schema/certificate.
Verify a certificate offline — without trusting us
This is what makes a KEEL certificate different from a log line: anyone holding the JSON and the authority's public key can check it, with no server, no account, and no network. If our infrastructure disappeared tomorrow, every certificate ever issued would still verify.
# save any certificate (bare, API response, or audit-pack sample)
curl -s "$KEEL_URL/api/certificates/keel:cert:XXXX?domain=gateway" \
-H "Authorization: Bearer $KEEL_API_KEY" > cert.json
# verify it anywhere — signature, payload hash, and Merkle inclusion
keel verify cert.json --key <authority_public_key_hex>
# ✓ signature
# ✓ leaf_matches_payload
# ✓ inclusion_proof
# result : VALID
Change one field of the certificate and the signature fails; swap in another entry's
inclusion proof and the leaf check fails. Pass --root to pin the log root
you saw earlier, and --json for a machine-readable report. The verifier is
deliberately dependency-free (keel/cert/verifier.py) so it can be reviewed
and vendored by a third party.
Agent passports — trust that travels
Cold start is where every trust system fails: an agent with thousands of verified outcomes behind one deployment starts from zero everywhere else. A passport is the agent's earned record — per action class: n, successes, harms, externally-verified count — signed by the issuing deployment's authority and verifiable offline like any KEEL artifact.
# issuing side: export the agent's earned record
curl -s -X POST "$KEEL_URL/api/gateway/agents/support-bot/passport" \
-H "Authorization: Bearer $KEEL_API_KEY" > passport.json
# receiving side: due diligence, then adopt with the issuer's key
keel passport verify passport.json --key <issuer_public_key_hex>
curl -s -X POST "$KEEL_URL/api/gateway/passport/import" \
-H "Authorization: Bearer $KEEL_API_KEY" -H 'Content-Type: application/json' \
-d "{\"passport\": $(cat passport.json), \"issuer_key\": \"<hex>\"}"
The statistical rules are the point, and they are deliberately conservative:
- Imported evidence is discounted 50% and capped, then held to the same Clopper-Pearson bound as local evidence — a foreign record pays a transfer price.
- It can bridge cold start for medium-risk actions only. High and critical actions require locally earned evidence, always.
- Tiers are never imported, a record containing any harm confers no benefit, and a passport can never vouch for itself — adoption requires the issuer's key obtained out-of-band.
Counterfactual policy replay — change policy with proof
Before you tighten or loosen anything, ask what the change would have done to the decisions your agents actually faced. Replay re-runs your recorded decision evidence under a candidate policy — no side effects, nothing stored — and returns the diff.
curl -s -X POST "$KEEL_URL/api/gateway/replay" \
-H "Authorization: Bearer $KEEL_API_KEY" -H 'Content-Type: application/json' \
-d '{"risk_overrides": {"issue_refund": "high"}, "floor_delta": 0.05}'
# → {"replayed": 412, "transitions": {"ALLOW→ESCALATE": 17, ...},
# "summary": {"would_tighten": 17, "would_loosen": 0}, "flips": [...]}
Knobs: risk_overrides (reclassify an action class),
floor_delta (raise every success-floor), tier_req (change the
tier required per risk), enforce_shadow (preview what shadow-mode exit would
do). Unknown knobs are an error, never a silent no-op. Every report states its limits:
replay is evidence-preserving, not behaviour-preserving, and the adaptive advisory layers
only ever tighten — so its ALLOW counts are an upper bound.
Signed log checkpoints — hold us to our log
GET /api/gateway/checkpoint is public and returns a signed
(size, root, time) statement of the transparency log. Fetch and keep them —
no account needed. Two authority-signed checkpoints of the same size with different roots
are non-repudiable proof the log was rewritten; a shrinking size is proof entries were
dropped.
curl -s $KEEL_URL/api/gateway/checkpoint > monday.json
curl -s $KEEL_URL/api/gateway/checkpoint > friday.json
keel checkpoint compare monday.json friday.json --key <authority_key_hex>
# verdict: APPEND-CONSISTENT · FORK-PROOF · TRUNCATION-PROOF
The comparison never over-claims: growth is reported as consistent with honest append, not proof of it.
Python SDK
keel.sdk.KeelGuard(base_url, agent_id, wait_for_approval_s=0)
| Method | Purpose |
|---|---|
| register(name, action_classes, framework, shadow_mode) | Declare the agent and its capabilities (once, at startup). |
| check(action_class, intent, payload, targets, claims, evidence, cost, …) | Ask before acting. Returns the decision dict. |
| outcome(request_id, success, harm, detail) | Report what happened after an allowed action. |
| @protect(action_class, cost=…, targets=…) | Decorator: wraps a function so it runs only on ALLOW and auto-reports the outcome. |
| export_passport() | This agent's earned record, signed for another deployment. |
| import_passport(passport, issuer_key) | Adopt a foreign record as a discounted prior (key obtained out-of-band). |
| replay_policy(risk_overrides=…, floor_delta=…) | Diff a candidate policy against recorded decisions before shipping it. |
REST API
Any language can integrate over HTTP. Core endpoints:
| Endpoint | Purpose |
|---|---|
POST/api/gateway/agents |
Register an agent and its action classes. |
POST/api/gateway/check |
Decide an action. Returns {decision, request_id, reasons, cert_id, confidence}. |
POST/api/gateway/outcome |
Report an outcome for calibration. |
GET/api/gateway/decisions |
Recent signed decisions. |
GET/api/gateway/approvals |
Pending human-approval queue. |
POST/api/gateway/approvals/{id} |
Approve or deny an escalation (records approver identity). |
GET/api/gateway/audit-pack |
Generate the self-verifying compliance evidence bundle. |
POST/api/webhook/alertmanager |
Ingest Prometheus Alertmanager events (ops vertical). |
Every /api endpoint requires your account key, with three deliberate
exceptions that exist to be read by strangers: the sign-in routes, the open certificate
schema (/api/schema/certificate), and the signed log checkpoint
(/api/gateway/checkpoint). Without the
Authorization header the server returns 401.
curl -X POST "$KEEL_URL/api/gateway/check" \
-H "Authorization: Bearer $KEEL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"agent_id":"support-bot","action_class":"issue_refund",
"intent":"refund duplicate charge","payload":{"amount":49},
"cost":49,"claims":[{"statement":"charged 49 twice",
"evidence_refs":["e1"]}],"evidence":[{"ref":"e1",
"content":"charges: 49 at 09:14, 49 at 09:14"}]}'
MCP proxy (non-bypassable)
For MCP-based agents (Claude Code, Cursor), deploy KEEL as a proxy that holds the real tool servers. The agent connects to KEEL instead of the tools, so it physically cannot act without a decision. Action classes are auto-derived from the tools' schemas — zero integration code.
keel guard proxy.json
# proxy.json
{"agent_id": "claude-code",
"shadow_mode": true,
"risk_overrides": {"run_command": "high", "write_file": "medium"},
"servers": [{"name": "fs", "command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/work"]}]}
Attach it to Claude Code with:
claude mcp add keel-guarded -- keel guard /path/to/proxy.json
The console
The operator console at /app is the daily surface: registered agents with their
earned tiers, the live decision feed, the human approval queue, the transparency ledger with
inclusion proofs, and one-click evidence-pack export. Everything it shows comes through the
same API — nothing is hidden from integrators.
Deployment
KEEL runs as a single process for evaluation and scales out for production.
# container
docker compose up # KEEL alone
docker compose --profile full up # + Qdrant + tracing
# kubernetes
helm install keel deploy/helm/keel
Optional integrations activate by environment variable: KEEL_LLM_MODEL (LiteLLM
routing for the optional reviewer), QDRANT_URL (vector retrieval),
OTEL_EXPORTER_OTLP_ENDPOINT (OpenTelemetry tracing). None are required.
Accounts & login
In production (KEEL_AUTH_REQUIRED=1) the console requires an account.
Each account is fully isolated: its agents, decisions, calibration, integration settings
and evidence packs are its own, and are never visible to another account.
- First visit to
/app→ Create your account; then sign in. - Passwords are scrypt-hashed; sessions are signed, httpOnly cookies.
- Each account gets an API key (shown in the console). Agents
authenticate to the gateway with
Authorization: Bearer keel_ak_…so their actions and billing attribute to that account.
For local self-host you can leave auth off; a single default account is used
so nothing needs configuring to try. See DEPLOY.md.
Security
- Fails closed. Unknown agents, undeclared action classes, and internal errors all deny.
- Signing keys should live in an HSM/KMS outside the agent's reach in production (Team & Enterprise tiers).
- Evidence is untrusted input — grounded status never raises the trust of an action touching credentials, funds, or data egress; instruction-shaped evidence is flagged.
- Tamper-evidence: the Merkle log detects any alteration of historical records; the audit pack re-verifies itself from the root.
Compliance
The evidence pack (/api/gateway/audit-pack) maps signed decisions to the controls
auditors and insurers sample:
| Framework | What KEEL provides |
|---|---|
| EU AI Act Art 12 | Per-decision signed, timestamped, tamper-evident logs. |
| EU AI Act Art 14 | Risk-tiered human oversight with recorded approver identity. |
| ISO/IEC 42001 | Uniform-random sampled decisions with verifiable signatures. |
| NIST AI RMF | Measured calibration and drift; autonomy from outcomes, not config. |
Plans
KEEL is free. Every capability is included for every account — self-hosted or on the managed service. There is no paid tier, no licence key, no quota to buy, and no payment is ever taken.
| Capability | Included |
|---|---|
| Gateway, SDK, tripwires, calibration | ✓ |
| Signed transparency log | ✓ |
| Evidence pack — full export & scheduling | ✓ |
| Slack / ticketing approval routing | ✓ |
| Hardened signing-key custody | ✓ |
| SSO, RBAC, private deployment | ✓ |
| WORM retention & SCITT export | ✓ |
Check what your account can do at any time:
curl -s https://keel.best/api/entitlement \
-H "Authorization: Bearer $KEEL_API_KEY"
# → {"plan":"free","features":[...],"valid":true,"expires_at":null}
Verified benchmarks
Every performance claim on this site is measured, not asserted. Reproduce them
with examples/assess_gateway.py against a running server.
| Claim | Measured |
|---|---|
| Deterministic decision latency | p50 ≈ 2.4 ms · p99 < 6 ms |
| Catastrophes caught (labeled set, cold-start & at earned trust) | 100% (0 missed) |
| Legitimate work permanently blocked | 0 false blocks |
| False positives on common dev commands | 0 / 15 |
| Transparency ledger integrity | chain-consistent, signatures verify |
| Test suite | 71 passing (auth, isolation, security, RL, stats) |
FAQ
Is this just another approve/deny prompt?
No. A permission prompt asks a human in the moment and leaves no record. KEEL blocks catastrophes without asking (so no one can be tricked into approving them), earns autonomy statistically so routine work stops prompting, governs every agent under one policy and one signed log, and produces the audit evidence a prompt cannot.
Does it work with my framework / language?
Yes — any framework via the SDK or REST, and MCP-based agents (Claude Code, Cursor) via the non-bypassable proxy. The guarantees never depend on the model or framework.
What if the LLM (Gemini) is down or unset?
The deterministic core runs with no LLM at all. The optional reviewer only ever lowers a decision; if it's unavailable, decisions proceed on the deterministic and statistical layers.
What are the honest limits?
Statistical bounds are marginal per-bucket, never a per-decision probability; they are void under detected drift (KEEL widens or abstains); citation integrity verifies that a claim traces to its evidence, not that the evidence is true. Every certificate states its own scope.
How much does it cost?
Nothing. Every capability is included for every account, self-hosted or on the managed service — there is no paid tier, no licence key, and no payment is ever taken. See Plans.
Questions? Open the console or read the product overview.