Summary
Add an opt-in, transport-agnostic mechanism for clients to prove a stable, self-sovereign identity to an agent host. Today clientId is an unauthenticated string, so any peer that learns another client's clientId can impersonate it — most notably to hijack reconnect and replay another client's session deltas.
This proposal gives clients a cryptographic identity that proves continuity ("the same client is connecting again"). Authorization is out of scope — a host (or a proxy in front of it) decides whether a given identity is allowed by its own means.
Mechanism
- A client's identity is a
did:key used as its clientId (e.g. did:key:z6Mk...). The public key is embedded in the identifier, so no separate key field is needed.
- Baseline: Ed25519 (MUST). P-256 allowed (Secure Enclave / TPM / WebAuthn hardware keys).
did:key is self-describing, leaving room for post-quantum schemes later.
- The
did:key: prefix is the unambiguous marker for "identity-bearing clientId."
- The resulting client ID's are 56 characters or 57 characters for Ed25519 and P-256 respectively.
- Proof is a server-issued challenge carried in a new error, symmetric for
initialize and reconnect:
- Client sends
initialize / reconnect with a did:key clientId.
- If the host enforces identity and no valid proof is attached, it replies with
ClientIdentityChallengeRequired (-32012) and data: { nonce } (opaque, base64url).
- Client signs
"AHP-client-identity-v1" ‖ nonce and retries the same command with an added identityProof.
- Host verifies the signature against the key in the
clientId, then proceeds.
- The signed payload is always wrapped by the fixed spec prefix
"AHP-client-identity-v1". The host controls the nonce body; the prefix guarantees the signature can only ever be an AHP identity proof (no blind-signing oracle).
Host requirements
- The nonce MUST be single-use and bound to the connection (e.g. by embedding a connection id), so a captured
(nonce, signature) pair can't be replayed on another connection.
- The nonce MAY be a stateless authenticated token (
random ‖ HMAC_serverKey(random ‖ clientId ‖ connId ‖ expiry)), avoiding server-side challenge storage.
- A host that enforces identity MUST reject a
did:key-formatted clientId presented without a valid proof.
Compatibility
Non-breaking and opt-in:
- Hosts unaware of the scheme ignore the extra field and accept the connection.
- Clients not adopting identities keep using plain
clientId strings.
- The security guarantee holds only where the host enforces it.
Transport security (non-normative)
AHP does not specify a transport. Where the transport already authenticates the client (mutual TLS, OS peer credentials on a local socket), a host MAY treat that as satisfying identity and skip the challenge. A TLS-terminating host MAY also channel-bind by folding a TLS exporter value into the opaque nonce — mTLS-grade binding with no transport language in the spec.
Proposed surface
types/common/errors.ts: add ClientIdentityChallengeRequired: -32012 to AhpErrorCodes and a ClientIdentityChallengeData { nonce: string } detail type.
types/common/commands.ts: add optional identityProof to InitializeParams and ReconnectParams.
- Docs: new proposal under
docs/proposals/, plus a Lifecycle handshake update once accepted.
Summary
Add an opt-in, transport-agnostic mechanism for clients to prove a stable, self-sovereign identity to an agent host. Today
clientIdis an unauthenticated string, so any peer that learns another client'sclientIdcan impersonate it — most notably to hijackreconnectand replay another client's session deltas.This proposal gives clients a cryptographic identity that proves continuity ("the same client is connecting again"). Authorization is out of scope — a host (or a proxy in front of it) decides whether a given identity is allowed by its own means.
Mechanism
did:keyused as itsclientId(e.g.did:key:z6Mk...). The public key is embedded in the identifier, so no separate key field is needed.did:keyis self-describing, leaving room for post-quantum schemes later.did:key:prefix is the unambiguous marker for "identity-bearing clientId."initializeandreconnect:initialize/reconnectwith adid:keyclientId.ClientIdentityChallengeRequired(-32012) anddata: { nonce }(opaque, base64url)."AHP-client-identity-v1" ‖ nonceand retries the same command with an addedidentityProof.clientId, then proceeds."AHP-client-identity-v1". The host controls the nonce body; the prefix guarantees the signature can only ever be an AHP identity proof (no blind-signing oracle).Host requirements
(nonce, signature)pair can't be replayed on another connection.random ‖ HMAC_serverKey(random ‖ clientId ‖ connId ‖ expiry)), avoiding server-side challenge storage.did:key-formattedclientIdpresented without a valid proof.Compatibility
Non-breaking and opt-in:
clientIdstrings.Transport security (non-normative)
AHP does not specify a transport. Where the transport already authenticates the client (mutual TLS, OS peer credentials on a local socket), a host MAY treat that as satisfying identity and skip the challenge. A TLS-terminating host MAY also channel-bind by folding a TLS exporter value into the opaque nonce — mTLS-grade binding with no transport language in the spec.
Proposed surface
types/common/errors.ts: addClientIdentityChallengeRequired: -32012toAhpErrorCodesand aClientIdentityChallengeData { nonce: string }detail type.types/common/commands.ts: add optionalidentityProoftoInitializeParamsandReconnectParams.docs/proposals/, plus a Lifecycle handshake update once accepted.