Enlil
The open-source control and audit plane for AI agent actions.
Enlil sits inline between your agents and any model or tool, and answers the question that keeps agents out of production: what is this agent allowed to do, and can you prove what it did?
Claude secures Claude. Enlil governs everything your agents touch — every model, every tool, one audit trail you own.
Then send your agent's traffic through it. Enlil inspects any JSON payload, so this works the same whichever provider or framework you use:
# OpenAI / any OpenAI-compatible endpoint (vLLM, Together, Groq, OpenRouter, Ollama)
=
# Anthropic
=
# MCP servers, A2A agents, or anything else — just point the base URL at Enlil
No signup. No config file. No cloud account. One binary.
Why it works for any provider
Enlil scans payloads structurally, not by schema. It walks every string in the
request rather than looking for one vendor's field names, so the same injection is
caught whether it arrives as OpenAI messages[].content, an OpenAI multimodal
content[].text array, Anthropic's top-level system, Gemini
contents[].parts[].text, an MCP tools/call argument, a bare prompt, or a
schema that doesn't exist yet. There are no per-vendor adapters to wait for.
What you just got
Send a request through it and Enlil will, on the same request:
- Block a prompt injection before it reaches the model —
403 prompt_injection_blocked - Redact PII (SSNs, emails, cards, phones) out of the outbound payload, reversibly
- Sever a runaway loop when an agent re-issues the same intent N times —
429 agent_loop_detected - Record the whole decision path to a queryable trace you own
WARN PROMPT-INJECTION BLOCKED: prompt_injection (score 135):
instruction-override phrase: 'ignore all previous instructions'
That request never reached the provider, and the attempt is in your trace log:
Why
Agent frameworks give you capability. Almost nothing gives you control. Before an agent touches production you need to answer:
| Question | Enlil |
|---|---|
| What is this agent allowed to do? | Declarative policy rules, evaluated inline |
| What did it actually do? | Every action traced with its full decision path |
| Can it be manipulated into doing something else? | Prompt-injection + tool-poisoning defense |
| Will it leak data on the way out? | Reversible PII redaction, exfiltration detection |
| Will it burn my budget in a loop? | Loop-breaker + token accounting |
Vendor-neutral by design. Your agents will not all be on one model or one framework, and your audit trail should not be owned by whoever sold you the model.
Features
Control (inline enforcement)
- Prompt-injection & tool-poisoning defense — scans message content for instruction-override attempts, and MCP/OpenAI tool descriptions for hidden imperatives, encoded blobs, and invisible Unicode-tag smuggling. Scores findings; blocks high-confidence attacks.
- Declarative policy rules — block / redact / alert / log rules evaluated per request. Credential exfiltration, SQL injection, and prompt injection ship enabled.
GET /api/rules. - Agent loop-breaker — detects a session re-issuing the same request intent within a window and hard-stops it before it burns budget. Intent is extracted from
messages+tools, so for OpenAI-shaped payloads volatile fields (temperature, a rotating request id) don't defeat detection. For other schemas it falls back to fingerprinting the whole body, which still catches identical repeats but can be defeated by a changing nonce or timestamp in the payload. Verified working on Anthropic-, Gemini-, and MCP-shaped requests. - PII redaction (reversible) — regex redaction masks SSNs, emails, credit cards, phone numbers. Also deobfuscates hex-encoded shell payloads and flags base64 secret exfiltration (read
.env→ base64 → POST). - SafeFix — advises safer command alternatives back to the agent via an
x-agent-safefixheader. It advises; it does not silently rewrite. - Context-window guard — rejects requests that would overflow the model's window instead of letting the provider truncate silently.
Audit (observability)
- Time-travel traces — every request gets an
x-trace-idand is recorded with its governance decision steps, cache disposition, status, latency, and token/cost outcome.GET /api/traces,GET /api/traces/{id}. - Built-in dashboard — served from the binary at
/. No separate frontend to deploy. - Local-first storage — traces persist to SQLite in
DATA_DIR. Your audit trail stays on your disk.
Efficiency
- Exact-intent caching — blake3 over the request intent (messages + tools) short-circuits redundant model calls. This is exact-match deduplication, not embedding-based semantic similarity — we are precise about this because the distinction matters when you are reasoning about correctness.
- Token attribution — separates base prompt from tool schemas and retrieved context, and prices provider prompt-cache reads/writes at their real rates.
- Circuit breaker — per-provider failure tracking with failover, including a local Ollama fallback for data sovereignty.
Protocols
- Any JSON payload is inspected. Enforcement walks the request structurally, so it does not depend on a provider's schema. OpenAI (including multimodal content arrays), Anthropic, Gemini, MCP
tools/callarguments, A2A, and unrecognised or future shapes are all scanned. - Protocol detection additionally labels traffic as OpenAI / MCP (JSON-RPC) / A2A / Generic, so tool calls are traced and governed as first-class actions rather than opaque request bodies.
- Routing presets ship for OpenAI (
/v1/), Anthropic (/anthropic/), and MCP (/mcp/), each overridable by env var.
Performance
Governance is on the critical path, so its cost is measured, not asserted. A deterministic micro-benchmark (tests/proxy_overhead_bench.rs) measures the synchronous per-request governance work — 5000 iterations, pure CPU, no network, release build — and gates regressions in CI:
| Per-request CPU overhead | |
|---|---|
| median (p50) | 18µs |
| p95 | 18µs |
| p99 | 28µs |
Those are the numbers from the CI run on a GitHub-hosted runner, so you can verify them in the Actions log rather than taking our word for it. Your hardware will differ; re-run it yourself with:
Rust, Tokio, Axum — no GC pauses on the hot path. The request body is parsed once and the parsed value shared across every analyzer (protocol detection, loop-breaker, RiskChain, prompt guard, token estimate, context-window guard, cache hash).
Configuration
Zero config to start. Everything is an environment variable:
| Variable | Default | Notes |
|---|---|---|
PORT |
8080 |
Listen port. |
UPSTREAM_URL |
https://api.openai.com |
Where unmatched requests go. |
DATA_DIR |
./data |
Trace/metrics storage. |
CACHE_TTL_SECS / CACHE_MAX_CAPACITY |
900 / 10000 |
Exact-intent cache tuning. |
LOOP_WINDOW_SECS / LOOP_MAX_REPEATS |
30 / 5 |
Loop-breaker. 0 repeats disables. |
MAX_PAYLOAD_BYTES |
1048576 |
Request body cap. |
RUST_LOG |
info |
tracing filter. |
Docker
API
| Endpoint | Purpose |
|---|---|
/ |
Dashboard |
/health |
Liveness |
/api |
Endpoint index |
/api/traces |
Recent agent actions |
/api/traces/{id} |
One action, full decision path |
/api/stats |
Counters: requests, blocks, redactions, tokens, latency |
/api/rules |
Active policy rules |
/api/events/recent |
Governance event feed |
/{*path} |
Everything else is proxied and governed |
Using it as a library
The proxy is generic over a ProxyEnv trait whose hooks all default to no-ops, so you can attach your own accounting, quota, or alerting logic without forking the hot path:
use ;
Enlil vs. Plumb
Enlil is the engine, and it is complete — it is not a crippled demo of a paid product. Everything above runs single-tenant, self-hosted, forever, for free.
Plumb is the commercial cloud built on this same engine, for when an organization rather than a developer needs it: multi-tenancy, SSO/RBAC, signed compliance evidence packs, long-term retention, cross-fleet policy management, and a managed control plane.
If you are one team running your own agents, Enlil is the whole product.
License
Business Source License 1.1. Use it in production, self-host it, modify it. The one restriction is offering Enlil itself to third parties as a competing managed service. It converts to Apache 2.0 on the Change Date in the license.
Practically: if you are running agents, you are unrestricted. If you are reselling Enlil as a service, talk to us.
Contributing
Issues and PRs welcome. The highest-value contributions are new detections — an injection technique, an exfiltration pattern, a tool-poisoning vector we miss. Each one should come with a test case that fails before your change.
Stewarded by Samji Technologies Private Limited.