Skip to main content

Fareground

agent-id

Self-certifying identity for AI agents — no registry, no CA, no central authority.

CI Python TypeScript


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:amp DIDs. The same key expressed as a W3C DID (did:amp:<base58>), registry-free in the spirit of did: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 (aliased ParticipantIdentity) with load_or_create persistence and the keyfile helpers, AgentCard, Delegation / DelegationChain / Revocation / KeyRevocation / RevocationRegistry, ChallengeStore / Challenge / ChallengeResponse, RotatingIdentity (Python) / rotation classes, and the did:amp helpers. 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, the CONTEXT_* constants and DOMAIN. 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 (see spec/SPEC.md), not an API tweak. Build on the facade tier unless you are implementing the spec.

Concepts

  • Addressamp:key:<base58>; the Ed25519 public key itself, used as a stable identifier.
  • DIDdid: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.2 is a breaking change from 0.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 under 0.1 will 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

fg_agent_id-0.2.0.tar.gz (135.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fg_agent_id-0.2.0-py3-none-any.whl (56.8 kB view details)

Uploaded Python 3

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

Hashes for fg_agent_id-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b7992dd01c4689b273cc533f96b6730a6c7b4bc6c230509e5c9e3d7e8cd6401c
MD5 3421a1b7803abb68c39c523ffc2362e8
BLAKE2b-256 c84cdf4333d355208ed0c4e6ba07d3bf8f00d28033012f72648ad74d2ef9d615

See more details on using hashes here.

Provenance

The following attestation bundles were made for fg_agent_id-0.2.0.tar.gz:

Publisher: release.yml on Fareground/agent-id

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for fg_agent_id-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 43377c76a44b80c16e15ce0fbb4aa4478f6aa5cd9b55cea3508631eeeabb9257
MD5 f97d635a75bb2a61b9a8e7326c2f1465
BLAKE2b-256 a5423fc35cc91dd9800a72305cf66093354297d0b85112a14d5fc30e8a8b0a06

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page