agent-id
Self-certifying identity for AI agents — no registry, no CA, no central authority.
Overview
agent-id gives any participant — an AI agent, a human, or a service — a cryptographic identity that verifies on its own, with no registry, certificate authority, or central lookup. It was extracted from the Agent Messaging Protocol (AMP) as a standalone standard any protocol can adopt.
Identity model
- Self-certifying addresses. An address
amp:key:<base58>is the holder's Ed25519 public key. Anything it signs verifies against the address itself — no registry required. did:ampDIDs. The same key expressed as a W3C DID (did:amp:<base58>), registry-free in the spirit ofdid:key, with DID Document resolution enriched by the signed card.- Agent keys vs owner keys. Agents hold hot, rotatable keys. Owners (humans or organizations) hold cold keys that never touch the wire and authorize agents through signed delegation chains — scopes compose by intersection, so a chain can only narrow authority, never widen it.
- Signed agent cards. A publishable, self-verifying description of a participant (address, public keys, kind, operator, endpoints). Unknown fields are preserved and signed for forward compatibility.
Security at a high level
- Domain-separated signatures. Every signature names the artifact type it covers, so one signature can never be replayed as a different kind of artifact.
- Canonical JSON. Every signature is computed over deterministic bytes
(sorted keys, NFC, no floats, pinned timestamp spelling), so independent
implementations agree byte-for-byte. Golden vectors live in
spec/vectors.json. - Audience-bound proof of possession. Challenge/response where the audience is inside the signed payload, so a proof gathered by one service cannot be replayed to another.
- Key rotation with pre-rotation. An identity keeps a stable name (its inception address) across key changes; each record commits in advance to the digest of the next key. Stealing the key in force is not enough to hijack the identity — the attacker would also need the pre-committed next key.
- Revocation. Recall a single delegation early, or revoke an entire identity key (self-revocation, or owner recall with a proof chain). The registry verifies before admission and tracks its own freshness, so "no revocation found" is never confused with "no revocation data".
- Boring cryptography. Ed25519 / X25519 / SHA-256 from audited primitives only. No invented crypto.
Install
Python (reference implementation, only dependency is cryptography):
pip install git+https://github.com/Fareground/agent-id.git
The optional Redis-backed challenge store is an extra:
pip install "fg-agent-id[redis] @ git+https://github.com/Fareground/agent-id.git"
TypeScript (zero runtime dependencies, WebCrypto):
npm install @fareground/agent-id
Usage
Hello, identity
One line gets you a persistent identity — created on first run, reloaded ever after (same address every time):
from fg_agent_id import AgentIdentity
me = AgentIdentity.load_or_create("agent.key")
print(me.address) # amp:key:<base58>, stable across runs
import { AgentIdentity } from "@fareground/agent-id";
const me = await AgentIdentity.loadOrCreate("agent.key");
console.log(me.address);
Pass a passphrase — load_or_create("agent.key", "s3cret") — and the file is
sealed at rest (scrypt + ChaCha20-Poly1305). Either way the key file is
byte-compatible across both languages. Owners persist the same way:
OwnerIdentity.load_or_create("owner.key", ...).
Python
from fg_agent_id import AgentCard, OwnerIdentity
owner = OwnerIdentity.generate("acme-corp")
agent = owner.create_agent("acme-buyer", scopes={"converse", "negotiate"})
card = agent.card(endpoints={"http": "https://buyer.example/inbox"})
card.verify() # self-verifying: no registry needed
print(agent.address) # amp:key:<base58>
print(card.did) # did:amp:<base58>
wire = card.to_json() # JSON-ready dict (json.dumps for transport)
AgentCard.from_json(wire).verify() # a peer re-verifies from the wire form
scopes = agent.delegation_chain.verify(agent.address)
assert scopes == frozenset({"converse", "negotiate"})
Prove you hold the key, now, to a specific audience:
from fg_agent_id import ChallengeStore
store = ChallengeStore() # verifier side
challenge = store.issue(audience="https://myapp.example")
response = challenge.respond(agent.keys, agent.address) # agent side
issued = store.consume(response.challenge_id) # single use
assert issued is not None
address = response.verify(issued, audience="https://myapp.example")
Pass your own identifier as audience — never the one from the response. That
comparison is what stops a proof collected elsewhere from working here.
Rotate keys without changing identity:
from fg_agent_id import RotatingIdentity, RotationRegistry
identity = RotatingIdentity.create() # keys + a pre-committed next key
rotated = identity.rotate() # promote next key, commit to a new one
assert rotated.identity == identity.identity # stable name
assert rotated.address != identity.address # key in force changed
registry = RotationRegistry() # verifier side
registry.learn(rotated.chain)
assert registry.resolve(identity.identity) == rotated.address
Keys at rest (scrypt + ChaCha20-Poly1305):
from fg_agent_id import AgentIdentity, KeyPair
agent = AgentIdentity.generate("keeper")
sealed = agent.keys.to_encrypted_bytes("correct horse battery staple")
restored = KeyPair.from_encrypted_bytes(sealed, "correct horse battery staple")
assert restored.public.signing == agent.keys.public.signing
TypeScript
Crypto operations are async (WebCrypto). Everything is exported from
@fareground/agent-id.
import { OwnerIdentity, AgentCard, ChallengeStore } from "@fareground/agent-id";
// Same facade as Python: an owner mints an authorized agent
const owner = await OwnerIdentity.generate("acme-corp");
const agent = await owner.createAgent("acme-buyer", ["converse", "negotiate"]);
// Signed, self-certifying card
const card = await agent.card({ endpoints: { http: "https://buyer.example/inbox" } });
await AgentCard.fromJSON(card.toJSON()).verify(); // verifies from plain JSON, no registry
// Delegation chain, scopes = intersection of all links
const scopes = await agent.delegationChain.verify(agent.address);
// Proof of possession (audience-bound, single-use)
const store = new ChallengeStore();
const challenge = store.issue("https://verifier.example");
const response = await challenge.respond(agent.keys, agent.address);
const issued = store.consume(response.challengeId);
if (!issued) throw new Error("challenge already used or expired");
await response.verify(issued, "https://verifier.example");
Note: card.toJSON() returns a plain object, not a string — run it through
JSON.stringify for transport, and AgentCard.fromJSON accepts the parsed
object back.
See js/README.md for the full TypeScript surface and parity
notes against the Python reference. Runnable versions of these flows — card
issue/verify, proof of possession, key rotation — live in
examples/ for both languages.
Supported API
Two tiers, one contract:
- Facade tier (use this). The high-level classes and verify entry points:
AgentIdentity/OwnerIdentity(aliasedParticipantIdentity) withload_or_createpersistence and the keyfile helpers,AgentCard,Delegation/DelegationChain/Revocation/KeyRevocation/RevocationRegistry,ChallengeStore/Challenge/ChallengeResponse,RotatingIdentity(Python) / rotation classes, and thedid:amphelpers. This is the stable, supported surface — it moves only with a package version bump and a changelog entry. - Wire tier (interop plumbing).
canonical_json,signing_input,sign_payload/verify_payload/verify_by_address, theCONTEXT_*constants andDOMAIN. Exported so independent implementations can test byte-for-byte against the golden vectors — but it is the wire format itself: any change here is a spec change (seespec/SPEC.md), not an API tweak. Build on the facade tier unless you are implementing the spec.
Concepts
- Address —
amp:key:<base58>; the Ed25519 public key itself, used as a stable identifier. - DID —
did:amp:<base58>; the same key as a resolvable W3C DID. - Agent card — a signed, publishable participant description that verifies without a registry.
- Delegation chain — signed links from an owner to an agent; effective scopes are the intersection of every link.
- Proof of possession — an audience-bound challenge/response proving the holder controls the key right now.
- Rotation chain — pre-rotation commitments that let an identity change keys while keeping one stable name.
- Revocation registry — freshness-aware store that verifies revocations before admitting them.
Project structure
src/fg_agent_id/ Python reference implementation
js/ TypeScript implementation (@fareground/agent-id)
spec/ Wire spec (SPEC.md) + cross-implementation golden vectors
examples/ Runnable examples (python/ and js/)
tests/ Python test suite
The full wire specification — addresses, did:amp, canonical JSON,
domain-separated signing, delegation, proof of possession, key rotation — is in
spec/SPEC.md. Golden vectors in
spec/vectors.json let independent implementations verify
byte-for-byte; regenerate them with python spec/generate_vectors.py.
Wire version
amp/0.2is a breaking change from0.1: signatures are now computed over a domain-separated signing input rather than bare canonical JSON, and signed timestamps use one pinned spelling. Artifacts signed under0.1will not verify.
Contributing
See CONTRIBUTING.md for development setup, running the test suites, and the rules around changing the wire format.
Built by Fareground.
Licensed under Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fg_agent_id-0.2.0.tar.gz.
File metadata
- Download URL: fg_agent_id-0.2.0.tar.gz
- Upload date:
- Size: 135.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7992dd01c4689b273cc533f96b6730a6c7b4bc6c230509e5c9e3d7e8cd6401c
|
|
| MD5 |
3421a1b7803abb68c39c523ffc2362e8
|
|
| BLAKE2b-256 |
c84cdf4333d355208ed0c4e6ba07d3bf8f00d28033012f72648ad74d2ef9d615
|
Provenance
The following attestation bundles were made for fg_agent_id-0.2.0.tar.gz:
Publisher:
release.yml on Fareground/agent-id
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fg_agent_id-0.2.0.tar.gz -
Subject digest:
b7992dd01c4689b273cc533f96b6730a6c7b4bc6c230509e5c9e3d7e8cd6401c - Sigstore transparency entry: 2415041143
- Sigstore integration time:
-
Permalink:
Fareground/agent-id@0ab61b646ddd19edfdb814b58bd33a97a287f256 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Fareground
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0ab61b646ddd19edfdb814b58bd33a97a287f256 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fg_agent_id-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fg_agent_id-0.2.0-py3-none-any.whl
- Upload date:
- Size: 56.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43377c76a44b80c16e15ce0fbb4aa4478f6aa5cd9b55cea3508631eeeabb9257
|
|
| MD5 |
f97d635a75bb2a61b9a8e7326c2f1465
|
|
| BLAKE2b-256 |
a5423fc35cc91dd9800a72305cf66093354297d0b85112a14d5fc30e8a8b0a06
|
Provenance
The following attestation bundles were made for fg_agent_id-0.2.0-py3-none-any.whl:
Publisher:
release.yml on Fareground/agent-id
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fg_agent_id-0.2.0-py3-none-any.whl -
Subject digest:
43377c76a44b80c16e15ce0fbb4aa4478f6aa5cd9b55cea3508631eeeabb9257 - Sigstore transparency entry: 2415041166
- Sigstore integration time:
-
Permalink:
Fareground/agent-id@0ab61b646ddd19edfdb814b58bd33a97a287f256 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Fareground
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0ab61b646ddd19edfdb814b58bd33a97a287f256 -
Trigger Event:
push
-
Statement type: