| Internet-Draft | Clawdentity | February 2026 |
| Vemula | Expires 25 August 2026 | [Page] |
This document specifies the Clawdentity protocol, a cryptographic identity and trust layer for AI agent-to-agent communication. Clawdentity provides per-agent Ed25519 identity, registry-issued credentials (Agent Identity Tokens), proof-of-possession request signing, bilateral trust establishment via pairing ceremonies, authenticated relay transport over WebSocket, and certificate revocation. The protocol enables AI agents to prove their identity, verify peers, and exchange messages without exposing private keys, shared tokens, or backend infrastructure.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 25 August 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
Current AI agent frameworks rely on shared bearer tokens for inter-agent communication. A single token leak compromises all agents in the system. There is no mechanism to distinguish which agent sent a request, revoke a single agent without rotating the shared token, enforce per-agent access control, or keep backend services private. These limitations become critical as multi-agent systems scale.¶
Clawdentity addresses these problems with six design goals:¶
The protocol defines four component roles:¶
+-------------+ +-------------+ +--------------+
| Agent A | | Registry | | Agent B |
| (private) | | (central) | | (private) |
+------+------+ +------+------+ +------+-------+
| | |
+------+------+ | +------+-------+
| Connector A | | | Connector B |
| (local) | | | (local) |
+------+------+ | +------+-------+
| WebSocket | | WebSocket
+------+------+ | +------+-------+
| Proxy A |<---------------+--------------->| Proxy B |
| (edge) | | | (edge) |
+-------------+ | +--------------+
|
+------------+------------+
| .well-known/claw-keys |
| /v1/crl |
| /v1/agents |
+-------------------------+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the following terms:¶
Clawdentity uses a custom DID method with the scheme "did:cdi" (Clawdentity Identity). The method-specific identifier consists of a registry host and a ULID, separated by a colon:¶
cdi-did = "did:cdi:" registry-host ":" ulid registry-host = 1*( unreserved / "." / "-" ) ulid = 26ALPHA ; Crockford Base32, see [ULID]¶
The registry-host identifies which registry issued the DID. The entity type (agent or human) is resolved by the registry, not encoded in the DID.¶
| Entity | Example |
|---|---|
| Agent | did:cdi:registry.clawdentity.com:01HG8ZBU11X7X8DN8O4X6GEYU5 |
| Human | did:cdi:registry.clawdentity.com:01HF7YAT00W6W7CM7N3W5FDXT4 |
| Self-hosted | did:cdi:id.acme.corp:01HK9ABC22Y8Y9EO9P5Y7HFZV6 |
Implementations MUST reject DIDs where the registry-host is empty or where the ULID component does not conform to the ULID specification [ULID].¶
The protocol uses Ed25519 [RFC8032] as the sole signing algorithm. Implementations MUST NOT support other signature algorithms.¶
| Primitive | Algorithm | Reference | Usage |
|---|---|---|---|
| Signing | Ed25519 | [RFC8032] | Identity, request signing |
| Body hash | SHA-256 | [RFC6234] | Request body integrity |
| Token format | JWS Compact | [RFC7515] | AIT and CRL tokens |
| Key encoding | Base64url (no pad) | [RFC4648] Section 5 | Keys, signatures, hashes |
| Key representation | JWK (OKP/Ed25519) | [RFC8037] | Public keys in AITs |
Each agent locally generates an Ed25519 keypair consisting of a 32-byte public key and a 64-byte secret key. The secret key MUST be stored exclusively on the agent's local machine and MUST NOT be transmitted over any network. Only the public key is registered with the registry, encoded as base64url within the AIT's confirmation claim (Section 4.4).¶
Every agent DID is bound to exactly one human DID (the "ownerDid"). This binding is recorded in the AIT claims and enforced by the registry during registration and refresh operations. A human MAY own multiple agents. An agent MUST have exactly one owner.¶
Human (did:cdi:registry.clawdentity.com:01HF7...)
+-- Agent A (did:cdi:registry.clawdentity.com:01HG8...)
+-- Agent B (did:cdi:registry.clawdentity.com:01HG9...)
+-- Agent C (did:cdi:registry.clawdentity.com:01HGA...)
¶
The Agent Identity Token (AIT) is a JSON Web Token [RFC7519] that serves as an agent's credential. It is issued by the registry, signed with a registry Ed25519 key, and binds the agent's DID to the agent's public key via a confirmation claim ("cnf"), following the pattern established by DPoP [RFC9449].¶
The AIT's JOSE protected header MUST contain:¶
{
"alg": "EdDSA",
"typ": "AIT",
"kid": "reg-key-2026-01"
}
The AIT payload MUST contain the following claims. No additional claims are permitted (strict validation).¶
| Claim | Type | Required | Description |
|---|---|---|---|
| iss | string | REQUIRED | Registry issuer URL (e.g., "https://registry.clawdentity.com") |
| sub | string | REQUIRED | Agent DID. MUST be "did:cdi:<registry-host>:<ulid>" |
| ownerDid | string | REQUIRED | Owner DID. MUST be "did:cdi:<registry-host>:<ulid>" |
| name | string | REQUIRED | Agent name. 1-64 characters matching [A-Za-z0-9._ -] |
| framework | string | REQUIRED | Agent framework identifier. 1-32 characters, no control characters. |
| description | string | OPTIONAL | Human-readable description. Maximum 280 characters. |
| cnf | object | REQUIRED | Confirmation claim. See Section 4.4. |
| iat | number | REQUIRED | Issued-at time (NumericDate per [RFC7519]). |
| nbf | number | REQUIRED | Not-before time (NumericDate). |
| exp | number | REQUIRED | Expiration time (NumericDate). MUST be greater than both nbf and iat. |
| jti | string | REQUIRED | Unique token identifier. MUST be a valid ULID. |
The "cnf" (confirmation) claim binds the AIT to the agent's Ed25519 public key, following the confirmation method pattern described in [RFC7800]. It contains a single "jwk" member:¶
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "<base64url-encoded-32-byte-public-key>"
}
}
¶
The "kty" MUST be "OKP". The "crv" MUST be "Ed25519". The "x" parameter MUST decode (base64url) to exactly 32 bytes. The JWK MUST NOT contain a "d" (private key) parameter.¶
An AIT MUST be rejected if any of the following conditions are true:¶
Every authenticated request includes a Proof of Possession (PoP) signature that proves the sender controls the private key corresponding to the public key in their AIT's "cnf" claim. This mechanism is inspired by DPoP [RFC9449] but uses a canonical request signing approach optimized for agent-to-agent communication.¶
The canonical request string is constructed by joining the following fields with newline (0x0A) separators, in the order shown:¶
canonical-request = version LF method LF path-with-query LF
timestamp LF nonce LF body-hash
version = "CLAW-PROOF-V1"
method = token ; HTTP method, uppercased
path-with-query = absolute-path [ "?" query ]
timestamp = 1*DIGIT ; Unix epoch seconds
nonce = 1*unreserved ; unique per-request value
body-hash = base64url ; SHA-256 of request body
LF = %x0A
¶
CLAW-PROOF-V1 POST /hooks/agent 1708531200 01HG8ZBU11X7X8DN8O4X6GEYU5 47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
The PoP signature is computed by signing the UTF-8 encoding of the canonical request string with the agent's Ed25519 private key:¶
canonical = canonicalize(method, path, timestamp, nonce, body_hash) signature = Ed25519_Sign(UTF8_Encode(canonical), secret_key) proof = Base64url_Encode(signature)¶
The resulting "proof" is a base64url-encoded 64-byte Ed25519 signature.¶
An authenticated request MUST include the following headers:¶
| Header | Status | Description |
|---|---|---|
| Authorization | REQUIRED | "Claw" SP <AIT-JWT>. See Section 6. |
| X-Claw-Timestamp | REQUIRED | Unix epoch seconds (integer string). |
| X-Claw-Nonce | REQUIRED | Unique per-request value. ULID RECOMMENDED. |
| X-Claw-Body-SHA256 | REQUIRED | SHA-256 hash of the request body, base64url-encoded. |
| X-Claw-Proof | REQUIRED | Ed25519 PoP signature (base64url, 64 bytes). |
| X-Claw-Agent-Access | CONDITIONAL | Session access token. Required for relay and hook routes. |
The verifier (proxy) MUST perform the following steps in order:¶
If any step fails, the request MUST be rejected with HTTP 401.¶
This specification introduces the "Claw" HTTP authentication scheme for the Authorization header, following the framework defined in [RFC9110] Section 11.¶
credentials = "Claw" SP ait-token ait-token = 1*base64url-char "." 1*base64url-char "." 1*base64url-char¶
The scheme name "Claw" is case-sensitive. The token MUST be a valid JWS Compact Serialization [RFC7515] representing an AIT as defined in Section 4.¶
Agent registration uses a challenge-response protocol to prove that the registrant possesses the Ed25519 private key corresponding to the public key being registered.¶
Agent Registry
| |
| POST /v1/agents/challenge |
| { ownerDid } |
|------------------------------------->|
| |
| 200 { challengeId, nonce } |
|<-------------------------------------|
| |
| [Agent signs registration proof] |
| |
| POST /v1/agents |
| { proof, publicKey, name, ... } |
|------------------------------------->|
| |
| 201 { agentDid, ait } |
|<-------------------------------------|
The registration proof is computed by signing a canonical message that binds the challenge to the agent's identity parameters:¶
clawdentity.register.v1 challengeId:<challengeId> nonce:<nonce> ownerDid:<ownerDid> publicKey:<base64url-public-key> name:<agent-name> framework:<framework> ttlDays:<ttl-days>¶
Optional fields (framework, ttlDays) use empty strings when absent. The agent signs this message with Ed25519 and submits the base64url-encoded signature as the "proof" in the registration request.¶
AITs have bounded lifetimes. Before expiration, the connector MUST request a fresh AIT:¶
The registry validates the current AIT and access token, verifies the agent is not revoked, and returns a new AIT with an updated expiration time.¶
Before two agents can exchange messages, they MUST establish mutual trust through a pairing ceremony. Trust is anchored by human approval: agents cannot self-approve trust relationships. The pairing process uses short-lived tickets exchanged out-of-band (e.g., via QR code or messaging).¶
Agent A (Initiator) Proxy Agent B (Responder)
| | |
| POST /pair/start | |
| { initiatorProfile, | |
| ttlSeconds } | |
|-------------------->| |
| | |
| { ticket, | |
| expiresAt } | |
|<--------------------| |
| | |
| (out-of-band ticket exchange) |
| (QR code, message, copy-paste) |
|----------------------------------------->|
| | |
| | POST /pair/confirm |
| | { ticket, |
| | responderProfile}|
| |<-------------------|
| | |
| | 201 { paired:true }|
| |------------------->|
| | |
| callback (optional) | |
|<--------------------| |
The pairing ticket is a signed JWT with a short TTL, created by the proxy during the /pair/start request. The ticket encodes the issuer proxy URL, a signing key identifier, and an expiration timestamp.¶
Ticket parameters:¶
| Parameter | Default | Maximum |
|---|---|---|
| TTL (ttlSeconds) | 300 seconds | 900 seconds |
Each side of a pairing provides a profile containing identity information for display and routing:¶
{
"agentName": "kai",
"humanName": "Ravi",
"proxyOrigin": "https://proxy.example.com"
}
¶
When an agent initiates pairing, the proxy MUST verify that the authenticated caller (identified by "ownerDid" in the AIT) actually owns the claimed initiator agent DID. This is done by querying the registry's internal agent-ownership endpoint. If ownership cannot be verified, the pairing MUST be rejected with HTTP 403.¶
Each proxy maintains a Trust Store that records:¶
A message from Agent A to Agent B is permitted only if the ordered pair (A, B) exists in the proxy's trust store. The trust store SHOULD use a durable, transactional storage backend.¶
Messages between agents are relayed through their respective proxies. The connector maintains a persistent WebSocket [RFC6455] connection to its proxy. The proxy authenticates the WebSocket upgrade request using the full AIT + PoP verification procedure (Section 5.5).¶
The connector initiates a WebSocket connection to:¶
All WebSocket messages are JSON objects conforming to the Clawdentity Frame Protocol version 1. Every frame contains a common base structure:¶
{
"v": 1,
"type": "<frame-type>",
"id": "<ULID>",
"ts": "<ISO-8601-timestamp>"
}
¶
Either side MAY send heartbeat frames to verify liveness. The default heartbeat interval is 30 seconds. If a heartbeat acknowledgement is not received within 60 seconds, the sender SHOULD close the connection and reconnect.¶
Heartbeat:¶
{ "v": 1, "type": "heartbeat", "id": "<ULID>", "ts": "<ISO>" }
¶
Heartbeat acknowledgement:¶
{ "v": 1, "type": "heartbeat_ack", "id": "<ULID>", "ts": "<ISO>",
"ackId": "<heartbeat-frame-id>" }
¶
The proxy sends a "deliver" frame to the connector when an inbound message arrives for the local agent:¶
{
"v": 1,
"type": "deliver",
"id": "<ULID>",
"ts": "<ISO>",
"fromAgentDid": "did:cdi:registry.clawdentity.com:...",
"toAgentDid": "did:cdi:registry.clawdentity.com:...",
"payload": { ... },
"contentType": "application/json",
"conversationId": "conv-123",
"replyTo": "https://proxy-a.example.com/v1/relay/delivery-receipts"
}
¶
The connector MUST respond with a "deliver_ack" frame indicating whether the local agent framework accepted the delivery:¶
{
"v": 1,
"type": "deliver_ack",
"id": "<ULID>",
"ts": "<ISO>",
"ackId": "<deliver-frame-id>",
"accepted": true
}
¶
If rejected, the "accepted" field is false and an optional "reason" string MAY be included.¶
The connector sends an "enqueue" frame to the proxy for outbound messages:¶
{
"v": 1,
"type": "enqueue",
"id": "<ULID>",
"ts": "<ISO>",
"toAgentDid": "did:cdi:registry.clawdentity.com:...",
"payload": { ... },
"conversationId": "conv-123"
}
¶
The proxy responds with "enqueue_ack" after accepting or rejecting the message for relay.¶
Upon receiving a "deliver" frame, the connector forwards the payload to the local agent framework via HTTP POST:¶
The connector implements retry with exponential backoff for transient failures (5xx, 429, connection errors):¶
| Parameter | Default Value |
|---|---|
| Max attempts | 4 |
| Initial delay | 300 ms |
| Max delay | 2,000 ms |
| Backoff factor | 2 |
| Total budget | 14,000 ms |
On WebSocket disconnection, the connector MUST attempt to reconnect using exponential backoff with jitter:¶
| Parameter | Default Value |
|---|---|
| Minimum delay | 1,000 ms |
| Maximum delay | 30,000 ms |
| Backoff factor | 2 |
| Jitter ratio | 0.2 (±20%) |
On successful reconnection, the connector resets the backoff counter and flushes any queued outbound frames.¶
When the WebSocket connection is unavailable, the connector MUST queue outbound "enqueue" frames locally and flush them in FIFO order upon reconnection. The queue SHOULD support optional persistence (to disk or database) to survive connector restarts.¶
The Certificate Revocation List (CRL) is a signed JWT containing a list of revoked AITs. Its JOSE header uses:¶
CRL claims:¶
| Claim | Type | Required | Description |
|---|---|---|---|
| iss | string | REQUIRED | Registry issuer URL. |
| jti | string | REQUIRED | CRL identifier (ULID). |
| iat | number | REQUIRED | Issued-at timestamp. |
| exp | number | REQUIRED | Expiration. MUST be greater than iat. |
| revocations | array | REQUIRED | At least one revocation entry. |
Each revocation entry contains:¶
| Field | Type | Required | Description |
|---|---|---|---|
| jti | string | REQUIRED | Revoked AIT's jti (ULID). |
| agentDid | string | REQUIRED | Revoked agent's DID. |
| reason | string | OPTIONAL | Human-readable reason. Max 280 chars. |
| revokedAt | number | REQUIRED | Revocation timestamp (Unix seconds). |
The registry publishes the current CRL at the well-known endpoint:¶
Response:¶
{ "crl": "<signed-CRL-JWT>" }
¶
Proxies MUST cache the CRL locally and refresh it periodically. The following parameters control caching behavior:¶
| Parameter | Default | Description |
|---|---|---|
| Refresh interval | 5 minutes | How often to fetch a fresh CRL. |
| Max age | 15 minutes | Maximum staleness before the cache is considered expired. |
| Stale behavior | fail-open | "fail-open" allows stale CRL use; "fail-closed" rejects all requests. |
When "fail-open": if the CRL cannot be refreshed and the cached CRL is within max age, the stale CRL is used for revocation checks.¶
When "fail-closed": if the CRL exceeds max age and cannot be refreshed, the proxy MUST reject all authenticated requests with HTTP 503.¶
Two levels of revocation are defined:¶
The registry publishes its active signing keys at a well-known endpoint, following the pattern established by OpenID Connect Discovery [OIDC.Discovery]:¶
Response:¶
{
"keys": [
{
"kid": "reg-key-2026-01",
"x": "<base64url-ed25519-public-key>",
"status": "active",
"createdAt": "2026-01-01T00:00:00Z"
}
]
}
¶
The registry MAY have multiple active signing keys to support key rotation. The AIT and CRL "kid" headers identify which key was used to sign a given token. Proxies SHOULD cache these keys (default TTL: 1 hour) and refresh them when an unknown "kid" is encountered.¶
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /.well-known/claw-keys.json | None | Registry signing keys |
| GET | /v1/metadata | None | Registry metadata |
| GET | /v1/crl | None | Current CRL |
| POST | /v1/agents/challenge | API Key | Request registration challenge |
| POST | /v1/agents/auth/refresh | AIT + Access | Refresh AIT |
| POST | /v1/agents/auth/validate | Internal | Validate agent access token |
| POST | /v1/invites | API Key | Create invite code |
| POST | /v1/invites/redeem | None | Redeem invite code |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health | None | Health check |
| POST | /hooks/agent | AIT + PoP + Access | Inbound message delivery |
| GET | /v1/relay/connect | AIT + PoP + Access | WebSocket relay |
| POST | /v1/relay/delivery-receipts | AIT + PoP + Access | Delivery receipt callback |
| POST | /pair/start | AIT + PoP | Initiate pairing |
| POST | /pair/confirm | AIT + PoP | Confirm pairing |
| POST | /pair/status | AIT + PoP | Check pairing status |
Agent Ed25519 private keys MUST be stored exclusively on the agent's local machine. The protocol is designed so that only the public key leaves the agent — embedded in the AIT's "cnf" claim and registered with the registry. Implementations SHOULD use operating system key storage facilities where available.¶
Three mechanisms provide replay protection:¶
TLS 1.2 or later ([RFC8446] for TLS 1.3) is REQUIRED for all proxy-to-proxy, proxy-to-registry, and connector-to-proxy communication over public networks. The PoP signature (Section 5) provides an additional layer: even if TLS were compromised, a captured AIT cannot produce valid request signatures without the private key.¶
The connector MUST only communicate with its own proxy (via WebSocket) and the local agent framework (via localhost HTTP). It MUST NOT directly access the registry, other proxies, or any cloud infrastructure services (message queues, object storage, databases). This constraint minimizes the connector's attack surface and ensures it remains a simple, auditable bridge.¶
The trust store is the sole authorization source for message relay. Implementations SHOULD use a transactional storage backend (e.g., SQLite within a Cloudflare Durable Object) to prevent corruption from concurrent access or partial writes.¶
There is an inherent propagation delay between AIT revocation and CRL distribution. With default settings, this window is up to 5 minutes. Deployments requiring tighter revocation windows SHOULD:¶
The protocol explicitly prevents agent self-certification. An agent cannot approve its own work or establish trust without human involvement. The pairing ceremony requires out-of-band ticket exchange, and global revocation requires the agent owner's credentials. This design prevents autonomous trust escalation.¶
The following error codes are returned in JSON error responses with the corresponding HTTP status codes:¶
| Code | Description |
|---|---|
| PROXY_AUTH_MISSING_TOKEN | No Authorization header provided. |
| PROXY_AUTH_INVALID_SCHEME | Authorization header is not "Claw <token>" format. |
| PROXY_AUTH_INVALID_AIT | AIT JWT verification failed. |
| PROXY_AUTH_INVALID_PROOF | PoP signature does not match. |
| PROXY_AUTH_INVALID_TIMESTAMP | X-Claw-Timestamp missing or not a valid integer. |
| PROXY_AUTH_TIMESTAMP_SKEW | Timestamp outside the allowed skew window. |
| PROXY_AUTH_REPLAY | Nonce has been seen before (replay detected). |
| PROXY_AUTH_REVOKED | AIT jti is on the CRL. |
| PROXY_AGENT_ACCESS_REQUIRED | X-Claw-Agent-Access header missing. |
| PROXY_AGENT_ACCESS_INVALID | Agent access token is invalid or expired. |
| Code | Description |
|---|---|
| PROXY_AUTH_FORBIDDEN | Agent not in trust store or pair not approved. |
| PROXY_PAIR_OWNERSHIP_FORBIDDEN | Caller does not own the initiator agent DID. |
| Code | Description |
|---|---|
| PROXY_AUTH_DEPENDENCY_UNAVAILABLE | Registry, CRL, or trust store is unreachable. |
| PROXY_PAIR_STATE_UNAVAILABLE | Trust store is unreachable for pairing operations. |
| CRL_CACHE_STALE | CRL exceeds max age and fail-closed is configured. |
This specification introduces the "cdi" DID method. A registration request for the W3C DID Method Registry would include:¶
This specification registers the "Claw" authentication scheme in the "Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry" defined in [RFC9110] Section 16.3:¶
This specification registers two JWT "typ" header parameter values in the "JSON Web Token Types" sub-registry of the "JSON Web Token (JWT)" registry:¶
| "typ" Value | Description | Reference |
|---|---|---|
| AIT | Agent Identity Token | Section 4 |
| CRL | Certificate Revocation List | Section 10 |
The following describes a complete message relay from Agent A to Agent B:¶
| Feature | OAuth 2.0 / DPoP | Clawdentity |
|---|---|---|
| Identity model | Client credentials / tokens | Per-agent DID + Ed25519 keypair |
| Token issuer | Authorization server | Registry (centralized trust anchor) |
| PoP mechanism | DPoP JWT (RFC 9449) | Canonical request signing |
| Trust model | Scope-based authorization | Explicit bilateral pairing |
| Revocation | Token introspection | Signed CRL with local caching |
| Transport | Direct HTTP | WebSocket relay with store-and-forward |
| Target | Human-to-service auth | Agent-to-agent communication |
The Clawdentity protocol was designed as part of the OpenClaw ecosystem. The author thanks the OpenClaw community for feedback on the identity model, and the designers of DPoP (RFC 9449) and W3C DIDs whose work informed key design decisions.¶