---
title: Agent identity
description: Every agent holds its own Ed25519 key pair, proves possession on every request, and can be revoked terminally.
label: Agent identity
---

An agent's identity is a key pair. Not an API key, not a name, not a row in a
table that a service happens to trust. The agent holds an Ed25519 private key
and signs a fresh assertion for every session; Veefore holds only the public
half and verifies it.

This matters because almost every other property in the system hangs off it. A
capability is granted *to an identity*. A delegation is issued *by an identity*.
An audit event records *which identity* acted. If identity is weak, none of the
rest means anything.

## What identity is not

**The name is not the identity.** `name` is display metadata. Two agents can be
called `finance-agent`. Policy can test `agent.name`, and it is occasionally
useful to, but a rule that depends on a name is a rule that a rename breaks.
Authorization resolves on `agent.id`.

**A bearer token is not the identity.** The gateway does issue a short-lived
session token, but you get one by presenting a signed assertion. A stolen token
expires in minutes (`AGENT_TOKEN_TTL_SECONDS`, 900 by default) and cannot be
refreshed without the key.

**The SDK is not the identity.** The SDK formats and signs the assertion. The
key is the identity. You can sign your own — see
[Sign your own assertion](/build/sign-your-own-assertion) — and the gateway
cannot tell the difference, which is exactly the property you want from a
security boundary.

## How the key gets created

Two paths, and the first is better.

**You generate it.** `veefore init` creates the key pair locally and sends only
the public key in `POST /v1/agents`. The private half never crosses the network,
so there is no moment at which Veefore could have logged it, cached it or leaked
it. This is the default.

**Veefore generates it.** If you omit `public_key`, the registration response
includes `private_key` once, base64 PKCS#8. It is not stored and not
recoverable. Use this only when the agent runs somewhere that cannot generate a
key, and treat the response as a secret in transit.

:::security The private key is written to `.env.local` at mode 0600
`veefore doctor` checks the file permissions and warns if the key file is
group- or world-readable. It is a real finding: a key readable by another
process on the host is a key that process can act with.
:::

## Proving possession

```http
POST /gateway/v1/token
```

The body is a signed assertion over a canonical JSON payload: the agent id, the
key id, the audience, a purpose, a nonce and a timestamp. The gateway checks all
of it.

| Check | Refusal |
| --- | --- |
| Signature verifies against the stored public key | `AGENT_AUTHENTICATION_FAILED` |
| `aud` matches the gateway's published audience, as a string | `AGENT_AUTHENTICATION_FAILED` |
| Timestamp is inside the acceptance window | `TIMESTAMP_OUT_OF_WINDOW` |
| Nonce has not been seen before | `NONCE_REPLAYED` |
| Key generation is current | `IDENTITY_GENERATION_STALE` |
| Identity is not revoked | `AGENT_IDENTITY_REVOKED` |

:::warning The audience is compared as a string
`http://127.0.0.1:4010` and `http://localhost:4010` are the same host and
different strings, and the second will be refused if the gateway is configured
with the first. This is the single most common onboarding failure, and it
presents as a bare `401`.

Fetch the authoritative value rather than guessing it:

```bash
curl -s https://gateway.veefore.example.com/.well-known/veefore-platform-key
```

The response carries `audience` alongside the platform verification key.
`veefore init` does this for you and writes what the gateway actually published.
:::

The nonce check is what makes replay useless. A captured assertion cannot be
submitted a second time, even within its validity window, so an attacker who
reads one off the wire gains a token they could already see.

## Lifecycle

Seven states, and the distinctions between them are operational, not cosmetic.

| State | Reversible | Means |
| --- | --- | --- |
| `PROVISIONING` | — | Registered, identity not yet fully issued |
| `ACTIVE` | — | Operating normally |
| `PAUSED` | yes | Operational stop. No new protected actions. |
| `SUSPENDED` | yes | Security stop. Same effect, different intent and audit severity. |
| `REVOKED` | **no** | Identity invalidated. Terminal. |
| `EXPIRED` | — | Credential lifetime elapsed |
| `DECOMMISSIONED` | **no** | Retired for good |

`PAUSED` and `SUSPENDED` both stop protected execution immediately. Keeping them
separate matters when someone reads the audit trail in six months: "paused for a
deploy" and "suspended because the key may be compromised" are not the same
event, and collapsing them loses the only information that mattered.

`REVOKED` is terminal by design. There is no un-revoke. If you revoke the wrong
agent, you register a new one — which is inconvenient, and far less
inconvenient than a revocation that can be undone by whoever caused the incident.

```bash
veefore agents revoke agt_01ABC --reason "Key suspected compromised"
```

The CLI shows the full blast radius — sessions that die, delegations that
cascade, approvals that are abandoned — before asking for confirmation. It also
requires step-up authentication, because `agent:revoke` is both high impact and
step-up gated.

## Rotation

```bash
veefore agents rotate-key agt_01ABC
```

Rotation issues a new key pair and bumps the identity *generation*. Every
outstanding session for that agent dies immediately, and any assertion signed
with the old key is refused with `IDENTITY_GENERATION_STALE` rather than a
generic authentication failure — so the agent's logs say what actually happened.

Rotate on a schedule, and rotate immediately on any suspicion. `veefore doctor`
flags identities that have not rotated in a long time.

## What policy can see

| Field | Type |
| --- | --- |
| `agent.id` | string |
| `agent.name` | string |
| `agent.status` | `PROVISIONING` `ACTIVE` `PAUSED` `SUSPENDED` `REVOKED` `EXPIRED` `DECOMMISSIONED` |
| `agent.risk_level` | `LOW` `MEDIUM` `HIGH` `CRITICAL` |
| `agent.verification_status` | `UNVERIFIED` `PENDING` `VERIFIED` `FAILED` |
| `agent.protocols` | `HTTP` `MCP` `A2A` |
| `agent.capabilities` | string list |
| `agent.tags` | string list |

Full list with operators in [Policy fields](/reference/policy-fields).

## Related

:::cards

:::card Agent Passport | /concepts/passport
The signed, portable statement of who an agent is.
:::

:::card Capabilities | /concepts/capabilities
What an identity is allowed to be for.
:::

:::card Sign your own assertion | /build/sign-your-own-assertion
The wire format, in full, with no SDK.
:::

:::card Agents API | /reference/api/agents
Every endpoint, permission and step-up requirement.
:::

:::
