Agent identity
Every agent in AgentValet has its own cryptographic identity. Not a username, not a shared API key — a private signing key that lives only on the machine the agent runs on.
When an agent calls AgentValet, it signs the request. AgentValet verifies the signature against the public key it has on file for that agent. If the signature doesn’t match, the request is rejected before any permission check, audit write, or platform call happens.
What’s actually issued
Every agent has its own RS256 keypair. AgentValet stores the public key; the private key stays with you. Where the keypair is generated depends on how the agent is set up:
- On your machine. The default installer,
npx @agentvalet/register(CLI 1.7.0 and later), generates the keypair locally and sends AgentValet only the public key. So donpx @agentvalet/register install <token>andclaim, the MCP server’s bind flow, and the Python SDK (agentvalet register --code). CLI versions up to 1.6.0 received a keypair generated on our side. - On our side. Adding a Claude Desktop agent from the dashboard (or re-keying one there), and accepting a teammate invite, generate the keypair on our side. The private key is handed to you once, and we keep no copy.
What that leaves:
- Public key — kept by AgentValet (
agents.public_key_pem) - Private key — yours to safeguard; the table below shows where each surface keeps it
- Token format — JWT signed with the private key, valid for about 60 seconds per call
Every call from the agent is a fresh signed JWT. There is no long-lived bearer token, no shared secret, no cookie. If a JWT leaks, it’s useless about a minute later.
Two kinds of agent hold no signing key at all. A child agent gets a bearer token signed by AgentValet that lives for its TTL (60 seconds to an hour). An agent connected over OAuth (Claude on the web, and other remote MCP clients) holds access tokens from AgentValet’s own authorization server: each lasts 15 minutes, renewed with a refresh token on a 30-day sliding window.
Where the private key lives
Different surfaces store the key in different places:
| Surface | Storage |
|---|---|
| MCPB (Claude Desktop) | Pasted into the extension’s user-config fields; held by Claude Desktop’s secure storage |
| CLI | .agentvalet/agent.key in the project directory, file mode 0600 |
| Dashboard-created agents | Handed over once at creation; you’re responsible for placement |
If you lose the private key, you can’t recover it. Delete the agent and register a fresh one — the old identity stays in your audit log but can no longer sign anything.
What the verification check covers
On every action call, AgentValet’s proxy:
- Parses the JWT from the
Authorization: Bearer …header - Looks up the agent record by the
agent_idclaim - Verifies the signature against the stored public key
- Checks expiry
A bad signature or expired token is rejected with 401. The agent’s status is checked next, as the first step of permission evaluation: anything other than active is refused with 403, "error": "agent_halted" and a reason of agent_suspended or agent_revoked (deleted agents read as revoked), plus a halt object explaining what happened and who can undo it.
Identity vs authorisation
It’s worth separating two things that get conflated elsewhere:
- Identity answers “who is this?” — handled by the signing key.
- Authorisation answers “is this caller allowed to do this?” — handled by scopes & permissions.
A valid signature gets you past the door. It does not get you any particular action. An agent with no granted scopes can sign requests all day; every one of them comes back denied.
Why this matters
Shared API keys are the default failure mode in the agent ecosystem. One key leaks, one config file gets checked in, one ex-employee keeps a copy — and you can’t tell which actions were “real” and which were the leak. The audit log says “Slack API used Stripe key” and you have no idea who.
Per-agent keypairs solve the attribution problem. Every action in your audit log carries a specific agent_id. You can revoke that one identity without touching any others. You can see which agent acted, when, and with which scopes.
Public identity and delegation
- Public identity documents. An organisation can opt in to publishing a did:web document per agent at
/agents/<agent-id>/did.json. It’s off by default; an agent in an organisation that hasn’t opted in resolves to404. - Delegation. A parent agent can mint child agents with a scope-attenuated slice of its own grants, today, on AgentValet’s own token plumbing. Moving that to a SPIFFE-compatible identity layer is still on the roadmap.