---
title: 'ADR 0005: Identity model'
description: One Ed25519 key pair per agent, generated locally, with no refresh token and terminal revocation.
label: '0005 Identity model'
---

## Context

Every other property in Veefore hangs off agent identity. A capability is
granted *to* an identity, a delegation is issued *by* one, an audit event
records *which* one acted. If identity is weak, nothing else means anything.

The default industry answer is an API key per agent. It is simple, every
language can send a header, and it is a bearer credential — which means
possession is proof, and anything that sees it can replay it.

For an agent, that is a worse position than for a service: the credential lives
in a process whose job is to produce text from a model, and whose context can be
influenced by whatever it reads.

## Decision

**One Ed25519 key pair per agent.** The agent signs a fresh assertion; Veefore
holds only the public half.

```json
{
  "sub": "agt_01JAV…",
  "aud": "https://gateway.veefore.example.com",
  "purpose": "agent_authentication",
  "nonce": "YmFzZTY0dXJsLXJhbmRvbQ",
  "iat": "2026-10-03T12:38:04.000Z",
  "exp": "2026-10-03T12:40:04.000Z"
}
```

**Generated locally by default.** `veefore init` creates the pair on your
machine and sends only `public_key`. The private half never crosses the network,
so there is no moment at which Veefore could have logged, cached or leaked it.

**Six checks on every assertion**: signature, audience as an exact string,
timestamp window, single-use nonce, key generation, revocation state. Each has
its own error code, so the agent's logs say which one failed.

**A generation counter.** Rotation bumps it, and an assertion signed with a
superseded key is refused with `IDENTITY_GENERATION_STALE` rather than a generic
authentication failure.

**No refresh token.** Session tokens last 15 minutes and the agent re-signs. The
key is already a better credential than a refresh token would be.

**Revocation is terminal.** There is no un-revoke.

**The audience is published** at `/.well-known/veefore-platform-key`.

## Alternatives

**API key per agent.** Simple, and it is a bearer credential. Theft is
undetectable, replay is free, and the credential sits in the agent's
environment where its own prompt context can reach it. Rejected as the primary
mechanism.

**mTLS.** Genuinely strong, and it moves identity into the transport layer —
which means every proxy, load balancer and service mesh between the agent and
the gateway has to be configured to pass the client certificate through, and a
misconfiguration is silent. It also makes identity something the platform
operator controls rather than something the agent holds. Supported as a
credential *type* for downstream tools, not as agent identity.

**OIDC with an external identity provider.** Good for humans. For agents it adds
a dependency on the provider being available in order to authenticate — which
under fail-closed means a provider outage refuses all agent traffic — and it
makes the provider the root of trust for agent identity.

**RSA instead of Ed25519.** Universally supported. Larger keys, slower
signatures, and more ways to use it wrongly — padding modes, key sizes, a
signature scheme that takes a digest. Ed25519 has one way to use it.

**Numeric epoch `iat`/`exp`, as in standard JWT.** Conventional. Every other
timestamp in the Veefore API is ISO-8601, and one format across the whole
surface is worth more than conformance to a convention in one place. Documented
prominently because it does surprise people.

**Allow revocation to be undone.** Convenient when you revoke the wrong agent.
It also means a revocation can be undone by whoever caused the incident, which is
not a revocation. Register a new agent instead.

**Longer assertion lifetimes.** Fewer signatures. A long-lived assertion is a
bearer credential, which is the thing this ADR exists to avoid. Two minutes.

## Consequences

**Agents must be able to sign.** Every language with Ed25519 can, and the two
SDKs do it for you — but a runtime without a crypto library cannot participate.
For those, Veefore can generate the pair and return the private half **once**,
which is weaker and documented as such.

**The private key is not recoverable.** Veefore stores the public key and
nothing else. Lose `.env.local` and you rotate rather than recover. `doctor`
checks the file permissions, because a key readable by another process on the
host is a key that process can act with.

**The audience is the most common onboarding failure.** It is compared as a
**string**, so `http://127.0.0.1:4010` and `http://localhost:4010` are the same
host and different values — and the symptom is a bare `401` with no explanation.

Mitigated by publishing it at `/.well-known/veefore-platform-key` and having
`veefore init` fetch it rather than assume. Publishing it is safe: it is the
value you sign *for*, and knowing it grants nothing without the key.

**Nonce tracking needs shared state in a multi-node deployment.** Without
`REDIS_URL` each instance keeps its own set, so replay protection is weaker than
it looks. Called out in [Conventions](/reference/conventions) and
[Troubleshooting](/operate/troubleshooting).

**Rotation invalidates every session for that agent.** That is the point — it is
what makes rotation a containment action rather than a housekeeping one — and it
means a long-running agent needs a path to re-read its configuration.

**Terminal revocation means revoking the wrong agent costs a re-registration.**
Accepted deliberately. The alternative is worse.

## Related

- [Agent identity](/concepts/identity)
- [Sign your own assertion](/build/sign-your-own-assertion)
- [Authentication](/reference/authentication)
- [Passport format](/architecture/adr/0006-passport-format)
