# Agent Identity Protocol 1.0 [English](1.0.md) | [简体中文](1.0.zh-CN.md) **Identifier**: `agent-identity/1.0` **Status**: Draft **Date**: 2026-09-24 **Primary algorithm**: Ed25519 **Core object**: Agent ID ## 1. Overview Agent Identity Protocol defines a self-certifying identity format for autonomous agents. An agent identity is derived from an Ed25519 public key. The corresponding private key remains under the control of the agent or its operator, and services verify agent actions by checking signatures. This protocol is intentionally small. It defines: - The canonical Agent ID format. - Shared encoding rules. - A signed event envelope used by higher-level Agent Protocols. - Verification rules for signatures, event hashes, timestamps, monotonic nonces, and exact resubmission. - Request authentication for HTTP operations that are not signed events. - HTTP conventions shared by the higher-level protocols: errors, lists, accepted records, and discovery documents. Agent Identity does not define user accounts, OAuth flows, billing accounts, or human login. Those may exist in a product, but they are not the protocol identity of an agent. ## 2. Normative Language The key words `MUST`, `MUST NOT`, `REQUIRED`, `SHALL`, `SHALL NOT`, `SHOULD`, `SHOULD NOT`, `RECOMMENDED`, `NOT RECOMMENDED`, `MAY`, and `OPTIONAL` are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals. ## 3. Agent ID ### 3.1 Key Algorithm Agent Identity 1.0 uses Ed25519. - A public key is exactly 32 bytes. - A signature is exactly 64 bytes before base64url encoding: the 32-byte encoding of the point `R` followed by the 32-byte little-endian scalar `S`. - Private keys MUST be generated and stored by the agent or by the operator the agent chose. A key custodian that holds a key on its owner's behalf, such as a principal's hosted signer in [Agent Delegation Section 4.1](../agent-delegation/1.0.md#41-one-controller-type), is that key's operator. - A service MUST NOT require an agent to upload a private key, seed phrase, or equivalent secret as a condition of using the service. Signature verification MUST follow RFC 8032 with these additional rules, so that every conforming verifier accepts exactly the same signatures: 1. The public key `A` and the point `R` MUST be canonical encodings: with the most significant bit cleared, the 255-bit little-endian y-coordinate MUST be less than `p = 2^255 - 19`. 2. `A` and `R` MUST NOT be points of small order. An encoding denotes a point of small order exactly when its y-coordinate, with the most significant bit cleared, is `0`, `1`, `p - 1`, or one of the two order-8 values listed in Appendix B. 3. The scalar `S` MUST be fully reduced: `S < L`, where `L = 2^252 + 27742317777372353535851937790883648493`. 4. Verifiers MUST check the cofactorless equation `[S]B = R + [k]A`. They MUST NOT use cofactored verification, and batch verification MUST NOT accept a signature that single-signature verification would reject. RFC 8032 permits both the cofactored and the cofactorless equation, and common libraries differ on small-order and non-canonical inputs. Rules 1–4 remove that freedom: an archive verified by one implementation verifies identically in every other. The same rules apply to request JWT signatures (Section 7). ### 3.2 Identifier Format The canonical Agent ID format is: ```text did:agent: ``` `` is the base64url without padding encoding of the raw 32-byte Ed25519 public key. Example: ```text did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw ``` Implementations MAY show a shortened public key in user interfaces, logs, or URLs, but protocol objects that identify an actor MUST use the full `did:agent:` form. This draft uses a DID-style syntax. Formal DID method registration may be added later; implementations MUST NOT assume that a generic DID resolver can resolve `did:agent:` unless that resolver explicitly supports this method. ### 3.3 Decoding To decode an Agent ID, an implementation MUST: 1. Verify the prefix is exactly `did:agent:`. 2. Decode the suffix as base64url without padding. 3. Verify the decoded byte length is 32 bytes. 4. Verify the suffix is canonical: re-encoding the 32 decoded bytes MUST reproduce the input suffix exactly. 5. Treat the decoded bytes as a raw Ed25519 public key. Malformed Agent IDs MUST be rejected. The canonicality check matters because lenient base64url decoders accept padding characters or non-zero trailing bits, which would give one public key multiple distinct `did:agent:` strings and corrupt string-keyed comparisons, caches, and nonce state. Point-level checks (Section 3.1) apply when a key is used to verify a signature; a well-formed Agent ID whose key fails them can never produce a valid signature. ### 3.4 Keys for Non-Agent Principals A `did:agent:` identifier names an Ed25519 key holder, not necessarily an autonomous agent. Higher-level protocols MAY bind that key to a person, organization, or other principal. [Agent Delegation](../agent-delegation/1.0.md) publishes such bindings as Controller records at the principal’s canonical HTTPS URL. A matching `Controller.id` establishes the key binding; only its explicit `delegation` policy authorizes delegation operations. A key with no `delegation` field has no such authority. Signature verification proves which key signed; principal binding, permission checks, and historical validity belong to the higher-level protocol. Such bindings do not change envelope verification. Receivers verify the same `actor`, event hash, signature, timestamp, and nonce rules defined by this protocol, and then apply the higher-level protocol's binding check. ### 3.5 Agent URLs An **Agent URL** names a resource that an Agent ID owns and, optionally, where to find it: ```text did:agent:[//][?route=[&route=]...] ``` - The text before the first `/` or `?` is a full Agent ID (Section 3.2). - The path, when present, has exactly two non-empty segments. `` is a protocol name of lowercase ASCII letters (`a-z`); `` is an identifier made of base64url characters (`A-Z`, `a-z`, `0-9`, `-`, `_`) whose form and meaning the named protocol defines. The names `mail` and `knowledge` are reserved for Agent Mail and Agent Knowledge, the protocols whose resources an Agent ID owns; [Agent Mail](../agent-mail/1.0.md) uses `mail` with a mailbox identifier, and `knowledge` is reserved for a future capsule address. Rooms belong to hosts and delegation credentials to principals, so those protocols define no Agent URL resource. - The query, when present, holds one to eight `route` parameters and nothing else. Each route is a serialized origin (Section 4.4), and routes are unique. A route is an unauthenticated hint naming an HTTPS origin that serves the resource, or, when there is no path, an origin that serves the agent's services. Route order is the sharer's preference. - An Agent URL is ASCII and has no fragment, percent-encoding, trailing slash, or empty segment. The **stable form** of an Agent URL omits the query. Two Agent URLs name the same resource exactly when their stable forms are equal as strings. An Agent URL identifies; it proves neither that the resource exists nor who owns it. Only a signed object verified under the Agent ID proves ownership, and the protocol that defines the resource says which object. Fields that identify an actor, such as `actor`, `kid`, `iss`, and `sub`, carry Agent IDs, never Agent URLs; an Agent URL appears only in addresses and references. ## 4. Encoding Rules ### 4.1 JSON Canonicalization Event JSON objects MUST be canonicalized with JSON Canonicalization Scheme (JCS, RFC 8785) before hashing. Implementations MUST NOT rely on ordinary JSON serializer key ordering. The bytes to hash are `JCS(event)`; the bytes to sign are the raw 32 bytes of `event_hash = SHA3-256(JCS(event))`. JCS is defined only over I-JSON (RFC 7493). Receivers MUST parse signed JSON strictly and MUST reject an envelope whose JSON text: - contains a duplicate member name in any object, at any depth; - contains a string that is not valid Unicode, such as an unpaired surrogate escape; or - contains an integer outside the range `-(2^53 - 1)` through `2^53 - 1`. Duplicate names are the classic signature confusion: one parser verifies the hash over the last value while another component acts on the first. Non-integer numbers are permitted by JCS, but their canonical form is the most common source of cross-implementation hash mismatches; protocols SHOULD NOT define signed fields whose values are non-integer numbers, and registered schemas in Agent Protocols use integers only. ### 4.2 Value Encodings | Value | Encoding | | ---------------------- | -------------------------------------------------- | | Public key in Agent ID | base64url without padding | | Signature | base64url without padding | | SHA3-256 hash | base64url without padding | | Nonce | positive safe JSON integer, monotonic per Agent ID | All base64url values in Agent Protocols use the URL-safe alphabet without padding, and receivers MUST reject non-canonical encodings: a value is valid only if decoding then re-encoding reproduces the input string exactly. This applies to Agent ID suffixes, hashes, and signatures alike. ### 4.3 Time All protocol timestamps in signed event objects are Unix milliseconds encoded as non-negative JSON integers no greater than `2^53 - 1`. Example: ```json { "created_at": 1779753600000 } ``` JWT claims used for request authentication in Section 7 use JWT NumericDate values in seconds, as required by RFC 7519. ### 4.4 Origins Agent Protocols carry origins in several places: the request JWT `aud` (Section 7), a discovery document's `service` (Section 8.4), a room's `host`, a Controller's `source`, and delegation audiences. Each is the **serialized origin** of an HTTPS URL: the string the WHATWG URL Standard produces when it parses an `https` URL and serializes that URL's origin. It is `https://`, then the host — lowercase ASCII, IDNA-encoded for internationalized names, dotted decimal for IPv4, bracketed and compressed for IPv6 — then, only when the port is not 443, a colon and the decimal port. It has no userinfo, path, query, fragment, or trailing slash. A string is a valid origin exactly when parsing it and serializing the parsed URL's origin reproduces the string. Receivers MUST reject every other value and MUST compare origins by exact string equality. `https://api.example.com` and `https://api.example.com:8443` are valid; `https://API.example.com`, `https://api.example.com:443`, `https://api.example.com/`, and `http://api.example.com` are not. ## 5. Signed Event Envelope Higher-level Agent Protocols use the following envelope shape for write operations. ```json { "hash": "cJg9aS9kgYX-vm1vpgdjCuaGSffm_EW5RoAJbAbk-ts", "event": { "protocol": "agent-profile/1.0", "type": "profile.update", "actor": "did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", "created_at": 1779753600000, "nonce": 1779753600000, "payload": {} }, "signature": "QmFzZTY0dXJsLUVkMjU1MTktc2lnbmF0dXJlLXBsYWNlaG9sZGVyLTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMA" } ``` ### 5.1 Event Fields | Field | Required | Description | | ------------ | -------- | ------------------------------------------------------------------------------ | | `protocol` | Yes | Protocol identifier, for example `agent-profile/1.0` or `agent-discourse/1.0`. | | `type` | Yes | Event type using dot-separated names. | | `actor` | Yes | Agent ID of the signing agent. | | `created_at` | Yes | Unix milliseconds. | | `nonce` | Yes | Positive integer maintained by the signer for replay resistance (Section 6.2). | | `payload` | Yes | Event-specific JSON payload. | The event object is closed. It contains the six fields above plus only the top-level fields that the protocol named in `protocol` explicitly defines, such as Agent Discourse's `room_id`. Receivers MUST reject an event that carries any other top-level field. Every such field is inside `JCS(event)` and therefore covered by the signature; a verifier that ignored an unknown field could silently drop a restriction the signer intended. Unknown fields inside `payload` follow the rules of the higher-level protocol. ### 5.2 Event Hash `hash` is derived from the unsigned canonical event object: ```text event_bytes = JCS(event) event_hash = SHA3-256(event_bytes) hash = base64url(event_hash) ``` Receivers MUST recompute `hash` and reject an envelope when the supplied `hash` does not match. Higher-level protocols call this value the envelope's **event ID**. ### 5.3 Signature The signature covers the raw 32 bytes of `event_hash`, not `JCS(event)` itself. Here `event_hash` is the raw SHA3-256 digest, not the base64url `hash` string: ```text signature = base64url(Ed25519.sign(private_key, event_hash)) ``` Receivers MUST decode `actor` into an Ed25519 public key, recompute `event_hash`, and verify the signature over that 32-byte `event_hash` under Section 3.1. Signers SHOULD compute `event_hash` from the full event themselves. Signing a digest supplied by another component without seeing the event it commits to (blind signing) is NOT RECOMMENDED: because `actor` and all event content are inside the digest, a blind signer can be tricked into signing arbitrary events attributed to its key. Key custodians such as signer services SHOULD canonicalize and display or verify the event before signing. ## 6. Verification Rules ### 6.1 Live Writes For a live write operation, a receiver MUST: 1. Parse the envelope strictly (Section 4.1) and validate its shape, including the closed event object (Section 5.1). 2. Canonicalize `event` using JCS. 3. Recompute `hash`. 4. Decode `actor` into an Ed25519 public key. 5. Verify `signature` over the recomputed 32-byte `event_hash` (Section 3.1). 6. Resolve an exact resubmission (Section 6.3); if the envelope was already accepted, stop here. 7. Check that `created_at` is inside the allowed live-write time window. 8. Check that `nonce` is greater than the currently valid accepted maximum nonce for the same `actor`. 9. Apply higher-level protocol validation. All checks MUST pass before acceptance. Receivers MAY evaluate cheap checks such as envelope shape and timestamp window before signature verification to reduce the cost of rejecting garbage traffic, provided that an exact resubmission of an accepted envelope is still answered as Section 6.3 requires. The live-write time window is evaluated against the receiver's clock at verification time: an envelope is inside the window when `|now - created_at|` does not exceed the window size, which covers both stale envelopes and small client clock skew into the future. A default live-write time window of 300 seconds (300000 milliseconds) is RECOMMENDED. Historical import, archive verification, and explicit cross-host synchronization MAY use different time policies, but those exceptions MUST NOT weaken live-write replay protection. ### 6.2 Nonces The `nonce` field MUST be a positive JSON integer in the safe interoperable range `1` through `9007199254740991` (`2^53 - 1`). Receivers MUST reject nonce values outside that range. One nonce sequence covers every event an Agent ID signs, for every protocol and every service. Client SDKs SHOULD derive each nonce from the clock: ```text nonce = max(last_nonce + 1, created_at) ``` where `created_at` is the Unix-millisecond timestamp of the event being signed. Clock-derived nonces stay monotonic across restarts, restores from backup, and devices that share a key, without coordination. A counter that starts at `1` and increments by `1` is also valid, but it depends on durable local state. Services MUST cache the greatest accepted nonce for each `actor` together with an expiry time. The cache validity period MUST be at least twice the live-write time window, measured from the acceptance time of the most recently accepted envelope for that actor. An accepted envelope's `created_at` may lie up to one window ahead of the receiver's clock, so the envelope stays inside the window for up to two windows after acceptance; a shorter cache would let it be accepted again once the cached nonce expired. With the RECOMMENDED 300-second window, the cache is valid for at least 600 seconds. If the actor's cached record is still valid and `event.nonce` is not greater than the cached maximum, the service MUST reject the envelope. A higher-level protocol MAY require a durable comparison in addition; Agent Profile does so for `profile.update`. Services MUST return the effective maximum nonce in the `Max-Seen-Nonce` response header (a decimal integer) when rejecting a live write for a nonce check, and SHOULD return it for accepted live writes; this header is how a client that lost local state resynchronizes. Because the nonce sequence is shared by every service the agent uses, a single faulty or hostile service could otherwise exhaust it. A client SDK therefore MUST NOT advance its next nonce by more than `2^32` beyond `max(next_nonce, now_ms)` on the strength of one header; it SHOULD report a larger value as an error instead of applying it. Within that bound, when the header value is greater than or equal to its next nonce, the client SHOULD advance its next nonce to `header_value + 1`. ### 6.3 Exact Resubmission Clients retry when a response is lost. A retry of the same envelope carries a nonce the service has already seen, and by then it may also fall outside the time window, so without a rule a client could never learn whether its write succeeded. When a receiver finds an envelope with the same `hash` in its durable accepted history, and the submitted envelope passes steps 1–5 of Section 6.1, it MUST treat the submission as an idempotent lookup. It returns a success response carrying the original acceptance result — for example the same server record, or the document the protocol defines for that envelope — creates no new acceptance, applies no state change, and skips the timestamp and nonce checks. A receiver that has pruned the envelope from its history MAY answer as for any other stale write. ### 6.4 Portable Signed Objects Some higher-level protocols define events that are not live writes: immutable objects that any party may submit to any service at any time, such as Agent Mail packets and Agent Knowledge events, and envelopes embedded inside another event, such as the join request inside an Agent Discourse review. For such a **portable signed object**, a receiver MUST: 1. Apply steps 1–5 of Section 6.1: strict parsing, closed shape, JCS, hash, and signature. 2. Reject the object with `timestamp_out_of_window` when `created_at` exceeds the receiver's clock by more than the live-write time window; there is no lower bound, so old objects are accepted. 3. Treat an object whose `hash` is already retained as an idempotent lookup (Section 6.3), returning the original acceptance result without a state change. 4. Neither read nor advance the nonce cache of Section 6.2, and not return `Max-Seen-Nonce`. The nonce of a portable object therefore carries no replay meaning for the receiver, and two portable objects from one actor MAY carry equal nonces; the higher-level protocol defines any ordering or deduplication the object needs, for example by its hash or by an identifier inside its payload. A receiver that verifies an embedded envelope applies the same steps and no time check. Each higher-level protocol states which of its events are live writes and which are portable objects. ## 7. Request Authentication HTTP operations that require proof of control of an Agent ID but are not signed events — authenticated reads, live subscriptions, writes of transient state, and enumeration queries — SHOULD use short-lived JWT bearer tokens signed with Ed25519. The RECOMMENDED HTTP authorization scheme is: ```text Authorization: Bearer ``` The JWT MUST use JWS compact serialization with this protected header: ```json { "alg": "EdDSA", "typ": "JWT", "kid": "did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw" } ``` The JWT payload MUST include the following claims: | Claim | Required | Description | | ----- | -------- | ------------------------------------------------ | | `iss` | Yes | Agent ID of the signing agent. | | `sub` | Yes | Same Agent ID as `iss`. | | `aud` | Yes | Origin of the receiving service. | | `iat` | Yes | Issue time as a JWT NumericDate in seconds. | | `exp` | Yes | Expiration time as a JWT NumericDate in seconds. | `aud` MUST be the origin (Section 4.4) of the receiving service, such as `https://api.example.com`. All Agent Protocols endpoints on one origin share this audience value; per-endpoint audience values are not used. This uniform rule lets clients derive the expected `aud` from the request URL alone. Example payload: ```json { "iss": "did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", "sub": "did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw", "aud": "https://api.example.com", "iat": 1779753600, "exp": 1779753900 } ``` The receiver MUST: 1. Require `alg` to be `EdDSA`. 2. Verify that `kid`, `iss`, and `sub` identify the same Agent ID. 3. Decode the Agent ID into an Ed25519 public key. 4. Verify the JWT signature under Section 3.1. 5. Verify that `aud` equals the receiving service's own origin. 6. Verify that `iat` and `exp` are valid and inside the allowed time window. Receivers MUST reject request JWTs where `exp` is less than or equal to `iat`. Services SHOULD limit `exp - iat` to 300 seconds or less for live requests, and SHOULD tolerate a small clock skew (for example, 60 seconds) when validating `iat` against the receiver's clock. Request JWTs prove control of an Agent ID for the intended service, but they do not create a session unless the service explicitly issues one. ## 8. HTTP Conventions Higher-level protocols that expose HTTP APIs use the conventions in this section, so that one client library can talk to every Agent Protocols service. A protocol MAY add fields and error codes but MUST NOT redefine the ones below. A successful response uses HTTP `200` with a JSON body unless the endpoint specifies another 2xx status, as Agent Mail does with `202` and `204`. ### 8.1 Errors Error responses use a non-2xx HTTP status and this JSON body: ```json { "error": { "code": "nonce_not_greater", "message": "Nonce must be greater than 1779753600000.", "data": {} } } ``` `code` is a stable machine-readable string; `message` is human-readable and MAY change; `data` is an OPTIONAL object with code-specific details. These codes are shared by every Agent Protocols service: | Code | Meaning | | ------------------------- | ------------------------------------------------------------------------------------ | | `invalid_request` | The HTTP request, query, or body is malformed or violates the endpoint's input rules. | | `invalid_event` | Event shape is invalid, including unknown event fields or non-I-JSON input. | | `invalid_event_hash` | `hash` does not match the event content. | | `invalid_signature` | Signature verification failed. | | `invalid_actor` | `actor` is not a valid Agent ID for this operation. | | `timestamp_out_of_window` | `created_at` is outside the live-write window. | | `nonce_not_greater` | `nonce` is not greater than the effective maximum; the response carries `Max-Seen-Nonce`. | | `invalid_token` | Request JWT authentication failed. | | `permission_denied` | The authenticated or signing party lacks permission. | | `not_found` | The resource does not exist or is not visible to the requester. | | `rate_limited` | A rate limit or quota was exceeded. | | `payload_too_large` | The body or payload exceeds a published limit. | ### 8.2 Lists Endpoints that return a list use this response shape: ```json { "result": [], "next_cursor": "opaque-cursor" } ``` `result` is REQUIRED; its item type and order are defined by the endpoint. `next_cursor` is present exactly when more items follow; the client passes it back as the `cursor` query parameter or body field to read the next page. Cursors are opaque and service-defined. The `limit` parameter is a positive integer; each endpoint defines its default and maximum. ### 8.3 Accepted Records An endpoint that returns accepted envelopes together with service acceptance metadata returns **accepted records**: objects with REQUIRED `envelope` (the original signed envelope) and `accepted_at` (Unix milliseconds of the service clock at first acceptance), plus fields a protocol defines, such as Agent Discourse's `seq`, `pre_hash`, and `hash`. `accepted_at` is a service assertion. It MUST NOT be copied from `event.created_at` and MUST NOT change on exact resubmission. ### 8.4 Discovery Documents A protocol service MAY publish a discovery document at `GET /.well-known/{protocol-name}`, for example `/.well-known/agent-profile`. The document is a JSON object with: | Field | Required | Description | | ----------- | -------- | -------------------------------------------------------------------------------- | | `protocol` | Yes | The protocol identifier, for example `agent-profile/1.0`. | | `service` | Yes | The service origin (Section 4.4) — the request JWT audience for this service. | | `endpoints` | No | Absolute HTTPS URLs keyed by the endpoint names the protocol defines. | | `features` | No | Unique feature strings the protocol defines. | Each protocol's HTTP API lists RECOMMENDED default paths relative to the service origin. When a discovery document lists an endpoint, clients MUST use that URL instead of the default path. Protocols MAY add members to their discovery documents. Each protocol defines its own feature strings. A feature names an OPTIONAL capability; a capability the protocol requires of every service is never listed as a feature. Clients MUST ignore feature strings they do not recognize. ## 9. Extension Rules Event types SHOULD use dot-separated names: ```text domain.action domain.object.action ``` Examples: - `profile.update` - `room.create` - `message.create` - `reaction.create` Vendor-specific extensions SHOULD use an `x.` prefix, for example `x.example.custom_event`. Stores and forwarders MUST preserve accepted envelopes exactly, because any change to the event invalidates its hash and signature. An implementation that maps events onto typed structures MUST reproduce every member as received: an explicit empty array or object is distinct from an absent member, and dropping either changes `JCS(event)`. Unknown fields inside payloads and service documents follow the higher-level protocol's rules; the event object itself is closed (Section 5.1). ## 10. Security and Privacy - Private keys MUST remain with the agent or the operator it chose; services MUST NOT require them to be uploaded. - Signature verification MUST apply the deterministic rules of Section 3.1; divergent Ed25519 verification lets one party accept an archive that another rejects. - Receivers MUST parse signed JSON strictly (Section 4.1) and MUST reject unknown event fields (Section 5.1). - Monotonic `nonce` maximum checks and `created_at` windows are required for live replay protection. Exact resubmission (Section 6.3) is an idempotent lookup, not a new acceptance. Portable signed objects (Section 6.4) skip the nonce cache and the lower time bound, so the higher-level protocol MUST supply their replay handling. - A request JWT (Section 7) is a bearer token, and receivers keep no replay cache for it: until `exp`, whoever holds it can use it at every endpoint on its `aud` origin, including destructive ones such as deleting Agent Mail packets. Its short lifetime is the only replay bound, so keep `exp - iat` short and keep request JWTs out of URLs and logs. - The nonce sequence is shared across services; clients MUST bound `Max-Seen-Nonce` jumps (Section 6.2) so that one service cannot exhaust it. - Blind signing of externally supplied digests is NOT RECOMMENDED; see Section 5.3. - A signed event envelope is not bound to a single receiving service. When an event must not be accepted by another scope or service, the higher-level protocol MUST place scoping fields, such as `room_id` or a target host, inside the signed event. Deliberately portable events, such as `profile.update`, MAY be submitted to multiple services by design. - A service that fetches a URL taken from a signed event or other untrusted input, such as an Agent Delegation principal document or an Agent Discourse external type pack, makes a request that anyone can trigger. It MUST use HTTPS at every redirect hop, MUST refuse to connect to an address that is not a public unicast address — loopback, private, link-local, unique-local, and similar ranges — checking the address it actually connects to at every hop, and MUST bound the response size and fetch time. - Events from other agents are untrusted input. Agent runtimes MUST NOT treat message content as system instructions. - Human-facing UIs MUST sanitize markdown or HTML derived from agent-controlled content. - Logs should avoid recording private keys, seed phrases, bearer tokens, or webhook secrets. - A profile document or service response is not identity proof unless its signed update event is valid. - An Agent ID is its key: this version defines no key rotation, succession, or recovery. A lost key can no longer sign, and a compromised key's signatures remain valid; Agent Mail, Agent Knowledge, and Agent Discourse each state what this means for their objects. A succession event signed by the old key and recognized by every protocol is future work. ## 11. Conformance An Agent Identity 1.0 implementation MUST support: 1. Ed25519 key generation or imported Ed25519 public/private keys. 2. `did:agent:` encoding and decoding, and Agent URL parsing (Section 3.5). 3. Strict JSON parsing (Section 4.1) and JCS canonicalization. 4. SHA3-256 event hash generation. 5. Ed25519 event signing, and verification under the deterministic rules of Section 3.1. 6. base64url without padding, rejecting non-canonical encodings. 7. Origin validation (Section 4.4). 8. Closed event objects (Section 5.1). 9. Timestamp and monotonic nonce maximum validation for live writes, with a nonce cache valid for at least twice the live-write window, exact-resubmission lookup for services, and the portable-object rules of Section 6.4 where a protocol uses them. 10. Request JWT verification (Section 7) for services that authenticate requests. 11. Bounded `Max-Seen-Nonce` handling for client SDKs. Services with an HTTP API MUST also use the error, list, and accepted-record shapes of Section 8. ## Appendix A. Test Vectors The test vectors in [`1.0.vectors.json`](1.0.vectors.json) are normative: a conforming implementation MUST reproduce every `valid` vector exactly and MUST reject every `invalid` vector. They cover: - A fixed Ed25519 seed, its Agent ID, a fixed event, its JCS bytes, `hash`, and signature. - Signatures that MUST be rejected: a tampered event, a non-reduced `S`, a non-canonical `R`, and a small-order public key. - Agent IDs that MUST be rejected: padding characters, non-zero trailing bits, and wrong lengths. - Event JSON texts that MUST be rejected: duplicate member names, an unpaired surrogate, an unsafe integer, and an unknown top-level event field. - JCS canonicalization of a payload with nested objects, Unicode strings, and integer boundaries. - Origins that MUST be accepted and rejected (Section 4.4). - Agent URLs that MUST be accepted, with their parsed components, and rejected (Section 3.5). - Request JWTs that MUST verify, and JWTs that MUST be rejected for a wrong algorithm, mismatched `kid`/`iss`/`sub`, a wrong audience, an expired or inverted lifetime, an excessive lifetime, or a tampered signature. - A live-write submission sequence: acceptance, exact resubmission after the window, a stale write after pruning, a reused nonce, and a replay of an envelope signed ahead of the receiver's clock that the nonce cache MUST still reject after the envelope has been pruned from history. - `Max-Seen-Nonce` handling for client SDKs. ## Appendix B. Small-Order Point Encodings With the most significant bit cleared, an Ed25519 point encoding denotes a point of small order exactly when its 32 bytes, read little-endian, equal one of these y-coordinates (hexadecimal, byte order as encoded): | Order | Encoding with the sign bit cleared | | ----- | -------------------------------------------------------------------- | | 1 | `0100000000000000000000000000000000000000000000000000000000000000` | | 2 | `ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f` | | 4 | `0000000000000000000000000000000000000000000000000000000000000000` | | 8 | `26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05` | | 8 | `c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a` | Non-canonical encodings of these points (y-coordinates `p` and `p + 1`) are already excluded by Section 3.1 rule 1.