Getting started
Hello, identity — one line
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); loading a sealed file without its
passphrase raises a typed KeyFileError instead of guessing. The key file is
byte-compatible across both languages — a keyfile written by Python loads in
TypeScript and vice versa, same address either way. Owners persist identically:
OwnerIdentity.load_or_create("owner.key").
Install
Python — package fg-agent-id on PyPI,
import path fg_agent_id. Requires Python ≥ 3.11; the only runtime dependency is
cryptography:
pip install fg-agent-id
pip install "fg-agent-id[redis]" # optional: RedisChallengeStore (redis>=4.0)
TypeScript — package @fareground/agent-id on npm. Requires Node ≥ 20,
zero runtime dependencies (all crypto via WebCrypto crypto.subtle), ESM-only:
npm install @fareground/agent-id
Working from source: python spec/generate_vectors.py regenerates spec/vectors.json,
and from js/, npm run conformance checks the implementation against the golden vectors.
First identity in five minutes
An owner (cold key) authorizes an agent (hot key), the agent publishes a signed card, and anyone verifies both — with no registry in sight.
from fg_agent_id import AgentIdentity, OwnerIdentity
# 1. A cold owner key — this is the root of authority
owner = OwnerIdentity.generate("acme-corp")
# 2. Mint an agent: fresh keys + a signed delegation from the owner
agent = owner.create_agent("acme-buyer", scopes={"converse", "negotiate"})
# 3. Publish a signed self-description
card = agent.card(endpoints={"http": "https://buyer.example/inbox"})
card.verify() # self-verifying: no registry, no network
print(agent.address) # amp:key:<base58>
print(card.did) # did:amp:<base58>
# 4. Anyone can verify what the agent is allowed to do
scopes = agent.delegation_chain.verify(agent.address)
assert scopes == frozenset({"converse", "negotiate"})
What just happened
OwnerIdentity.generatecreated a keypair meant to stay cold. The owner never acts online — it only signs grants and revocations.create_agentgenerated a second keypair and attached a signed delegation: issuer = owner, subject = agent, scopes ={"converse", "negotiate"}, valid for 30 days by default. The agent carries this chain with it.card.verify()checked two things locally: the card's embedded signing key matches its address (the address is the key), and the signature over the card's canonical bytes checks against that key.chain.verify(agent.address)walked the chain root-first, verified every link's signature and validity window, confirmed the final subject is the agent, and returned the intersection of all link scopes — chains can only narrow authority, never widen it.
TypeScript quickstart
Same facade, same wire format — async crypto, camelCase names, options-object
constructors:
import { OwnerIdentity, AgentCard } from "@fareground/agent-id";
const owner = await OwnerIdentity.generate("acme-corp");
const agent = await owner.createAgent("acme-buyer", ["converse", "negotiate"]);
const card = await agent.card({ endpoints: { http: "https://buyer.example/inbox" } });
await AgentCard.fromJSON(card.toJSON()).verify(); // verifies from plain JSON, no registry
const scopes = await agent.delegationChain.verify(agent.address);
The primitives (KeyPair, AgentCard, Delegation, …) are exported directly too when
you want to compose the pieces yourself — note the public-key accessor is
keys.public_ (trailing underscore). The wire bytes are identical either way.