A working spec for the agent identity layer. This is a thinking document — it will have unresolved questions, because they're genuinely unresolved. The goal is to get the design decisions on paper so we can make deliberate choices instead of discovering them in production.
This is not an authentication spec. It's more fundamental than that. It's about what makes an agent this agent — persistent, portable, recognizable identity across sessions, channels, restarts, and framework changes.
Right now, an "agent" is whatever the current session says it is. There's no persistent identity. If you restart an agent, it's a new entity from the perspective of any peer. If the same agent runs in two different channels (a web UI and a CLI, say), those instances have no shared identity primitive that would let peers recognize them as "the same thing."
This creates several downstream failures:
The identity layer fixes this at the primitive level. Everything else (Passport, Registry, Distribution) depends on it.
Agent identity is the persistent, portable, verifiable anchor that makes an agent recognizable as itself — independent of session, channel, runtime, or implementation.
Key properties:
The core primitive is an AgentID record. Proposed structure:
{
"id": "agnt_01HZ...",
"version": 1,
"created_at": "2026-03-31T05:00:00Z",
"updated_at": "2026-03-31T05:00:00Z",
"name": "string — human-readable label (not unique, not immutable)",
"description": "string — what this agent does (optional, operator-set)",
"public_key": "base64-encoded Ed25519 public key",
"key_algorithm": "Ed25519",
"fingerprint": "sha256:<hex> — derived from public_key",
"operator": {
"id": "string — operator/org identifier (optional)",
"name": "string — human-readable operator name (optional)"
},
"metadata": {
"framework": "string — optional, e.g. openclaw, autogen, crewai",
"version": "string — agent implementation version",
"tags": ["array of string tags"]
},
"signature": "base64 — self-signature of the record using private_key"
}
The private key never leaves the agent. The identity record contains only the public key and a self-signature proving the agent controls the corresponding private key.
agnt_ prefix + ULID (26 chars). ULIDs are time-sortable and URL-safe.
Example: agnt_01HZ9MXKK2JVTC3N4P5Q6R7S8
Ed25519 for initial implementation: fast signing and verification, small key size (32 bytes), widely supported, no parameter choices that could be misconfigured.
generate_identity(name?, metadata?) → { AgentID, private_key }
Creates a new keypair, constructs the AgentID record, self-signs it. Private key is returned exactly once and never stored by the identity layer.
verify_identity(AgentID) → { valid: bool, errors: string[] }
Checks: self-signature valid against embedded public key, required fields present, ID format valid, timestamps sane.
sign_claim(payload: bytes, private_key) → { payload, signature, signer_id }
General-purpose signing. An agent uses this to sign messages, handshakes, capability claims — anything that needs to be attributed to this identity.
verify_claim(payload: bytes, signature, AgentID) → { valid: bool }
Verifies a signed payload against a known AgentID's public key.
export_identity(AgentID) → string
import_identity(string) → AgentID
Portable format for sharing identity records between systems.
The identity layer enables cross-channel continuity by giving agents a stable anchor that doesn't change when the channel does.
Scenario: Agent "Liz" is running in a Discord thread and an API session simultaneously. A peer agent encounters Liz in the API session and wants to know if this is the same Liz it interacted with in Discord.
With the identity layer:
agnt_01HZ...)Without the identity layer: the peer has no way to know. It would have to rely on a name string or some operator-specific convention.
The identity layer provides the primitive for trust bootstrapping but doesn't define trust policy.
V1 (out of band): Operator A shares Agent A's AgentID with Operator B (email, file transfer, whatever). Operator B imports it to a local trust store. Now Agent B can verify Agent A's identity cryptographically.
V3 (Registry): Agent B queries the Registry for Agent A's published identity record. The Registry provides a signed record with operator attestation. Removes the need for out-of-band exchange.
V1 is out-of-band by design. The trust infrastructure comes later. The identity primitive has to exist first.
Option A: No rotation. Identity permanently tied to keypair. Compromise → generate new identity.
Option B: Rotation via a signed "supersedes" claim linking old identity to new.
Current lean: Option A for v1. Rotation adds significant complexity (revocation, chain validation, time windows). Add in v2 when we understand real requirements better. Decision needed before implementation starts.
Option A: Agents generate their own identity, no operator involvement required.
Option B: Operators issue identity to agents (operator signs the AgentID).
Current lean: Option A with optional operator attestation field. Self-sovereign by default, operator signature is additive trust signal.
Should any fields be mutable after creation?
Current lean: name and description mutable; everything else immutable at this layer.
Current spec has optional capabilities field.
Current lean: Remove from v1. Capabilities belong in Passport. Unverified capability claims in an identity record are misleading — they're just assertions, not attested claims.
Proposed: version field (currently 1). Parsers reject records with unknown versions rather than silently ignoring fields. Breaking changes require a new major version.
packages/identity/
src/
types.ts — AgentID type, related types
generate.ts — keypair generation, identity creation
verify.ts — signature verification
sign.ts — claim signing
serialize.ts — export/import, canonical JSON
index.ts — public API
tests/
generate.test.ts
verify.test.ts
roundtrip.test.ts
README.md
This spec is a living document. Decisions should be recorded with date and rationale.
Questions? hello@agentcy.services ·
GitHub