Your agents do tasks for you. warden proves it with cryptography.
A note on language. The core sections below use ASD-STE100 Simplified Technical English — short sentences, the active voice, and one idea per sentence. Security infrastructure must be easy to read.
An AI agent is not a user. An AI agent is not a usual service. An AI agent is a proxy. It is software that does tasks for a person. It frequently operates without supervision. It operates across many tools and other agents.
This condition breaks identity. An agent can send money. An agent can send an email. An agent can call a different agent. For these actions, three questions have no good answer today:
- Who does the action, and for whom?
- What permissions does the agent hold?
- Can you prove the action later — or stop the action before it starts?
A stolen API key cannot answer these questions. warden answers these questions.
warden gives an agent a verifiable ID card. The ID card is a signed token. The token
proves this statement:
Agent
Xdoes tasks for principalY. The permissions are limited to scopeZ. The token is valid until timeT.
warden uses proven standards. The ID card is a signed JWT. warden shapes the token with
RFC 8693 delegation claims. warden signs
the token with Ed25519. A relying party verifies the token offline. warden does not
call a central server.
You install and operate warden yourself. Thus the issuer keypair is your root of trust.
You become your own trust anchor. This model is the same model as an OIDC provider that you
operate.
Each token action emits a structured event. Each event has a version. Thus you, or the security tools that you connect, can see the actions of each agent. You can also see the principal for each action.
principal (human) warden issuer verifier / any service
│ │ │
│ "let agent-007 read email" │ │
│─────────────────────────────▶│ mint signed ID card │
│ │──────────────────────────────────▶│ verify offline ✔
│ │ │ → acts for: alice
│ emits token.issued emits token.verified
│ └──────────────┬──────────────┘
│ ▼
│ your event stream → anomaly detection
uv sync
# 1. Become your own trust anchor (generate the issuer keypair)
uv run warden keygen
# 2. Mint an ID card: agent-007 may read email & calendar on Alice's behalf
uv run warden issue --principal alice --agent agent-007 --scope "email:read calendar:read"
# 3. Verify it (offline, against your public key)
uv run warden verify <paste-the-token>
# VALID
# principal: alice
# agent: agent-007
# scope: email:read calendar:read
# issuer: warden.local
# expires: 2026-07-26T18:42:10+00:00If you change one character, or you wait until after --ttl, verify rejects the token.
verify then emits a token.rejected or token.expired event. The event gives the reason.
Agents give work to other agents. warden lets an agent delegate a part of its authority.
The scope can only become smaller along the chain. The scope can never become larger.
# Alice grants agent A read+send on email
PARENT=$(uv run warden issue --principal alice --agent A --scope "email:read email:send")
# A delegates to sub-agent B — but only email:read (a subset)
CHILD=$(uv run warden delegate "$PARENT" --agent B --scope "email:read")
uv run warden verify "$CHILD"
# VALID
# principal: alice ← still on Alice's behalf, all the way down
# agent: B ← current actor
# scope: email:read ← attenuatedIf you try to make the scope larger (--scope "email:read email:send calendar:read"),
warden denies the request. warden also denies a cycle (for example A → B → A).
warden also denies a chain that is longer than the depth limit. A child token cannot stay
valid for longer than its parent token. You can get the full chain — alice → A → B — from
any card with card.chain. Each delegation emits a token.delegated event.
from warden import Issuer, Verifier, EventBus, RequestContext, generate_keypair
private_key, public_key = generate_keypair()
# emit lifecycle events wherever you want them
bus = EventBus()
bus.on("token.issued", lambda e: print("issued:", e.jti))
bus.on("token.rejected", lambda e: print("rejected:", e.reason))
issuer = Issuer(private_key, iss="warden.local", event_bus=bus)
verifier = Verifier(public_key, expected_iss="warden.local", event_bus=bus)
# capture ambient signals for downstream security profiling (opt-in, never resolved by warden)
ctx = RequestContext(source_ip="203.0.113.5", transport="mcp", client_id="agent-007")
token = issuer.issue("alice", "agent-007", ["email:read"], context=ctx)
card = verifier.verify(token, context=ctx) # → DelegationCard, or a typed errorOne deployment is its own trust anchor. Your agent can prove itself to the systems of a different organization. To do this, the other organization trusts your issuer. The other organization uses your public key or your published JWKS. There is no central authority.
from warden import FederatedVerifier
fed = (FederatedVerifier()
.trust("org-a", org_a_public_key) # by key
.trust_jwks("org-b", httpx.get("https://org-b/.well-known/jwks.json").json())) # by JWKS
card = fed.verify(token) # picks the right key by the token's `iss`An attacker can claim a trusted iss value. But the attack fails. warden checks the token
against the real key of that issuer. warden rejects an unknown issuer with an
untrusted_issuer event.
You can also discover an issuer across the network. trust_discovery reads the issuer
configuration and the keys for you. The network call happens only at setup. Verification
stays offline.
fed.trust_discovery("https://org-c.example") # reads /.well-known + JWKS, then trustswarden includes an HTTP issuer service. Any service can trust it across the network. The
service reads the JWKS from warden. The service then verifies tokens offline. You
do not need a shared secret.
uv sync --extra server
uv run warden serve # http://127.0.0.1:8080
curl localhost:8080/.well-known/jwks.json # the issuer's public keys
curl -X POST localhost:8080/issue -d '{"principal":"alice","agent":"A","scope":"email:read"}'
curl -X POST localhost:8080/delegate -d '{"parent_token":"...","agent":"B","scope":"email:read"}'
curl -X POST localhost:8080/verify -d '{"token":"..."}'You can also run the container (docker build -t warden . && docker run -p 8080:8080 warden).
In Python, a relying party trusts the issuer with only its published keys:
import httpx
from warden import Verifier
jwks = httpx.get("http://issuer/.well-known/jwks.json").json()
card = Verifier.from_jwks(jwks, expected_iss="warden.local").verify(token) # offlineVerification tells you that a card is valid. The gateway decides if a specific action is permitted. The gateway checks the scope of the card against a policy. The gateway can send a dangerous action to a human approver first. The gateway denies an unknown action by default.
from warden import Gateway, Policy, Rule, Verifier
policy = Policy(rules=[
Rule("email.read", required_scope="email:read"),
Rule("email.send", required_scope="email:send", require_approval=True),
])
gateway = Gateway(verifier, policy, approver=lambda card, action, ctx: ask_human(card, action))
gateway.authorize(token, "email.read") # → ALLOW (scope satisfied)
gateway.authorize(token, "email.send") # → REQUIRE_APPROVAL, then ALLOW/DENY
gateway.authorize(token, "wire.money") # → DENY (no rule → deny by default)If the approver fails, the gateway denies the action (it fails closed). Each decision emits
an action.allowed, action.denied, or action.pending_approval event.
Revocation: give the verifier a RevocationStore. If you revoke a jti, that token
fails verification. The direct delegations of that token also fail verification. warden
emits a token.revoked event. Across HTTP, POST /revoke (admin only) does the same
operation.
warden captures data and emits data. warden does not classify data. Each action
produces an Event. The event contains the card and the
request context that you supply (for example the raw source_ip, the transport, and the
TLS fingerprint). warden does no GeoIP, ASN, or Tor lookups. warden gives you clean,
signed, structured data. Thus you, or any security vendor, can build the detections on top of
the data:
| You emit | Someone builds |
|---|---|
identity + source_ip/geo + time |
impossible travel |
identity + asn |
unseen ASN / network |
source_ip |
Tor exit-node flags |
identity + scope + time |
velocity / scope-escalation anomalies |
The event schema has a version and is stable. This schema is the contract for downstream
tools. warden also includes example detectors (UnseenASNDetector, TorExitDetector,
ImpossibleTravelDetector). These detectors consume the stream. The detectors stay outside
the core. They prove the model.
In a multi-agent mesh, you can only monitor the data that the agents propagate. warden
carries the card (and a delegation trace id) across each hop — HTTP, MCP, A2A, or gRPC.
warden uses one helper that works with any carrier. The AuthorityMonitor then rebuilds
the full picture:
from warden import AuthorityMonitor, EventBus, Issuer, Verifier, inject, extract
bus = EventBus()
monitor = AuthorityMonitor().attach(bus) # subscribes to every event
# ... issuer/verifier/gateway built with event_bus=bus ...
# propagate across a hop (agent A -> agent B): put it on the wire, read it off
trace = inject(outgoing_headers, token) # HTTP headers / MCP metadata / A2A message
token, trace = extract(incoming_headers)
monitor.by_principal("alice") # everything any agent did on Alice's behalf
monitor.by_trace(trace) # the whole delegation tree for one request
monitor.denials() # rejections / expirations / revocations / denied actions
monitor.as_spans() # OpenTelemetry-style spans → any tracing backendexamples/langchain_multi_agent.py is a runnable
LangChain multi-agent system on warden. It issues, delegates (and blocks a wider
delegation), propagates the card across a hop, enforces every tool call through the gateway,
federates to a partner org, revokes the whole chain, and monitors it all. It runs on a small
local model (Ollama) or a deterministic fake model with no network:
uv run --group dev python examples/langchain_multi_agent.pywarden is built in phases. See PHASED_PLAN.md for detail.
- ✅ Phase 0 — issue & verify on-behalf-of tokens · event stream · CLI
- ✅ Phase 1 — delegation chains + attenuation (agent → sub-agent, scope only narrows)
- ✅ Phase 2 — HTTP issuer + JWKS endpoint · offline verification by JWKS
- ✅ Phase 3 — enforcement gateway (allow / deny / require-approval) + revocation
- ✅ Phase 4 — monitoring plane (authority tracing across A2A/MCP) + enrichment plugins
- ✅ Phase 5 — cross-org federation (trust many issuers, select by
iss)
- Proven crypto, proven standards.
wardenuses JWT, Ed25519, RFC 8693, and OIDC. Trust comes from code that you can read. - Self-hosted, no central server. Verification is offline. Your keys, your data, your trust anchor.
- Capture, do not classify.
wardenproduces trustworthy data. Security products build on the data. - Simple to audit. If you cannot read the code, you cannot trust the code.
AGPL-3.0-or-later. Free to self-host, read, and modify. Building a hosted service on top? Talk to us about a commercial license.
See AGENTS.md for setup, tests, architecture, and conventions — written for human and AI contributors alike.