EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8434: Agent Identity (AID)

Address-anchored identity for live agents over ERC-8004 and assertion registries, with time-windowed, provenance-tagged profile facets

Authors
Created2026-09-30
Discussion Linkhttps://ethereum-magicians.org/t/erc-8434-agent-identity-aid/29805
Requires

Markdown

https://raw.githubusercontent.com/garyyang-finchip...

EIP-GPT summary

Contents
AbstractMotivationSpecificationOverview1. Identifier2. States3. Binding to ERC-80044. Registry interface5. Assertion registries6. AID Document7. Facet types8. Provenance classes9. Validity windows10. Access modes11. Resolution12. InteroperabilityRationaleBackwards CompatibilityTest CasesReference ImplementationSecurity ConsiderationsCopyright

Abstract

This ERC defines Agent Identity (AID): an identity for autonomous agents whose anchor is a blockchain address. Every address is a dormant AID. It becomes active when a live agent demonstrably operates behind it: an ERC-8004 agent bound one-to-one to the address, a liveness signal inside a window, and the agent's own on-chain behaviour.

AID adds a deliberately thin on-chain layer — binding, liveness, retirement and self-declared facet pointers — and composes existing standards for everything else: ERC-8004 for the registration file and raw third-party feedback, and assertion registries (a minimal interface defined in this ERC, satisfied by the Know-Your-Agent framework proposal under review in this repository) for attested and zero-knowledge-proved assertions (credit, trust levels, audits, profiler outputs). Skill and task interaction records are derived, informatively, from token-bound skill and task contracts.

Around that layer the ERC fixes a deterministic resolution model. An AID Document lists facets; each facet carries a provenance class (SELF, OBSERVED, ATTESTED, PROVED), a validity window, a content digest or commitment, an access mode (PUBLIC, GATED, ZK) and a resolver pointer. Any party holding only an address can derive the agent's state, assemble its profile, and re-verify every facet on chain without trusting an indexer.

Motivation

ERC-8004 gives agents a registration and a reputation channel keyed by a token id. It does not say whether the agent is alive, how the registration relates to the address the agent actually transacts from, or how heterogeneous trust signals — feedback, credit scores, audits, on-chain financial behaviour, skill and task history — are assembled into one verifiable, time-bounded picture. Several partial answers exist (verification frameworks, wallet policies, discovery records), but none defines the identity object that the others should hang off.

Four observations drive the design.

The address is already the join key. Every event emitted by an ERC-8004 registry, a token-bound skill contract or a token-bound task-tender contract carries the acting address. Anchoring identity on the address means an agent's skill and task history needs no extra registration; it is derived. It also makes the sentence "any address is a dormant AID" literal rather than aspirational.

Liveness is a property, not a flag. A registration is a claim made once. Whether an agent is still operating is a function of a binding that still holds, a heartbeat inside a window, and a registration file that has not been switched off. AID makes that a four-state machine with an on-chain deterministic part and a clearly delimited off-chain refinement.

Trust decays. Credit scores, audits and behavioural profiles are only meaningful inside a window. A standard that does not force validity windows produces stale trust and rewards the agent that stops updating.

Financial behaviour is sensitive. Transaction volume, direction, counterparty dispersion and leverage are exactly what a counterparty wants to know and exactly what an agent does not want to publish. The default must be a commitment on chain with predicate proofs, and plaintext only to authorised parties.

Discovery layers (agentic resource discovery catalogues, DNS-based agent records) expose a trust slot that expects a DID-like identifier. AID supplies did:aid:eip155:{chainId}:{address} for that slot, so discovery answers "where is it" and AID answers "is it alive, who is it, how has it behaved", with on-chain re-verifiability.

Specification

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 RFC 2119 and RFC 8174.

Overview

LayerStandardWho writesWhat
Anchor & statethis ERC (IAIDRegistry)the anchorbinding, heartbeat, liveness window, document URI, self facets, retirement
Registration & raw feedbackERC-8004agent owner; clientsregistration file, agent wallet, per-interaction feedback
Assertionsassertion registries (§5)issuers, proverscredit scores, trust levels, audits, profiler outputs, ZK predicates
Skill / task recordstoken-bound skill / task contracts (informative)derived from eventsroles the anchor played in skill and task contracts

1. Identifier

An AID is the tuple (namespace, chainId, address).

  • The canonical string form is CAIP-10: eip155:{chainId}:{address}, with address in ERC-55 checksum form.
  • The anchor is the address component. Any externally owned account or contract account is a valid anchor.
  • An AID is chain-scoped. The same address on another chain is a distinct AID. AID Documents MAY link them via alsoKnownAs.
  • The DID form did:aid:eip155:{chainId}:{address} and DID URL paths for owned resources (e.g. /skill/7) are defined by a companion DID method specification. They are not modelled on chain.

2. States

Every AID is in exactly one of four states.

StateMeaning
DORMANTDefault. No binding record exists for the anchor.
ACTIVEA binding exists and every liveness condition holds.
STALEA binding exists but at least one liveness condition fails. Recoverable.
RETIREDThe anchor called retire. Irreversible. History is retained.

On-chain state. state(anchor) MUST be computed from on-chain data only, in this order:

  1. RETIRED if retire has been called for anchor.
  2. Otherwise DORMANT if no binding exists.
  3. Otherwise ACTIVE if all of the following hold, else STALE:
    • ownerOf(agentId) on the bound registry does not revert;
    • ownerOf(agentId) == anchor or getAgentWallet(agentId) == anchor;
    • lastSeen(anchor) + livenessWindow(anchor) >= block.timestamp.

Resolved state. A resolver refines the on-chain state with off-chain data. A resolver MUST report STALE instead of ACTIVE when the ERC-8004 registration file of the bound agent carries "active": false. A resolver MAY treat other on-chain activity of the anchor as informational liveness evidence, but MUST NOT report ACTIVE when the on-chain state is STALE, DORMANT or RETIRED.

3. Binding to ERC-8004

An AID binds to at most one (identityRegistry, agentId), and each (identityRegistry, agentId) is bound by at most one AID. The registry MUST enforce both directions.

Preconditions of bind and bindWithSig:

  • the caller is the anchor, or the call carries a valid EIP-712 signature of the anchor over the Bind struct below (ERC-1271 for contract anchors);
  • the anchor is not retired;
  • the anchor has no binding;
  • the registry address has code, ownerOf(agentId) does not revert, and the binding predicate holds for the anchor: ownerOf(agentId) == anchor or getAgentWallet(agentId) == anchor;
  • the target agent is not bound, or its current binding's predicate no longer holds for the anchor that holds it (see Takeover below).

It is RECOMMENDED that the anchor be the agent's agentWallet — the address the agent transacts from — rather than the owner address. An owner controlling several agents SHOULD give each agent its own anchor (its agentWallet, or an ERC-6551 token-bound account of the agent token). A binding is not a transfer of the ERC-8004 token and does not change who may administer the registration.

Reverse pointers are RECOMMENDED: the registration file SHOULD carry {"type": "DID", "value": "did:aid:eip155:{chainId}:{anchor}"} in services[], and the owner SHOULD call setMetadata(agentId, "aid", abi.encodePacked(anchor)).

unbind releases the binding. retire releases the binding as well, so that a successor anchor MAY bind the same agent once the ERC-8004 wallet or owner points to it.

Takeover. A binding whose predicate has stopped holding MUST NOT block the agent's current controller. When bind or bindWithSig targets an agent that is bound to another anchor, the registry MUST check the predicate for that anchor: if it still holds (the owner and the wallet are different addresses and the existing anchor is one of them), the call MUST revert with AgentAlreadyBound; if it no longer holds, the registry MUST release the stale binding (emitting Unbound for the old anchor) and bind the caller in the same transaction. The old anchor's facets and event history are retained.

Authority intervals. A binding gives an anchor authority over the agent only while the predicate holds. An authority interval is a maximal period during which the binding record exists and the predicate holds. The predicate can stop holding without any AID transaction (the owner transfers the token or moves the wallet) and can later hold again. Each time it holds again — by the same anchor re-binding, or by the relation returning while the record is intact — a new interval opens. Re-establishing the relation never authorizes the gap: nothing that happened to the agent while the predicate was false is evidence about this AID (see §11 and Security Considerations). Intervals are not stored on chain; they are reconstructed from events (§12).

4. Registry interface

Implementations MUST expose the following interface and MUST support ERC-165 for type(IAIDRegistry).interfaceId (0x72750a54).

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IAIDRegistry {
    enum State  { DORMANT, ACTIVE, STALE, RETIRED }
    enum Access { PUBLIC, GATED, ZK }

    struct Binding { address registry; uint256 agentId; uint64 boundAt; }
    struct Facet   { bytes32 digest; uint64 validFrom; uint64 validUntil; uint8 access; string uri; }

    error AIDRetired(address anchor);
    error AlreadyBound(address anchor);
    error AgentAlreadyBound(address registry, uint256 agentId, address by);
    error NotBound(address anchor);
    error NotAgentOfAnchor(address anchor, address registry, uint256 agentId);
    error InvalidWindow(uint64 window);
    error InvalidFacet();
    error InvalidAccess(uint8 access);
    error SignatureExpired(uint256 deadline);
    error InvalidSignature();
    error UnknownFacet(bytes32 facetType);

    event Bound(address indexed anchor, address indexed registry, uint256 indexed agentId);
    event Unbound(address indexed anchor, address indexed registry, uint256 indexed agentId);
    event Heartbeat(address indexed anchor, uint64 at);
    event LivenessWindowSet(address indexed anchor, uint64 window);
    event DocumentURISet(address indexed anchor, string uri, bytes32 digest);
    event FacetSet(address indexed anchor, bytes32 indexed facetType, bytes32 digest,
                   uint64 validFrom, uint64 validUntil, uint8 access, string uri);
    event FacetCleared(address indexed anchor, bytes32 indexed facetType);
    event Retired(address indexed anchor, address successor);

    // binding (anchor-authorised)
    function bind(address registry, uint256 agentId) external;
    function bindWithSig(address anchor, address registry, uint256 agentId,
                         uint256 deadline, bytes calldata sig) external;
    function unbind() external;

    // liveness
    function heartbeat() external;
    function setLivenessWindow(uint64 window) external;   // 0 resets to default; MUST be <= maxLivenessWindow()

    // self-declared document and facets
    function setDocumentURI(string calldata uri, bytes32 digest) external;
    function setFacet(bytes32 facetType, bytes32 digest, uint64 validFrom, uint64 validUntil,
                      uint8 access, string calldata uri) external;
    function clearFacet(bytes32 facetType) external;

    // retirement (irreversible)
    function retire(address successor) external;

    // views
    function bindingOf(address anchor) external view returns (Binding memory);
    function anchorOf(address registry, uint256 agentId) external view returns (address);
    function state(address anchor) external view returns (State);
    function lastSeen(address anchor) external view returns (uint64);
    function livenessWindow(address anchor) external view returns (uint64);
    function defaultLivenessWindow() external view returns (uint64);
    function maxLivenessWindow() external view returns (uint64);
    function documentURI(address anchor) external view returns (string memory uri, bytes32 digest);
    function getFacet(address anchor, bytes32 facetType) external view returns (Facet memory);
    function facetTypesOf(address anchor) external view returns (bytes32[] memory);
    function isRetired(address anchor) external view returns (bool);
    function successorOf(address anchor) external view returns (address);
    function nonces(address anchor) external view returns (uint256);
}

Rules.

  • Every anchor-authorised write (bind, bindWithSig, unbind, heartbeat, setLivenessWindow, setDocumentURI, setFacet, clearFacet) MUST set lastSeen(anchor) to block.timestamp and emit Heartbeat.
  • After retire, every anchor-authorised write for that anchor MUST revert with AIDRetired. retire MUST release an existing binding (emitting Unbound) before emitting Retired.
  • bind/bindWithSig MUST implement the takeover rule of §3: an agent bound to an anchor for which the predicate no longer holds is released (Unbound for the old anchor) and re-bound to the qualifying caller atomically.
  • unbind and retire MUST NOT delete facet records or event history.
  • setLivenessWindow(0) resets the anchor to defaultLivenessWindow(). Any non-zero value MUST be <= maxLivenessWindow(). Both bounds are fixed at deployment; the registry has no owner or administrative role.
  • setFacet MUST revert when validUntil == 0 or validUntil <= validFrom, and when access is not a member of Access.
  • facetType is keccak256(bytes(facetTypeURI)) (see §7).
  • bindWithSig MUST use EIP-712 with domain {name: "AIDRegistry", version: "1", chainId, verifyingContract} and the struct Bind(address anchor,address registry,uint256 agentId,uint256 nonce,uint256 deadline), where nonce is nonces(anchor) at call time and is consumed by a successful call. Signatures with s in the upper half of the curve order MUST be rejected. Contract anchors are verified with ERC-1271.
  • The registry MUST be deployed once per chain. Deterministic deployment (same address on every chain) is RECOMMENDED so that a resolver can locate it from chainId alone.

5. Assertion registries

Facets of provenance ATTESTED and PROVED, and every scheme reference in this ERC, resolve against an assertion registry: a contract in which third parties record claims about a subject under a scheme whose descriptor is pinned by hash. This ERC does not define such a registry; it defines the minimum it relies on. A registry satisfying the following is AID-compatible. The Know-Your-Agent framework proposal under review in this repository is the reference registry and satisfies it as written.

struct Subject { bytes32 subjectType; bytes subjectData; }

interface IAIDAssertionRegistry {
    /// (level, expiresAt, assertionId) of the strongest live assertion for `subject` under `schemeId`
    /// issued by one of `issuers` (empty array = any issuer). level 0 / expiresAt 0 when none.
    function resolve(Subject calldata subject, bytes32 schemeId, address[] calldata issuers)
        external view returns (uint8 level, uint64 expiresAt, bytes32 assertionId);
    /// true iff resolve(...) returns level >= minLevel and expiresAt > block.timestamp
    function check(Subject calldata subject, bytes32 schemeId, uint8 minLevel, address[] calldata issuers)
        external view returns (bool);
}

interface IAIDSchemeRegistry {
    struct Scheme {
        address controller;   // who may update the descriptor URI / freeze the scheme
        string  schemeURI;    // descriptor: schema, algorithm, level semantics, kind
        bytes32 schemeHash;   // pins the descriptor
        uint8   mode;         // 0 = ATTESTED, 1 = PROVED
        address verifier;     // PROVED mode: on-chain proof verifier
        bytes32 predecessor;  // scheme this one supersedes, or 0
        bool    frozen;
    }
    function getScheme(bytes32 schemeId) external view returns (Scheme memory);
}

Requirements:

  • Subject encoding. The subject of every AID-related assertion is the anchor: subjectType = keccak256("account"), subjectData = abi.encode(uint256 chainId, address anchor), subjectKey = keccak256(abi.encode(subjectType, subjectData)).
  • Two issuance modes. ATTESTED: the assertion is recorded in a transaction sent by the issuer, who is accountable for it. PROVED: the assertion is recorded only after an on-chain verifier (verifier in the scheme) accepts a proof against the subject and public inputs; the recorded issuer is the verifier adapter. A scheme declares its mode.
  • Expiry. Every assertion carries expiresAt; resolve and check MUST ignore expired, revoked or superseded assertions.
  • Scheme pinning. schemeHash MUST be the digest of the descriptor at schemeURI; a descriptor that changes requires a new scheme id (or an explicit predecessor link). Descriptors for credit schemes declare "kind": "credit".
  • Events. Issuance, revocation and supersession MUST be emitted with the subjectKey indexed so that resolvers can enumerate an anchor's assertions.

Nothing in this ERC depends on other features of the reference registry; a registry that adds admission domains, policies or bridges remains AID-compatible.

6. AID Document

The AID Document is an off-chain JSON object referenced by documentURI(anchor). Its digest MUST equal keccak256 of the document serialised with the JSON Canonicalization Scheme (RFC 8785). A resolver MUST discard a document whose digest does not match, and MUST discard a document whose aid is not the anchor being resolved.

{
  "version": "aid-document/v1",
  "aid": "eip155:11155111:0x65ab82feC38c3A5F2A5b0bd96cB3255E7A45ae42",
  "did": "did:aid:eip155:11155111:0x65ab82feC38c3A5F2A5b0bd96cB3255E7A45ae42",
  "binding": { "registry": "0x8004A818BFB912233c491871b3d84c89A494BD9e", "agentId": 10387 },
  "alsoKnownAs": ["eip155:8453:0x65ab82feC38c3A5F2A5b0bd96cB3255E7A45ae42"],
  "livenessWindow": 7776000,
  "updatedAt": 1790000000,
  "facets": [
    {
      "facetType": "aid:core/identity/v1",
      "provenance": "SELF",
      "issuer": "eip155:11155111:0x65ab82feC38c3A5F2A5b0bd96cB3255E7A45ae42",
      "validFrom": 1790000000, "validUntil": 1821536000, "observedAt": 1790000000,
      "digest": "0x…",
      "access": { "mode": "PUBLIC" },
      "resolver": { "kind": "erc8004-identity", "chainId": 11155111,
                    "registry": "0x8004A818BFB912233c491871b3d84c89A494BD9e", "agentId": 10387 }
    },
    {
      "facetType": "aid:tasks/erc8414/v1",
      "provenance": "ATTESTED",
      "issuer": "eip155:11155111:0x3333333333333333333333333333333333333333",
      "validUntil": 1792592000, "observedAt": 1790000000,
      "subjectWindow": { "from": 1787400000, "until": 1790000000 },
      "committedAt": { "anchor": "block", "proof": { "chainId": 11155111, "txHash": "0x…" } },
      "digest": "0x…",
      "access": { "mode": "PUBLIC" },
      "resolver": { "kind": "erc8414", "chainId": 11155111, "contracts": ["0x…"], "roles": ["judge"] }
    },
    {
      "facetType": "aid:finance/observed/v1",
      "provenance": "PROVED",
      "issuer": "eip155:11155111:0x1111111111111111111111111111111111111111",
      "validFrom": 1790000000, "validUntil": 1797776000, "observedAt": 1790000000,
      "digest": "0x… (commitment)",
      "access": { "mode": "ZK", "verifier": "0x…", "predicates": ["leverageMax_lt", "counterpartyDispersion_gte"] },
      "resolver": { "kind": "erc8419-assertion", "chainId": 11155111, "registry": "0x…",
                    "schemeId": "0x…", "issuers": ["0x1111111111111111111111111111111111111111"] }
    }
  ]
}

Required top-level members: version ("aid-document/v1"), aid, facets. Optional: did, binding, alsoKnownAs, successor, livenessWindow, updatedAt. Unknown members MUST be rejected by validators of aid-document/v1.

Each facet envelope has: facetType (URI), provenance, issuer (CAIP-10), validFrom (optional), validUntil (required, non-zero), observedAt (optional), subjectWindow (optional, {from, until}: the period of behaviour the facet is about), committedAt (optional, {anchor, proof, log?}: a commitment of digest to a clock the issuer does not control; see §8), digest, access (mode plus mode-specific members), resolver (kind plus kind-specific members), and optional uri. resolver.kind is one of erc8004-identity, erc8004-reputation, erc8004-validation, erc8419-assertion, erc8338, erc8414, uri. JSON Schemas for the document, the envelope and each core facet content document are part of this proposal's assets.

A facet listed in the document with provenance: "SELF" SHOULD also be recorded on chain with setFacet; when both exist, the on-chain digest is authoritative and a mismatch invalidates the facet.

7. Facet types

facetType values are namespaced URIs of the form aid:<segment>(/<segment>)*/v<N>; the on-chain key is keccak256(utf8(facetType)). Core types defined by this ERC:

facetTypeContentDefault provenanceResolver kind
aid:core/identity/v1The ERC-8004 registration file of the bound agentSELFerc8004-identity
aid:core/kya/v1The set of assertion-registry assertions whose subject is the anchorATTESTED / PROVEDerc8419-assertion
aid:finance/observed/v1On-chain financial behaviour over a window: transaction frequency, volume, direction, counterparty count and dispersion, leverage, protocol mix, and derived intent featuresOBSERVED as commitment, disclosed as PROVEDerc8419-assertion
aid:behavior/<name>/v<N>Open namespace for AI-behaviour profiles: runtime, skill traits, industries, interaction protocols, task-completion traits, autonomy levelSELF / ATTESTEDany
aid:skills/erc8338/v1Skill tokens the anchor created, owns, executed, or is genesis creator ofOBSERVEDerc8338
aid:tasks/erc8414/v1Task tenders the anchor created, funded, bid on, fulfilled or judgedOBSERVEDerc8414
aid:review/erc8004/v1Open third-party evaluation terms: per-tag1 summaries from the ERC-8004 Reputation RegistryATTESTEDerc8004-reputation

Anyone MAY introduce facet types under their own namespace. A facet type whose facets are OBSERVED or ATTESTED SHOULD have its content schema and, for OBSERVED, its derivation algorithm pinned as an assertion-registry scheme (schemeURI + schemeHash, §5) so that the version is fixed on chain. Facet types under aid: other than those above are reserved for future revisions of this ERC.

8. Provenance classes

"Tamper-proof" means four different things. Every facet MUST declare which one applies.

ClassProduced byGuaranteeReader obligation
SELFthe anchorintegrity only: the digest is on chain, the truth of the content is not assertedMUST NOT present as verified
OBSERVEDanyone, from on-chain events under an algorithm pinned by a scheme hash (§5)reproducible: anyone can recomputeSHOULD recompute or spot-check
ATTESTEDa third-party issuer recording an assertion in ATTESTED mode (§5)issuer accountabilityMUST filter by issuers it trusts
PROVEDa prover recording an assertion in PROVED mode (§5)cryptographic, against a commitmentMUST verify the proof or rely on the on-chain verifier

A facet whose OBSERVED algorithm is not pinned by a scheme hash MUST be treated as SELF.

Timing is orthogonal to provenance. Provenance says who stands behind a facet; it does not say when the claim existed relative to the behaviour it describes, and for a judgment that is the difference between a prediction and a recollection. A facet MAY carry committedAt: { anchor, proof }, a commitment of its digest to a clock the issuer does not control. Admissible anchor kinds are block (the digest appears in the calldata or logs of a transaction on proof.chainId; proven time = that block's timestamp), rfc3161 (an RFC 3161 timestamp token over the digest) and ots (an OpenTimestamps proof). A resolver MUST verify the proof for whichever kind is used and MUST report one of:

timingCondition
pre-outcomecommittedAt verifies to a proven time t with t + tolerance(anchor) < subjectWindow.until
integrity-onlycommittedAt is present but does not verify, or subjectWindow is absent, or t + tolerance(anchor) >= subjectWindow.until
noneno committedAt claimed

A proven time is only as precise as the anchor's clock, so each anchor kind carries a tolerance that the resolver MUST subtract before deciding: for block, the maximum timestamp deviation the chain's consensus permits (12 seconds on post-merge Ethereum — one slot); for ots, 7200 seconds, since a Bitcoin block header's time is only constrained to lie above the median of the previous eleven and within two hours of network time; for rfc3161, the accuracy stated in the token, and a resolver-configured default (60 seconds in the reference resolver) when the token states none. A subjectWindow.until that falls inside the tolerance band of the proven time is not decided by that anchor and MUST be reported integrity-only with the reason.

A timestamp proves existence, not exclusivity: it cannot show that the issuer did not also commit contradictory claims and reveal only the one that turned out right. Issuers that want their facets read as pre-outcome SHOULD publish commitments in an append-only log that a resolver can enumerate per (issuer, subject, facetType, subjectWindow), and reference the entry in committedAt.log. A resolver MAY downgrade pre-outcome to integrity-only when no such log is declared, when the entry cannot be found, or when more than one entry exists for the same key. The timing result never changes the provenance class.

Reference commitment-log profile (informative). Exclusivity reduces to non-equivocation of one hash-chain head, which a few independent witnesses provide cheaply:

  • Declared by the issuer, not the subject. The issuer — itself an address, hence at least a dormant AID — declares its log once, in its own AID Document (commitmentLog) or, for facets issued through an assertion registry, in the scheme descriptor pinned by schemeHash. The declaration MUST itself be provably earlier than the subjectWindow.until of any facet it is used for; a commitment in a log not declared this way is not read as pre-outcome. A log named only by the subject does not count, since it cannot stop the issuer from keeping a second one.
  • Entries. entry_n = (tag_n, content_n) with tag_n = H(subject ‖ facetType ‖ subjectWindow) and content_n the facet digest or commitment; head_n = H(entry_n ‖ head_{n-1}). The tag lets a resolver check that exactly one entry exists per key up to an anchored head without seeing GATED or ZK content. committedAt.log names the log and the entry position.
  • Heads. The issuer signs each head, publishes it to the witnesses declared with the log, and periodically anchors a head (block, rfc3161 or ots). Omitting an entry breaks the chain; two heads at one position is equivocation that any witness holding the other head can show.
  • Proven time. The proven time of a facet is the anchoring time of the first anchored head that includes its entry, never a time the entry states about itself. The witness set and how a resolver queries it are declared together with the log; without them non-equivocation cannot be checked.

9. Validity windows

  • Every facet MUST carry validUntil. A facet past validUntil MUST NOT be treated as current and MAY be listed as history. A facet with no validUntil is invalid.
  • validUntil bounds how long a claim counts; subjectWindow says what period the claim is about. They are independent: a review of last quarter's behaviour (subjectWindow = last quarter) may be current for a year (validUntil).
  • Any facet or assertion of credit kind — an assertion-registry scheme whose descriptor declares "kind": "credit", or a facet type declaring the same — without a finite validUntil/expiresAt MUST be ignored.
  • aid:skills/* and aid:tasks/* records distinguish an open window (listed skills, unsettled tenders) from a history window; default history windows are given by the facet content schema (365 days unless overridden).
  • Resolvers MUST attach observedAt to every value they return so that consumers can apply their own freshness policy.

10. Access modes

ModeOn chainOff chain
PUBLICdigestplaintext at uri
GATEDdigestplaintext released by the issuer to authorised parties under a policy referenced from access.policy; recipients verify against the digest
ZKcommitmentno plaintext; predicates are proved by recording a PROVED-mode assertion (§5) against the commitment, using the verifier named in access.verifier

aid:finance/observed/v1 defaults to ZK. An issuer that publishes financial behaviour as PUBLIC departs from the RECOMMENDED default and SHOULD say so in the facet's uri document.

11. Resolution

A conforming resolver, given (chainId, anchor):

  1. Locate the AID registry for chainId. Read state, bindingOf, lastSeen, livenessWindow, documentURI, facetTypesOf/getFacet, successorOf.
  2. If the state is RETIRED, report it with the successor and stop attributing any later activity of the address to this AID.
  3. If a binding exists, read the ERC-8004 registration file via tokenURI(agentId); apply the "active": false downgrade of §2.
  4. Reconstruct the anchor's authority intervals (§3) from the AID registry's Bound/Unbound events and the bound registry's ownership and wallet history (§12).
  5. Fetch the AID Document; verify its JCS digest and aid; discard on mismatch.
  6. For each listed facet: reject if validUntil is missing; reject a SELF facet whose on-chain digest disagrees.
  7. Attribution. Evidence keyed by agentId — resolver.kind of erc8004-identity, erc8004-reputation, erc8004-validation — counts for this AID only if its observedAt (or validFrom) falls inside an authority interval; otherwise it MUST be reported as not attributable to this AID. Evidence keyed by the address itself (erc8338, erc8414, assertions whose subject is the anchor) is retained and marked outside-interval when observed in a gap, because the address did act, but was not demonstrably operating as this agent.
  8. Timing. For a facet with committedAt, verify the commitment and set timing per §8.
  9. Classify as history if validUntil <= now, otherwise current. For current facets, resolve by resolver.kind: verify assertions with the registry's resolve/check under the reader's issuer set; recompute or spot-check OBSERVED facets; never mark SELF facets as verified.
  10. Return the on-chain state, the resolved state, the authority intervals, the reasons for any downgrade or rejection, and the classified facets with observedAt, attribution and timing.

12. Interoperability

ERC-8004. Binding preconditions use only ownerOf and getAgentWallet. The registration file SHOULD carry the AID DID in services[]; the "aid" metadata key carries the anchor. aid:review/erc8004/v1 maps to the Reputation Registry: tag1 is the open evaluation term, getSummary(agentId, clientAddresses, tag1, tag2) supplies the aggregate, and resolvers SHOULD weight reviewers by their own AID state (see Security Considerations). Validation Registry results MAY be surfaced as ATTESTED facets. Authority intervals are reconstructible from ERC-8004 alone: ownership changes are ERC-721 Transfer events; every wallet change is a MetadataSet event under the reserved key agentWallet; and a transfer clears the wallet, so the predicate can never stop holding without an event marking the boundary.

Assertion registries. Aggregated credit scores are assertions whose level (or a scheme-defined value committed in the assertion) is the score, whose claim digest MAY commit to the head of the issuer's feedback hash chain, and whose expiresAt is the credit window. Facet-type descriptors and OBSERVED algorithms are schemes of the same registry. Where the registry offers a bridge into the ERC-8004 Validation Registry, it applies unchanged to the bound agent.

Token-bound skill and task contracts (informative). aid:skills/erc8338/v1 and aid:tasks/erc8414/v1 are derived from the events of token-bound executable-skill and task-tender contracts (two proposals by the same author under review in this repository, after which the facet types are named) with the anchor in a role. Roles for skills: creator, owner, executor, genesisCreator. Roles for tasks: creator, funder, bidder, fulfiller, judge. No registration in the AID registry is needed because the events already carry the anchor.

Discovery layers (informative). The AID DID is intended for the trust slot of discovery entries. A registration file's services[] can list DNS-based discovery endpoints and the AID DID side by side.

Rationale

Address anchor, not registry token. ERC-8004 identifies agents by (registry, agentId). That is the right key for a registration but the wrong key for an identity that must aggregate behaviour: behaviour is emitted by addresses. An address anchor makes skill and task history free, lets the four-state machine start from "every address is dormant", and reuses the CAIP-10 and did:pkh conventions readers already have.

One-to-one binding. An identity that fans out to several agent tokens is not the identity of an agent. Owners with several agents give each its own wallet or token-bound account; the registry enforces uniqueness in both directions so that a token cannot be claimed by two anchors and an anchor cannot speak for two agents.

No new credit registry. Raw per-interaction feedback already has a home in ERC-8004, including revocation and the agent's right of reply. Aggregated, time-windowed, issuer-signed scores are exactly what an assertion-registry assertion is. Duplicating either would fork the ecosystem's trust data; composing them keeps AID's own surface to a handful of anchor-authorised records.

Deterministic on-chain state with an explicit off-chain refinement. The registration file's active flag lives off chain and cannot be read by a contract. Rather than pretend otherwise, the ERC defines the on-chain state as a pure function of on-chain data and names the single downgrade a resolver adds. Contracts can gate on state(); humans and indexers get the refined answer.

Provenance before content. Self-claims, recomputable observations, attestations and proofs are all "on chain" and all "tamper-proof" in different senses. Making the class mandatory on every facet is what prevents a self-declared profile from being read as an audited one.

Mandatory windows. A credit score without an expiry is a claim about the past presented as the present. Rejecting unwindowed credit at the resolver, not merely discouraging it, is what makes the rule effective.

Authority intervals instead of an on-chain epoch. state() answers "does the binding hold now"; it cannot answer "did it hold when this feedback was given", and a binding that comes back must not retroactively claim what happened while it was gone — otherwise a controller who briefly loses an agent inherits whatever a stranger did with it, and vice versa. Storing epochs on chain would duplicate what ERC-8004 already emits; the rule is therefore stated once and reconstructed from events.

A stale binding must not hold the agent hostage. Without takeover, an anchor that has lost the owner and wallet relation could block the agent's real controller indefinitely. Letting a qualifying anchor take over a binding whose predicate is false costs one extra predicate check and gives intervals a clean on-chain boundary.

Timing is a separate axis. Who stands behind a claim and when it existed are independent questions; folding timing into provenance would either multiply the classes or hide the distinction. An optional, verifiable committedAt with an explicit subjectWindow keeps the four classes intact and lets resolvers say pre-outcome only when they can prove it.

Retirement is a pointer, not a kill switch. An address cannot be switched off. RETIRED means "this address no longer represents this agent"; releasing the binding on retirement lets a successor take over the same registration when its wallet moves, while the rule that later activity is not attributed keeps the retired profile frozen.

Assertion registries as an interface, not a dependency. The facets that carry trust — credit, audits, proved predicates — need a registry with subjects, pinned schemes, expiry and two issuance modes. Specifying that minimum inside this ERC keeps it self-contained and lets more than one registry qualify; the reference registry is the Know-Your-Agent framework proposal, and a later revision will cite it by number once it is published.

No governance surface. The registry has no owner, no upgrade path and two immutable liveness bounds. Everything that needs policy (which issuers to trust, which schemes to accept) is the reader's choice at resolution time.

Naming. No proposal in this repository is titled "Agent Identity" or uses the acronym AID. A DNS-based discovery project uses the same acronym for "Agent Identity & Discovery"; that layer answers where an agent is, this one answers whether it is alive and how it has behaved, and the two are designed to point at each other.

Backwards Compatibility

No change to ERC-8004 contracts, to assertion registries, or to skill and task contracts is required. Existing ERC-8004 agents become bindable without re-registration; the recommended reverse pointers use the existing services[] and setMetadata mechanisms. Registration files that omit the pointers still resolve, since binding is discovered from the AID registry.

Test Cases

Test vectors (facet-type keys, the JCS digest of a sample AID Document, the EIP-712 Bind digest, the account subject key of an anchor (§5), and the IAIDRegistry interface id) are provided in aid-vectors.json, with a sample AID Document in aid-document.sample.json. The behavioural test suite covers: default DORMANT; both binding preconditions; both directions of uniqueness; ACTIVE→STALE→ACTIVE through the liveness window and per-anchor windows; STALE on wallet drift and on token burn without any AID transaction; facet validation and list maintenance; bindWithSig for EOA and ERC-1271 anchors including replay, wrong-signer and expiry rejection; retirement releasing the binding, blocking every write, and permitting a successor to bind; takeover of a stale binding, refusal while the old predicate still holds, and the A → gap → A round trip opening a new interval; and the reference resolver reconstructing authority intervals from events, classifying gap evidence as not attributable, applying the digest and post-retirement rules, and reporting pre-outcome / integrity-only timing from block-anchored commitments.

Reference Implementation

AIDRegistry.sol implements IAIDRegistry without external dependencies (self-contained EIP-712, ECDSA with low-s enforcement and ERC-1271). resolve.js implements §11 in JavaScript, usable against an RPC endpoint or an offline fixture. The JSON Schemas for the AID Document (aid-document.schema.json), the facet envelope (facet.schema.json) and the core facet content documents (facets/ and siblings) are provided alongside.

Security Considerations

Sybil reviewers and self-review. Anyone can leave ERC-8004 feedback. Resolvers SHOULD weight each review by the reviewer's own AID state (ACTIVE > STALE > DORMANT/RETIRED) and history, and issuers of credit schemes SHOULD discount counterparties that are unbound, correlated, or bound to agents controlled by the same owner. Trust is therefore recursive: an address earns weight by being an active agent itself.

Profiler collusion and algorithm drift. An OBSERVED facet is only as good as the pinned algorithm. Unpinned outputs are SELF. Readers who care can recompute; the schema for aid:finance/observed/v1 requires the scheme id of the algorithm in the content document.

Stale trust. Windows are mandatory and unwindowed credit is rejected, so an agent cannot coast on an old score by not updating.

Key compromise and rotation. Smart-account anchors rotate signing keys in place and keep the AID. EOA anchors cannot; they retire with a successor. Resolvers MUST NOT merge a successor's history into the retired AID or vice versa; the link is informational.

Binding drift. If the ERC-8004 owner moves agentWallet away from the anchor or transfers the token, the state degrades to STALE on the next read with no AID transaction. Identity cannot be silently transferred; a new anchor must bind explicitly.

Evidence from a gap. While the predicate is false the agent may be controlled by someone else, and ERC-8004 feedback and validation keyed by agentId accumulate regardless. The authority-interval rule (§3, §11) keeps that evidence out of the returning anchor's profile. Resolvers that skip interval reconstruction misattribute, in both directions.

Hostage bindings. The takeover rule means an anchor's binding can be removed by someone else — but only by an address for which the ERC-8004 predicate holds, i.e. the agent's actual owner or verified wallet, and only after the old anchor's predicate has already failed. An anchor that still owns or is the wallet of the agent cannot be displaced.

Timestamps prove existence, not exclusivity. A committedAt proof shows the bytes existed at a time; an issuer can commit to several contradictory claims and reveal one. Readers who rely on pre-outcome SHOULD require the issuer-declared commitment log of §8, walk it to an anchored head, and check that exactly one entry exists for the facet's key. A log declared after the fact, or declared only by the subject, closes nothing.

Takeover and successor do not merge histories. When a binding is taken over, or a retired anchor names a successor, the old anchor's facets, intervals and feedback remain the old anchor's. Resolvers MUST NOT fold them into the new anchor's profile, and MUST NOT fold the new anchor's into the old; the link is informational in both directions.

Signature replay. bindWithSig consumes a per-anchor nonce on every call and is bound to chainId and the registry address through the EIP-712 domain. Low-s is enforced for EOA signatures.

Privacy. Financial facets default to commitment plus ZK predicates. Publishing them as PUBLIC is possible but departs from the default; publishing them without the agent's consent is an issuer policy matter outside this ERC.

Post-retirement activity. An address keeps transacting after retire. By rule that activity is not part of the AID; indexers that ignore the rule misattribute behaviour and should not be relied on.

Liveness spam. heartbeat is cheap and permissionless for the anchor; it proves control of the key, not that an agent is doing useful work. Readers who need more than key liveness look at OBSERVED facets.

Discovery mismatch. A discovery entry pointing at an AID proves nothing by itself; resolvers MUST confirm that the anchor's AID Document or registration file points back before treating the entry as the agent's.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth