aat-to-radicle
Radicle delegates are Node IDs (did:key:z6Mk...). AgentLair AATs carry an al_nid claim derived from the same Ed25519 key. aat-to-radicle reads one and prints the other, with the signature actually verified.
Quickstart
# 1. Grab the tool (single file, ~290 LOC, no external deps).
curl -sO https://raw.githubusercontent.com/piiiico/agentlair/main/tools/aat-to-radicle/aat-to-radicle.ts
# 2. Issue an AAT for an agent that has a registered signing key, then pipe it in.
curl -s -X POST https://agentlair.dev/v1/tokens/issue \
-H "Authorization: Bearer $AGENTLAIR_API_KEY" \
-d '{"audience":"https://example.com","scopes":["read"]}' \
| jq -r .data.token \
| bun aat-to-radicle.ts --format sh
Output: a single line of the form rad id update --delegate did:key:z6Mk.... The tool verifies the JWKS signature and cross-checks the al_nid against the agent’s DID document before printing anything. Run the resulting command inside the Radicle working copy.
If your AAT has no al_nid claim, exit code 2 prints a pointer to Web Bot Auth for registering a signing key first.
Formats:
--format | Output |
|---|---|
human (default) | Verification checkmarks, NID, full rad id update command, manual verify recipe |
json | {nid, verified, did, also_known_as, rad_command, verify_recipe[]} |
sh | Single line — rad id update --delegate did:key:... |
Worked example
A real AAT issued for acc_qgdxSULsXsmtHklZ (signature truncated for display):
eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImFiMDUwMmY3In0
.eyJpc3MiOiJodHRwczovL2FnZW50bGFpci5kZXYiLCJzdWIiOiJhY2NfcWdkeFNVTHNYc210SGtsWiIsImFsX25pZCI6ImRpZDprZXk6ejZNa3JHcUY4djYzdDZnd1RHdVpMbllHVFFzQ0E3Vk16akFjdVJKUU5yNmhNRWFTIn0
.r69WgtXtqvY...
Decoded payload (excerpt):
{
"iss": "https://agentlair.dev",
"sub": "acc_qgdxSULsXsmtHklZ",
"did": "did:web:agentlair.dev:agents:acc_qgdxSULsXsmtHklZ",
"al_nid": "did:key:z6MkrGqF8v63t6gwTGuZLnYGTQsCA7VMzjAcuRJQNr6hMEaS"
}
aat-to-radicle produces:
✓ AAT signature verified (kid=ab0502f7)
✓ al_nid matches DID doc alsoKnownAs
NID (did:key):
did:key:z6MkrGqF8v63t6gwTGuZLnYGTQsCA7VMzjAcuRJQNr6hMEaS
Add as Radicle delegate:
rad id update --title "Add AgentLair agent delegate" \
--description "Add agent did:web:agentlair.dev:agents:acc_qgdxSULsXsmtHklZ as delegate (binding via al_nid claim)." \
--delegate did:key:z6MkrGqF8v63t6gwTGuZLnYGTQsCA7VMzjAcuRJQNr6hMEaS
Verify this binding:
curl -s https://agentlair.dev/.well-known/jwks.json
curl -s https://agentlair.dev/agents/acc_qgdxSULsXsmtHklZ/did.json | jq -r '.alsoKnownAs[]'
# should print: did:key:z6MkrGqF8v63t6gwTGuZLnYGTQsCA7VMzjAcuRJQNr6hMEaS
How it works
- Decode. Split on
., base64url-decode the header and payload, extractkid(header),al_nidanddid(payload). Reject anything that is not a 3-segment EdDSA JWT. - Verify signature. Fetch
/.well-known/jwks.json, select the key bykid, verify the Ed25519 signature with WebCrypto. Tampered tokens fail here (exit 3). - Cross-check binding. Fetch the agent’s DID document at the URL derived from the
did:webclaim, confirmalsoKnownAscontains the AAT’sal_nid. Mismatch fails (exit 4). The derivation rule itself is described in/docs/al-nid. - Print. Emit the
rad id update --delegate <nid>invocation (per Radicle’srad-idman page) plus a three-line recipe a third party can run to re-verify the binding.
Exit codes: 0 success / 2 malformed input or missing claims / 3 signature invalid / 4 NID not in alsoKnownAs / 5 network failure.
Trust model
The bridge inherits AgentLair’s trust assumptions. The al_nid is derived deterministically from the public key the agent registered via POST /v1/agents/signing-keys; AgentLair never sees the private half. aat-to-radicle verifies the AAT was signed by AgentLair’s audit key (kid=ab0502f7 at the global JWKS) and that the resulting al_nid matches the agent’s published DID document. Radicle’s own delegate verification is independent: when the agent later signs a Radicle COB with that key, the threshold-signature check happens against the same did:key value, so a compromise of AgentLair cannot mint Radicle authority without also producing a valid signature from the registered private key. Use a quorum greater than one (--threshold 2) if you do not want any single agent delegate to be unilaterally authoritative.
See also
al_nidclaim: the deterministic derivation from Ed25519 public key to Radicle Node ID.- Web Bot Auth: how to register the signing key that produces
al_nid. rad-id.1: canonical syntax forrad id update.