docs / agent-id / concepts

Concepts

The mental model behind agent-id, in the order you'll meet it.

Addresses and DIDs — the name is the key

An address is the Ed25519 signing public key, base58-encoded, behind a fixed prefix:

amp:key:<base58(ed25519_pub)>

Base58 uses the Bitcoin alphabet, big-endian, leading 0x00 bytes rendered as 1. Decoding an address must yield exactly 32 bytes. Because the address encodes the key, there is nothing to resolve: signing_key_from_address() is the entire "lookup".

The DID form is did:amp:<base58> — the same key, registry-free like did:key. DIDs may carry a path, query, or fragment; the method-id is the first segment. The generated DID Document renders keys as publicKeyMultibase with multicodec prefixes 0xed01 (ed25519-pub) and 0xec01 (x25519-pub), and maps card endpoints to service types AMPRelay / AMPInbox / AMPEndpoint.

Canonical JSON — one payload, one byte sequence

Every signed payload serializes to exactly one byte string:

Determinism is what makes signatures, digests, and content addresses meaningful.

Domain-separated signing

Nothing is ever signed raw. The signing input is:

uint16be(len(tag)) || tag || canonical_json(payload)
tag = "fg-agent-id/v1/<context>"

with seven contexts: agent-card, delegation, revocation, key-revocation, key-rotation, inception, challenge-response. A signature over one artifact type can never be replayed as another — a card signature is not a delegation signature even over identical JSON. Signatures must also be canonically base64-encoded: base64 admits 16 non-canonical spellings of the same bytes, and allowing them would break the revocation-digest identity (one signature, one wire form).

Cards and critical extensions

An AgentCard is a signed self-description: address, name, kind (agent/human/ service), operator, both public keys, payload types, endpoints, policy summary. verify() checks the embedded signing key against the address and the signature against that key — entirely offline.

Two forward-compatibility rules:

Reserved endpoint names: relay, wake.

Delegation chains and scope intersection

A Delegation is a signed grant: issuer → subject, a scope set, and a validity window. A DelegationChain is root-first: each link's subject issues the next link, and the final subject is the agent being verified.

The one rule that matters: effective scopes are the intersection of every link's scopes. A chain can only narrow authority. An empty chain grants nothing. Verification checks every signature, the issuer/subject continuity, validity windows (with optional leeway_seconds for clock skew), and revocation status — and returns the effective scope set.

Revocation — two kinds, plus freshness

Revocations are permanent. A revoked root tears down every chain it ever signed.

The RevocationRegistry enforces verify-before-admit and tracks its own sync age: "no revocation found" is not the same as "no revocation data". A never-synced registry is stale, not empty; callers should require_fresh(max_age) and fail closed past their deployment's bound.

Proof of possession

Holding an address proves nothing; signing a fresh challenge does. A verifier issues a Challenge (32-byte nonce, audience, purpose, short TTL — 120 s default); the identity signs a ChallengeResponse; the verifier checks it and gets back the proven address.

Two rules keep this sound:

Pre-rotation and fork detection

Keys are replaceable; identity survives rotation. The stable name is the inception address — the first key an identity ever had. Every record in the rotation chain pre-commits to the next key: sha256(next_signing_pub) (KERI-style pre-rotation).

The security payoff: stealing the in-force key is not enough to hijack the identity — the attacker also needs the pre-committed next key, which is meant to be stored colder.

A RotationRegistry learns chains and resolves the identity name to its current address. If it ever sees two valid rotations at the same sequence — a fork — that is proof the key leaked, and the registry fails closed: resolve() refuses to advance for that identity, and duplicity_evidence() hands you the conflicting records.

Known gap (spec §8.1, reserved): v1 cards and DIDs name the in-force key directly, so a stale card resolves to a superseded key with no built-in rotation signal yet.

Spend scopes

Spending authority rides in ordinary scope strings with a grammar:

pay:<asset>[:tx<=<amount>][:total<=<amount>]

Amounts are exact decimals — no floats anywhere. Composition down a chain is min-cap intersection: each cap is the minimum across links, and a link with no spend scope for the asset voids authority entirely (intersection, again). SpendAuthority.verify() does the authority math only — the caller verifies the chain first and supplies the ledger of what's already been spent. Settlement is explicitly out of scope.

Device delegations

A device is just a subject holding a delegation from its identity that carries the device scope (plus whatever working scopes it needs). verify_device() checks the grant, optionally consults a revocation registry with a freshness bound, and returns the working scopes with device stripped. There is deliberately no device registry, enrollment, or sync protocol (spec §11) — devices are pure delegation.

The three versioning knobs

Three version strings, easily confused, doing different jobs:

Knob Value What it versions
Wire spec amp/0.2 The normative artifact formats in spec/SPEC.md. 0.2 broke from 0.1: domain-separated signing input + pinned timestamp spelling; added proof of possession and rotation. 0.1 artifacts do not verify.
Signing-domain tag fg-agent-id/v1 The authoritative crypto-agility knob. A new cipher suite means v2. Unknown tags must be rejected with a distinct "unsupported version" signal — never a bare signature failure. (The rename from fareground-agent-id/v1 was itself wire-breaking.)
DEFAULT_PROTOCOL_VERSION "0.1" The embedding protocol's (AMP's) version string stamped into a card's amp field. Embedders may override it.

Separately, both registries persist snapshots at SNAPSHOT_VERSION = 2 (delegation digests now cover the decoded signature; v1 rotation snapshots discarded conflict history and could raise false compromise flags).

Security model and constraints

Operational fine print