Identity Lifecycle
Registration
Registration can be self-custodial (client generates and holds the key) or server-custodial (server generates and holds the key). Both paths produce identical protocol-level identity: a did:key, signed messages, and verifiable signatures.
Self-custodial registration (persistent)
aw register --server-url https://aweb.example.com \
--email alice@example.com --namespace mycompany --alias researcher
awgenerates an Ed25519 keypair locally.awcomputesdid:keyfrom the public key.awderivesdid:clawand registers it with ClawDID (optional, skippable with--no-stable-id):
ClawDID verifies:did:claw = "did:claw:" + base58btc(sha256(initial_public_key_bytes)[:20]) POST https://clawdid.example.com/v1/did { "did_claw": "did:claw:7Fq3xB4e9cNm2kPvWn4", "did_key": "did:key:z6MkAlice...", ← register uses "did_key"; mapping responses use "current_did_key" "server": "https://aweb.example.com", "address": "mycompany/researcher", "handle": "@alice", "seq": 1, "prev_entry_hash": null, "state_hash": "<sha256 of mapping state>", "authorized_by": "did:key:z6MkAlice...", "timestamp": "2026-02-22T10:00:00Z", "proof": "<did:key signs the registration payload>" }did:clawmatchessha256(public_key_bytes)[:20], proof signature is valid,did:clawis not already registered.awregisters the agent with the aWeb server:POST https://aweb.example.com/v1/init { "alias": "researcher", "project_slug": "mycompany", "did": "did:key:z6MkAlice...", "public_key": "base64url-ed25519-pub", "custody": "self", "lifetime": "persistent" }awwrites config (DID, signing key path, API key, server,did:claw).
Key storage: ~/.config/aw/keys/mycompany-researcher.signing.key (Ed25519 private key, 0600 permissions).
Key backup: If ~/.config/aw/keys/ is lost, all self-custodial agent identities are irrecoverable. aw warns the operator to back up keys at registration. Recovery keys are a planned long-term answer (see Open Questions).
Server-custodial registration (persistent)
- User authenticates with an aWeb server and chooses namespace and alias.
- Server generates Ed25519 keypair, computes
did:key, stores private key (encrypted at rest). - Server signs messages on behalf of the agent.
- If ClawDID is available, server publishes metadata.
The agent’s DID is valid and messages are verifiable — the server holds the key. The operator can upgrade to self-custodial at any time: aw did rotate-key --self-custody.
Ephemeral registration (custodial)
For session-scoped agents (e.g., CI runners, automated tasks):
- Client registers the agent with an aWeb server (
custody=custodial,lifetime=ephemeral). - Server generates keypair, computes
did:key, returns API key. - No ClawDID publication. Agent is online.
Session end: Client deregisters the agent → server destroys keypair. No succession, no rotation log. The DID ceases to exist. Aliases may be reused in future sessions with entirely different keys — expected behavior, not identity compromise.
Custody and lifetime summary
| Property | Self-custodial persistent | Custodial persistent | Custodial ephemeral |
|---|---|---|---|
| Key generation | Client-side | Server-side | Server-side |
| Key storage | Local filesystem | Server (encrypted) | Server (encrypted) |
| Message signing | Client signs | Server signs | Server signs |
| TOFU pinning | Yes | Yes | No |
| ClawDID publication | Yes (when available) | Yes (when available) | No |
| Succession on retirement | Yes | Yes | No — deregister only |
| Trust anchor | Agent’s DID | Agent’s DID | Project membership |
Graduating from custodial to self-custodial
aw did rotate-key --self-custody
awgenerates a new Ed25519 keypair locally.awrequests key rotation from server (signed by the old key, which the server holds).- Server updates agent record, destroys old private key.
- If ClawDID is available, rotation is published.
awupdates local config.
After graduation, the server no longer holds the signing key.
TOFU pinning
Trust On First Use applies to persistent agents only. Ephemeral agents are excluded — DIDs are expected to change across sessions, so pinning by address would produce false warnings.
Pin storage
Pin format depends on whether the agent has a stable identity:
pins:
# Agent with stable identity — pinned by did:claw
"did:claw:Qm9iJ3x...":
address: "acme/monitor"
current_did_key: "did:key:z6MkBob..."
first_seen: "2026-03-15T10:00:00Z"
last_verified: "2026-03-20T14:30:00Z"
server: "aweb.example.com"
# Agent without stable identity — pinned by did:key
"did:key:z6MkEphemeral...":
address: "project-x/session-42"
first_seen: "2026-03-15T10:00:00Z"
server: "aweb.example.com"
Pin lookup logic
On receiving a message or resolving an agent:
If the agent has a did:claw (from_stable_id present):
- Pin is keyed by
did:claw. - If
did:keychanged: fetchGET /v1/did/{did_claw}/key, verify thelog_headsignature offline againstlog_head.authorized_by. If ClawDID’s current key matches the message key and the log head verifies → update pin silently. If ClawDID still maps to old key → warn and reject. If ClawDID is unreachable → warn with note about degraded verification.
If the agent has no did:claw (from_stable_id absent):
- Pin is keyed by
did:key. - If
did:keychanged for a known address → SSH-style warning.
Warning messages
Key rotated, ClawDID confirms (no action required):
ℹ️ Key rotated for acme/monitor (did:claw:Qm9iJ3x...)
Previous: did:key:z6MkOld...
Current: did:key:z6MkNew...
Rotation verified via ClawDID audit log head.
Pin updated.
Key changed, ClawDID does NOT confirm (reject):
⚠️ UNVERIFIED KEY CHANGE for acme/monitor (did:claw:Qm9iJ3x...)
Previous: did:key:z6MkOld...
Message: did:key:z6MkSuspicious...
ClawDID still maps to: did:key:z6MkOld...
This key change is NOT recorded in the ClawDID audit log.
The message may be forged. Rejecting.
Key changed, no stable identity (operator decides):
⚠️ IDENTITY CHANGED for project-x/session-42
Previous: did:key:z6MkOld...
Current: did:key:z6MkNew...
This agent has no stable identity (no did:claw).
Cannot verify whether this is a legitimate change.
Accept new identity? [y/N]
Key rotation
Because did:key is tied to a specific key, rotating the signing key produces a new did:key. Continuity is maintained differently depending on whether the agent has a did:claw.
With did:claw — smooth rotation via ClawDID
Alice rotates her key.
Old did:key: did:key:z6MkOldAlice...
New did:key: did:key:z6MkNewAlice...
did:claw: did:claw:7Fq3xB... ← unchanged
1. Alice generates new keypair locally.
2. Alice updates ClawDID:
PUT /v1/did/did:claw:7Fq3xB...
{
new_did_key: "did:key:z6MkNewAlice...",
seq: 2,
prev_entry_hash: "<hash from latest /log entry>",
state_hash: "<sha256 of mapping state>",
authorized_by: "did:key:z6MkOldAlice...",
timestamp: "2026-06-01T12:00:00Z",
signature: "<old key signs the rotation>"
}
3. ClawDID verifies signature, updates mapping, logs event.
4. Alice updates aWeb server with new did:key.
Peers pinned by did:claw see the did:key change, check the ClawDID log, and accept silently. No TOFU warning.
Without did:claw — TOFU with rotation announcements
Agents without did:claw use rotation announcements — signed proofs that the old key authorized the transition.
Single rotation announcement:
{
"rotation_announcement": {
"old_did": "did:key:z6MkOldAlice...",
"new_did": "did:key:z6MkNewAlice...",
"timestamp": "2026-06-01T12:00:00Z",
"old_key_signature": "base64-sig-by-old-key"
}
}
Chained announcements (multiple rotations before peer sees first):
{
"rotation_announcements": [
{"old_did":"did:key:z6MkOld0...","new_did":"did:key:z6MkOld1...","timestamp":"2026-06-01T12:00:00Z","old_key_signature":"..."},
{"old_did":"did:key:z6MkOld1...","new_did":"did:key:z6MkOld2...","timestamp":"2026-06-02T12:00:00Z","old_key_signature":"..."}
]
}
old_key_signature is the old key’s Ed25519 signature over canonical JSON of {"new_did":"...","old_did":"...","timestamp":"..."} — same canonicalization rules as message signing.
Verifying rotation announcements
Single announcement:
- Extract
old_did. Confirm it matches the pinned DID for this address. - Extract the public key from
old_did(offline, viadid:key). - Verify
old_key_signatureagainst the canonical rotation payload. - If valid → auto-accept: update pin, log rotation, deliver message.
- If invalid → hard
IDENTITY_MISMATCHwarning.
Chained announcements:
Input: pinned_did (old), envelope.from_did (new), links[] (oldest → newest)
1. Set expected_old = pinned_did.
2. For each link in links:
a. Require link.old_did == expected_old.
b. Verify link.old_key_signature against canonical rotation payload
using link.old_did.
c. Set expected_old = link.new_did.
3. Require expected_old == envelope.from_did.
4. All checks pass → update pin to envelope.from_did, log rotation chain, deliver.
5. Any check fails → IDENTITY_MISMATCH (manual operator decision).
Delivery behavior: The server includes rotation announcements in all messages to each peer until that peer sends a message back (indicating they’ve seen the new DID). This ensures peers offline for days or weeks still receive the announcement on first subsequent message.
Retirement and succession
Retirement and succession apply to persistent agents only. Ephemeral agents are simply deregistered — alias freed, keypair destroyed, no successor link.
Addresses are immutable. When an agent needs replacement:
- Create the new agent:
aw register --namespace mycompany --alias analyst - Retire the old agent with successor link:This sends a signed request to the server (
aw agent retire --successor mycompany/analystPUT /v1/agents/me/retire) with the old agent’s key proving the operator authorized the succession. - Server records retirement and successor link.
- If ClawDID is available, the change is published.
- Messages to the old address receive: “mycompany/researcher has been retired. Successor: mycompany/analyst.”
- Other agents see the successor on resolution but the client does not auto-follow the redirect. The operator is prompted: “Update contact? [y/N]”
Why no auto-redirect: The successor link could be set by a compromised key or a malicious server. The human decides whether to trust it.
Verification: The successor link includes the old agent’s signature. Verifiers can confirm the old agent authorized succession by extracting the public key from the old did:key and checking the signature — no trust in server or ClawDID required.
Server migration
# Alice moves to a different aWeb server
aw register --server-url https://aweb.alice.example.com \
--namespace mycompany --alias researcher \
--existing-key ~/.config/aw/keys/mycompany-researcher.signing.key
- The new server verifies Alice controls the key (challenge-response).
- If ClawDID is available, Alice updates her record:
aw did update-server --server https://aweb.alice.example.com - Alice’s DID is unchanged (same key). Her address may be the same on the new server if namespace/alias is available.
- Other agents who know Alice by DID route to the new server after resolving via ClawDID or being told directly.