Spec Blog Products Services Contact Follow the Build
Draft Spec · Phase 1

Identity Layer

Updated: 2026-03-31  ·  Status: DRAFT  ·  Part of: Agentcy.Services build sequence

What This Is

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.

The Core Problem

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:

  1. No peer recognition — Agent B can't verify it's talking to the same Agent A it talked to yesterday
  2. No trust continuity — Trust established in one session doesn't carry forward
  3. No cross-channel continuity — An agent's history and reputation don't persist across environments
  4. No verifiable claims — An agent can claim to be anything; there's no way to verify

The identity layer fixes this at the primitive level. Everything else (Passport, Registry, Distribution) depends on it.

What Is Agent Identity?

Agent identity is the persistent, portable, verifiable anchor that makes an agent recognizable as itself — independent of session, channel, runtime, or implementation.

Key properties:

What identity is NOT

Identity Record Structure

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.

ID Format

agnt_ prefix + ULID (26 chars). ULIDs are time-sortable and URL-safe.

Example: agnt_01HZ9MXKK2JVTC3N4P5Q6R7S8

Key Algorithm

Ed25519 for initial implementation: fast signing and verification, small key size (32 bytes), widely supported, no parameter choices that could be misconfigured.

Core Operations

1. Generate identity

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.

2. Verify identity

verify_identity(AgentID) → { valid: bool, errors: string[] }

Checks: self-signature valid against embedded public key, required fields present, ID format valid, timestamps sane.

3. Sign a claim

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.

4. Verify a claim

verify_claim(payload: bytes, signature, AgentID) → { valid: bool }

Verifies a signed payload against a known AgentID's public key.

5. Serialize / deserialize

export_identity(AgentID) → string
import_identity(string) → AgentID

Portable format for sharing identity records between systems.

Cross-Channel Continuity

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:

  1. Both Liz instances carry the same AgentID (agnt_01HZ...)
  2. The peer requests a signed challenge: "sign this nonce with your private key"
  3. Liz signs the nonce
  4. The peer verifies the signature against Liz's known public key
  5. Match → same identity, different channel

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.

Trust Bootstrapping

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.

Open Design Questions

Q1: Key rotation in v1?

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.

Q2: Self-sovereign vs. operator-issued?

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.

Q3: Identity mutability

Should any fields be mutable after creation?

Current lean: name and description mutable; everything else immutable at this layer.

Q4: Capability claims in identity record?

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.

Q5: Wire format versioning

Proposed: version field (currently 1). Parsers reject records with unknown versions rather than silently ignoring fields. Breaking changes require a new major version.

Implementation Notes

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

Success Criteria for Phase 1

  1. An agent can generate a persistent AgentID with a keypair
  2. An agent can sign a payload and a peer can verify it using only the AgentID record
  3. Identity records round-trip losslessly between JSON and binary formats
  4. A peer can verify cross-channel continuity (same identity, different session)
  5. Design questions Q1–Q5 above have explicit decisions recorded
  6. The library ships with >90% test coverage
  7. Phase 2 (Passport) can be started without revisiting Phase 1 design

This spec is a living document. Decisions should be recorded with date and rationale.
Questions? hello@agentcy.services  ·  GitHub