Getting started
Install
Python — package fg-agent-id, import path fg_agent_id (renamed from
fareground-agent-id). Requires Python ≥ 3.11; the only runtime dependency is
cryptography>=42.0:
pip install fg-agent-id
pip install "fg-agent-id[redis]" # optional: RedisChallengeStore (redis>=4.0)
TypeScript — package @fg/agent-id. Requires Node ≥ 20, zero runtime
dependencies (all crypto via WebCrypto crypto.subtle), ESM-only:
npm install @fg/agent-id
Working from source: pytest runs the Python suite; from js/, npm test builds and
runs the TS suite, and npm run conformance checks the golden vectors alone.
python spec/generate_vectors.py regenerates spec/vectors.json.
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 wire format, async crypto, camelCase names, options-object constructors. Note the
public-key accessor is keys.public_ (trailing underscore):
import {
KeyPair, addressFromSigningKey, AgentCard,
Delegation, DelegationChain, ChallengeStore,
} from "@fg/agent-id";
const keys = await KeyPair.generate();
const address = addressFromSigningKey(keys.public_.signing);
const card = await AgentCard.create({ keys, address, name: "my-agent" });
await card.verify();
const grant = await Delegation.grant({
issuerKeys: keys, issuerAddress: address,
subjectAddress: worker, scopes: ["read"], ttlSeconds: 3600,
});
const scopes = await new DelegationChain([grant]).verify(worker);
The TS twin exposes the primitives (KeyPair, AgentCard, Delegation, …) directly —
the Python AgentIdentity/OwnerIdentity convenience wrappers are not ported. Compose
the pieces yourself; the wire bytes are identical either way.