As AI agents start acting on your behalf — calling tools, reaching data, spending budgets — the real question isn't can this agent prove who it is? It's can I still decide, right now, what it's allowed to do? AIAgentPrincipal keeps a human in control of an agent's authority, checked on every request and revocable the instant you change your mind — including from the AI itself.
// the whole product in one line: revocation that beats a still-valid token.
# the agent calls a tool it's scoped for enforce tool:call:search 200 granted # you change your mind — kill that one token revoke ait_9f3e… revoked ✓ # the SAME call, seconds later enforce tool:call:search 403 denied ← before its TTL
Every other system treats a bearer token as the authority: a valid signature plus an unexpired clock means you're in, and nobody can take it back until it dies on its own. For a person clicking a button, fine. For an agent that fans out thousands of times a minute, a token you can't recall is the wrong model.
Here the signed token — an AIT, an Agent Identity Token — is only an assertion of identity. Authority is decided on every request by a policy enforcement point that checks the live principal.
Disable an agent, or revoke one token, and every outstanding call dies on the next request — not at the next TTL.
That gap — between "the token is still cryptographically valid" and "the call is refused anyway" — is the entire point. You never stop being the one in control of you.
AIAgentPrincipal came out of building FaradayMail — a private communications product I'm developing across a couple of nodes, not yet in production. Building it, I kept answering the same question: who is this, and what are they allowed to do right now? The identity was internal, because the product needed it.
But the more I built, the clearer it got: that layer didn't want to stay welded inside one app. It wanted to be external — metaversable. Something any system could reach for if it needed to. So alongside the mail product, I split it out and built this: an IdP for AI agents and the objects that act for us.
There was a second realization, less comfortable. It was so easy to reach for the commercial clouds to build exactly what I wanted — even when I had local inference models that could do the job. That convenience is a gravity well.
By design, that pull drags smaller companies into consumption they don't control: someone else's identity, someone else's authority, someone else's bill — and someone else's kill switch.
So I made the identity layer its own thing, and I made it assert control. As AI objects start acting for us, the authority to say what they may do — and to take it back — should belong to you, runnable on infrastructure you own, not rented from the thing you're trying to stay independent of.
— the reason this is a separate, self-hostable control plane, and not a feature buried in someone's product.
Model- and host-agnostic. Works whether the agent is Claude, another hosted model, or something you run on-prem; whether your directory is Active Directory, LDAP, or Entra; whether your store is SQLite or Postgres.
A per-request PEP checks the live principal, so agent- and token-level revocation take effect on the next call — not at TTL.
EdDSA (ed25519) AITs verified with verify_strict. Signing keys rotate live; retired keys keep verifying old tokens.
Workload-identity JWTs (SPIFFE / K8s / cloud) are verified at registration — not just captured — and can fail closed.
An agent's owner resolves against the directory you already run — Active Directory, any LDAP, or Entra.
The signing seed is sealed (XChaCha20-Poly1305) under a KEK that can come from a cloud KMS by envelope encryption. The DB holds only ciphertext.
Every decision is an append-only, hash-chained JSONL record. Edit, delete, or reorder a line and the chain breaks — provably, offline.
In-process TLS 1.3, and mutual TLS: callers prove a client certificate chaining to a pinned CA on top of the bearer key.
A reverse proxy in front of an MCP server maps each JSON-RPC call to a scope and calls enforce before forwarding — fail-closed.
SQLite or Postgres behind one Store trait; Postgres coordinates multiple instances with advisory locks.
Every decision is counted per agent, and spend accrues whether or not you cap it — so the numbers are there to bill against or chargeback from day one. Give an agent a budget and it's enforced on the same hot path as revocation: the call that would go over is refused on the next request, not at the token's TTL.
# this agent has a 3-credit budget. token TTL: 3600s. enforce tool:call:search 200 granted used 1/3 enforce tool:call:search 200 granted used 2/3 enforce tool:call:search 200 granted used 3/3 enforce tool:call:search 403 quota_exhausted ← budget spent
Calls, allowed, denied, last-seen and spend tracked per agent — GET /v1/usage. Put a price on a credit and it reads as a dollar figure.
A per-agent credit cap denied at the boundary — quota_exhausted on the next request, before the TTL. Top it up with one call.
Last-seen per agent surfaces the ones gone quiet — the authority, and spend, a terminations pass recovers. Revoke in one call.
The agent-identity control plane is where the whole field is heading — Microsoft, Okta, the PAM giants and the standards bodies are all converging on it. AIAgentPrincipal is built to sit on that consensus, not around it — and to hand it to the people the enterprise platforms price out.
The security-critical properties are enforced in code and checked automatically — all of them enabled in one process, driven end to end over mutual TLS, on every commit, against both backends.
Bring up the whole stack — control plane, enforcing gateway, a mock MCP server — and watch a token get used, revoked, and refused mid-flight while the agent itself stays live.
# whole stack in one command docker compose up --build # issue a token, use it, revoke it, use it again POST /v1/agents/:id/token → { token, jti } POST /v1/gateway/enforce → 200 granted POST /v1/tokens/revoke → { revoked: true } POST /v1/gateway/enforce → 403 revoked_or_disabled # same call, before TTL