--- eip: 8239 title: Agent Skill Registry description: A canonical onchain registry for agent skills with usage and installation attestations. author: Craig Branscom (@craigbranscom) discussions-to: https://ethereum-magicians.org/t/erc-8239-agent-skill-registry/28335 status: Draft type: Standards Track category: ERC created: 2026-04-21 requires: 721, 8004, 8183 --- ## Abstract This proposal introduces a set of minimal contracts for canonicalizing agent skills onchain and recording an agent's relationship to those skills. A `SkillRegistry` mints a unique [ERC-721](./eip-721.md) token per skill, owned by the skill's author and pointing at an offchain manifest. A `SkillAttestation` contract exposes two event modes under one address: usage attestations that an [ERC-8004](./eip-8004.md) agent used a skill (with three verification paths — self, evaluator, hook) and installation declarations that an agent currently has a skill installed (mutable via paired install / uninstall emits). An optional `SkillAttestationHook` implements the [ERC-8183](./eip-8183.md) hook interface so that skill declarations can be associated with a completed job's lifecycle. Skill-related reputation piggybacks on ERC-8004's existing Reputation Registry via a documented tag convention, avoiding a parallel reputation contract. ## Motivation ERC-8004 standardizes agent Identity, Reputation, and Validation registries but defines no canonical identifier for the **skills** an agent offers. ERC-8183 standardizes agent-based commerce (job escrow, evaluation) but similarly leaves skill identity and capability declaration out of scope. Today, an agent describes its skills only inside offchain registration files. This is sufficient for self-description but makes three use cases awkward: 1. **Cross-agent discovery by skill.** Without a shared onchain identifier, indexers cannot reliably answer "which agents offer skill X" except by parsing each agent's offchain manifest and manually parsing the data set. 2. **Skill-scoped reputation.** ERC-8004 feedback is keyed by `agentId`. To aggregate per-skill reputation ("how reliable is skill X across agents"), indexers need a stable skill identifier that feedback can reference. 3. **Evidence linking a job to the skills used.** ERC-8183 jobs contain an unstructured `description`; there is no machine-readable way to say "agent X completed job Y using skill Z," which blocks outcome-based reputation per skill. This proposal addresses (1)-(3) with the smallest possible onchain surface: one ERC-721 registry for skill identity, two events-only attestation layers for the usage and installation relationships, and a convention on how to tag ERC-8004 feedback so that skill-level reputation becomes a pure offchain aggregation task over existing 8004 data. ## 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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). ### Overview A conforming deployment MUST provide: - A `SkillRegistry` contract implementing `ISkillRegistry` and ERC-721. - A `SkillAttestation` contract implementing `ISkillAttestation`. This contract exposes both usage attestations (`SkillUsed`) and installation declarations (`SkillInstalled` / `SkillUninstalled`) under one address. A deployment MAY additionally provide a `SkillAttestationHook` implementing `IACPHook` (as defined by [ERC-8183](./eip-8183.md)) for automatic attestation from ERC-8183 job completions. `SkillAttestation` MUST reference: - An ERC-8004 Identity Registry (ERC-721) used to verify agent ownership. - A `SkillRegistry` used to verify skill existence. These references MUST be set at construction and SHOULD be immutable. ### SkillRegistry ```solidity interface ISkillRegistry { event SkillRegistered(uint256 indexed skillId, address indexed registrant); event SkillURIUpdated(uint256 indexed skillId, string newURI, bytes32 newHash); error NotSkillOwner(uint256 skillId, address caller); function registerSkill(string calldata skillURI, bytes32 manifestHash) external returns (uint256 skillId); function setTokenURI(uint256 skillId, string calldata newURI, bytes32 newHash) external; } ``` Implementations MUST also expose a public mapping `manifestHashes(uint256 skillId) returns (bytes32)` (auto-generated when the storage variable is declared `public`) so consumers can read the current commitment without an interface-level declaration. The `SkillRegistry` MUST conform to ERC-721. Each skill is represented by a token whose `tokenId` is assigned by the implementation and whose `tokenURI` is the `skillURI` provided at registration. The minted token MUST be owned by `msg.sender` at the time of registration. `manifestHash` MUST be `keccak256` of the manifest's exact byte representation as published. Authors are responsible for ensuring canonical byte-stability of their manifest (whitespace, key order) across publications. The contract treats `manifestHash` as opaque and does not attempt to derive or validate it; the field is a commitment that downstream consumers verify against the bytes they fetched. `registerSkill` MUST emit `SkillRegistered(skillId, msg.sender)` followed by `SkillURIUpdated(skillId, skillURI, manifestHash)`. `SkillURIUpdated` is the single canonical source for the current URI and hash of a skill; it is emitted both on initial registration (updating from the zero default) and on every subsequent update. This specification does not require skill URIs or manifest hashes to be unique. Two authors MAY independently register skills pointing at the same URI or committing to the same hash; indexers resolve canonical authorship and deduplication offchain. The `skillURI` and `manifestHash` for a given `skillId` MAY be updated together by the current owner via `setTokenURI(skillId, newURI, newHash)`, which MUST revert with `NotSkillOwner` if the caller is not the current owner of `skillId`, and MUST emit `SkillURIUpdated(skillId, newURI, newHash)`. Implementations MUST NOT expose any URI-update path that does not also update `manifestHash` atomically; permitting URI changes without hash changes would defeat the integrity commitment. Implementations SHOULD NOT expose a burn or token-destruction function for skill tokens. Soft retirement of a skill is expressed by setting `status` to `"deprecated"` in the manifest file. A hard burn would invalidate downstream existence checks in `SkillAttestation` (both usage and installation modes) and prevent agents from cleanly retracting prior installations. ### Skill Manifest File The `skillURI` MUST resolve to a JSON skill manifest file. It MAY use any URI scheme such as `ipfs://` (e.g. `ipfs://cid`), `https://` (e.g. `https://example.com/skill.json`), or a `data:application/json;base64,...` URI to embed the manifest directly in the `tokenURI` string rather than by reference. The manifest file MUST include the following top-level fields for ERC-721 app compatibility: - `type`: a URL identifying the manifest file version. The example below uses a fragment of this EIP's URL. - `name`: a short, human-readable skill name. - `description`: a freeform natural-language description of the skill. - `image`: a URL to an icon or representative image for the skill. The manifest file SHOULD additionally include: - `version`: the skill's own version string (SemVer RECOMMENDED). - `license`: an SPDX license identifier or URL to a custom license. A skill is typically code, prompt content, or both, and consumers need to know the redistribution and use terms. - `homepage`: a URL to the skill's documentation, landing page, or canonical project site. - `targets`: an array describing the host environments for which the skill is materialized (a Claude Code SKILL.md, a Cursor rule, an MCP server, etc.). At least one entry is RECOMMENDED. Each entry contains: - `kind` (REQUIRED): the target category. Values are convention- based, not enforced; common values include `"claude-code-skill"`, `"cursor-rule"`, `"cline-rule"`, `"windsurf-rule"`, `"mcp-server"`, `"prompt-bundle"`, `"cli"`, `"agents-md"`. Clients SHOULD reject targets whose `kind` they do not implement, with a clear error rather than a panic. - `uri` (REQUIRED for target kinds where a client materializes runtime content): a URL or content address from which the target's bytes can be obtained (e.g. `ipfs://cid`, `https://...`, `data:text/markdown;base64,...`). Any URI scheme is permitted; integrity is enforced separately via `integrity`. Future revisions MAY add `git+https://...?ref=` and similar schemes; clients MAY accept additional schemes consistent with this rule. - `integrity` (REQUIRED whenever `uri` is set): an OCI-style `:` digest of the bytes served at `uri`. The algorithm portion is lowercase (e.g. `"keccak256:97837f988678ef34cf4ea778f062a25e334d896d73bcf70bf81bfd7c9243f01e"`, `"sha256:4a8b3c2d..."`). Reserved algorithms in this revision: `keccak256` (default convention; matches the onchain `manifestHash`) and `sha256` (broad ecosystem compatibility). Clients MUST reject targets declaring an unrecognized algorithm. Even when `uri` is itself content-addressed (`ipfs://`, `data:`), `integrity` is REQUIRED — it provides a uniform verification path and a cheap defense-in-depth checksum. - `format` (OPTIONAL): how to interpret the bytes fetched at `uri`. Default `"file"` (a single content file written verbatim). Reserved values: `"file"`, `"directory"` (e.g. an IPFS directory CID; the client walks the tree), `"tar+gzip"` (a gzipped tarball the client decompresses after integrity check). Clients MUST reject targets declaring an unrecognized format. - `version` (SHOULD): version of the target artifact or of the protocol it speaks, where distinct from the skill's overall `version`. For targets backed by a dated-release protocol (e.g. MCP), this MAY be a protocol revision date rather than a semver string. - `runtime` (OPTIONAL): execution requirements for code-style targets (e.g. `"python>=3.11"`, `"node>=20"`). - `installation` (OPTIONAL): a string (multi-line allowed) describing setup steps that MUST be performed in addition to (or before) materializing the bytes at `uri`. MAY be a raw command sequence (e.g. `"pip install foo"`) or a natural-language prompt intended for an LLM to execute. Distinct from `uri`/`integrity`, which carry the runtime content itself. Clients are NOT REQUIRED to execute `installation` automatically; treat it as informative instructions for the installing user or agent. A future revision MAY introduce a structured object form; consumers SHOULD accept both. Three hosting modes the schema supports interchangeably: - **Hosted**: `uri` references publicly accessible content (e.g. an IPFS CID, a pinned-SHA GitHub raw URL). - **Inline**: `uri` is a `data:` URI carrying the content directly. - **Onchain**: the manifest itself is encoded as a `data:` URI in the registry's `tokenURI`, optionally with all target bodies also inline. This makes the entire skill self-contained on chain. - `author`: an object identifying the skill's author: - `name` (REQUIRED): the author's human-readable name. - `url` (SHOULD): a homepage, repository, or contact URL. - `agentId` (OPTIONAL): the author's ERC-8004 agent identifier, when the author is also a registered agent. - `agentRegistry` (OPTIONAL, required if `agentId` is present): a namespaced identifier of the form `::` (e.g. `eip155:1:0x742...`). The manifest file MAY include: - `keywords`: an array of free-form discovery tags (e.g. `["security", "audit", "smart-contracts"]`). Indexers MAY surface these in search; this specification does not interpret them. - `repository`: a URL to the skill's source repository (e.g. a git remote). Distinct from any `targets[].uri` value that may happen to point at the same repo: `repository` is the canonical dev source, whereas `targets[].uri` references the specific bytes that make up a runnable artifact. - `supersedes`: when a skill is minted as a new token rather than updating a prior token's URI (see Rationale: "Two version-lifetime patterns"), the new manifest SHOULD populate `supersedes` so that indexers can stitch the version chain. The object contains: - `skillId`: the superseded skill's identifier. - `skillRegistry`: a namespaced identifier of the form `::`. - `requirements`: an object declaring resources the skill expects from its execution environment. This specification does not enumerate permitted keys; common examples include: - `tools`: an array of tool or permission identifiers the skill requires (e.g. `"filesystem:read"`, `"execute:forge"`). - `models`: an array of model capability hints (e.g. `"claude-opus-4+"`, `"gpt-5+"`). - `taxonomy`: an object of taxonomy classifications keyed by provider name (e.g. `oasf`, custom industry verticals). Each provider's value structure is defined by that taxonomy's own schema; this specification does not interpret it. Skills MAY carry multiple classifications within a single provider when the underlying taxonomy supports it; most non-trivial skills cross categories. Array entries within a provider's structure are ordered by the author's preferred relevance, but consumers MUST NOT assume any specific semantic for ordering. Skill taxonomy describes the skill itself, not agents that compose with it. The same skill may legitimately appear inside agents that fall under different agent-level classifications; that distinction is intentional and is the responsibility of agent-side metadata, not the skill manifest. - `dependencies`: an array of other skills this skill depends on. Each entry contains: - `skillId`: the dependency's skill identifier. - `skillRegistry`: a namespaced identifier of the form `::`. - `version`: a version specifier (e.g. semver range). - `status`: a lifecycle indicator. RECOMMENDED values are `"stable"`, `"experimental"`, and `"deprecated"`. - Any additional author-specific fields. Example: ```json { "type": "https://eips.ethereum.org/EIPS/eip-8239#skill-v1", "name": "Example Skill", "description": "Short natural-language description of what the skill does, its inputs and outputs, and any usage notes.", "image": "https://example.com/skill-icon.png", "version": "1.2.0", "license": "MIT", "keywords": ["translation", "summarization", "language"], "homepage": "https://example.com/skills/example-skill", "repository": "https://github.com/example-org/example-skills", "author": { "name": "Example Org", "url": "https://example.com", "agentId": 22, "agentRegistry": "eip155:1:0x0000000000000000000000000000000000000000" }, "targets": [ { "kind": "claude-code-skill", "uri": "ipfs://bafkreig5x4nh7l3vqz8j5e9p2y4w8d6q3v6m4j5e7p2y4w8d6q3v6m4j5e", "integrity": "keccak256:97837f988678ef34cf4ea778f062a25e334d896d73bcf70bf81bfd7c9243f01e", "format": "file", "version": "1.2.0" }, { "kind": "cursor-rule", "uri": "https://raw.githubusercontent.com/example-org/skills/4f3c2a1b/plugins/example-skill/cursor-rule.mdc", "integrity": "sha256:4a8b3c2d9e7f1a5b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b", "format": "file" }, { "kind": "mcp-server", "uri": "https://mcp.example.com/example-skill/manifest.json", "integrity": "sha256:7c4d8e2f1a3b5d7e9f1a3b5d7e9f1a3b5d7e9f1a3b5d7e9f1a3b5d7e9f1a3b5d", "version": "2025-06-18", "installation": "/plugin marketplace add example-org/skills && /plugin install example-skill@example-org" }, { "kind": "cli", "uri": "ipfs://bafybeih2k4l6m8n0p2q4r6s8t0u2v4w6x8y0z2a4b6c8d0e2f4g6h8i0j2k4l6m8n0", "integrity": "keccak256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "format": "tar+gzip", "runtime": "python>=3.11", "version": "1.2.0", "installation": "pip install -r requirements.txt" } ], "requirements": { "tools": ["filesystem:read", "execute:example-tool"], "models": ["claude-opus-4+"] }, "taxonomy": { "oasf": { "skills": [ "natural_language_processing/text_translation", "natural_language_processing/text_summarization" ], "domains": ["language"] } }, "dependencies": [ { "skillId": 23, "skillRegistry": "eip155:1:0x0000000000000000000000000000000000000000", "version": "^1.0" } ], "supersedes": { "skillId": 41, "skillRegistry": "eip155:1:0x0000000000000000000000000000000000000000" }, "status": "stable" } ``` The number and composition of `targets` is fully customizable, allowing authors to expose the same skill across multiple host environments (e.g. a Claude Code skill, a Cursor rule, an MCP server for agent-to-agent consumption, and a plain CLI for direct integration). ### SkillAttestation `SkillAttestation` exposes two semantically distinct event modes under one contract: - **Usage events** (`SkillUsed`) are append-only records of historical use, emitted by self-attestation, evaluator attestation, or hook-mediated attestation. - **Installation events** (`SkillInstalled` / `SkillUninstalled`) are paired emits that declare an agent's current installation state for a given skill; consumers fold the latest event per `(agentId, skillId)` to reconstruct current state. Both modes share the same auth root (the agent owner via the ERC-8004 Identity Registry) and target the same `(agentId, skillId)` keys. ```solidity interface ISkillAttestation { // Usage (append-only) event SkillUsed( uint256 indexed agentId, uint256 indexed skillId, address indexed jobContract, uint256 jobId, address attester, bytes32 evidenceHash, string evidenceURI ); // Installation (current state via paired emits) event SkillInstalled( uint256 indexed agentId, uint256 indexed skillId, address indexed installer, bytes32 metadataHash ); event SkillUninstalled( uint256 indexed agentId, uint256 indexed skillId, address indexed installer ); error NotAgentOwner(uint256 agentId, address caller); error NotJobEvaluator(address jobContract, uint256 jobId, address caller); error NotJobHook(address jobContract, uint256 jobId, address caller); error JobNotCompleted(address jobContract, uint256 jobId); error InvalidJobReference(); error LengthMismatch(uint256 skillIds, uint256 metadataHashes); // Usage functions function attestSelfUsage( uint256 agentId, uint256 skillId, address jobContract, uint256 jobId, bytes32 evidenceHash, string calldata evidenceURI ) external; function attestEvaluatorUsage( uint256 agentId, uint256 skillId, address jobContract, uint256 jobId, bytes32 evidenceHash, string calldata evidenceURI ) external; function attestHookUsage( uint256 agentId, uint256 skillId, address jobContract, uint256 jobId, bytes32 evidenceHash, string calldata evidenceURI ) external; // Installation functions function installSkill(uint256 agentId, uint256 skillId, bytes32 metadataHash) external; function uninstallSkill(uint256 agentId, uint256 skillId) external; function installSkills( uint256 agentId, uint256[] calldata skillIds, bytes32[] calldata metadataHashes ) external; function uninstallSkills(uint256 agentId, uint256[] calldata skillIds) external; } ``` `SkillAttestation` is events-only. Implementations MUST NOT persist attestation state beyond the immutable references set at construction. The evaluator and hook attestation paths depend on the following surface from [ERC-8183](./eip-8183.md): a `getJob(uint256) returns (Job)` view function, and a `Job` struct with at least `evaluator`, `hook`, and `status` fields, where `status` takes a `JobStatus.Completed` value. Consult ERC-8183 for normative definitions. All three attestation functions emit the same `SkillUsed` event shape. A consumer can derive the provenance of a given attestation by inspecting `jobContract` (zero indicates a standalone self-claim) and, when `jobContract != 0`, looking up the referenced job to compare `attester` against `job.evaluator` and `job.hook`. Indexers MAY additionally filter by function selector at capture time if they wish to distinguish provenance without a job lookup. Both `evidenceHash` and `evidenceURI` are OPTIONAL; callers MAY pass `bytes32(0)` and `""` respectively when no offchain evidence applies. `evidenceHash` commits to offchain evidence content (for independent verification); `evidenceURI` indicates where that evidence can be fetched (for discovery). Emitting both is RECOMMENDED when a URI is available. The hash algorithm used to produce `evidenceHash` is at the author's discretion; `keccak256` of the canonicalized evidence bytes is RECOMMENDED so consumers verifying evidence integrity offchain can do so against a familiar primitive without negotiating algorithms per attester. Indexers that verify evidence integrity SHOULD document which algorithm(s) they accept. Implementations MUST NOT attempt to prevent duplicate attestations. The same `(agentId, skillId, jobContract, jobId, attester)` tuple MAY be re-emitted; indexers are responsible for deduplication and for weighting when multiple provenances cover the same pair. #### `attestSelfUsage` A self-attestation by the agent's current owner. Implementations: - MUST revert with `NotAgentOwner(agentId, msg.sender)` if `identityRegistry.ownerOf(agentId) != msg.sender`. - MUST verify `skillId` exists in the referenced `SkillRegistry` (e.g. by checking `ownerOf(skillId) != address(0)`). - MUST revert with `InvalidJobReference` if `jobContract == address(0)` and `jobId != 0`. The pair `(address(0), 0)` denotes a standalone attestation. - MUST NOT verify `jobContract`/`jobId` when either is non-zero; the reference is passed through as metadata and is the caller's claim only. - MUST emit `SkillUsed(agentId, skillId, jobContract, jobId, msg.sender, evidenceHash, evidenceURI)`. #### `attestEvaluatorUsage` A third-party attestation by the evaluator of a completed ERC-8183 job. Implementations: - MUST revert with `InvalidJobReference` if `jobContract == address(0)`. - MUST verify `skillId` exists in the referenced `SkillRegistry`. - MUST verify `agentId` exists in the referenced Identity Registry. - MUST read the job via `IERC8183(jobContract).getJob(jobId)`. - MUST revert with `NotJobEvaluator(...)` if `job.evaluator != msg.sender`. - MUST revert with `JobNotCompleted(...)` if `job.status != Completed`. - MUST emit `SkillUsed(agentId, skillId, jobContract, jobId, msg.sender, evidenceHash, evidenceURI)`. #### `attestHookUsage` A hook-mediated attestation, intended to be called from an ERC-8183 `IACPHook` after `complete()`. Implementations: - MUST revert with `InvalidJobReference` if `jobContract == address(0)`. - MUST verify `skillId` and `agentId` exist in their respective registries. - MUST read `IERC8183(jobContract).getJob(jobId)`. - MUST revert with `NotJobHook(...)` if `job.hook != msg.sender`. - MUST revert with `JobNotCompleted(...)` if `job.status != Completed`. - MUST emit `SkillUsed(agentId, skillId, jobContract, jobId, msg.sender, evidenceHash, evidenceURI)`. The three verification paths carry different signal strengths. Reputation aggregators SHOULD weight them accordingly: self-claims are weakest; hook-mediated attestations verify only that the job's registered hook ran; direct evaluator attestations are the strongest because the evaluator personally called the function after completing the job. #### Installation Mode `SkillAttestation` also exposes installation declarations as a separate event mode under the same contract. Current installation state is reconstructed offchain by folding `SkillInstalled` and `SkillUninstalled` events per `(agentId, skillId)` pair; the latest event for a pair is the current state. Implementations: - `installSkill` MUST revert with `NotAgentOwner` if `identityRegistry.ownerOf(agentId) != msg.sender`, MUST verify `skillId` exists, and MUST emit `SkillInstalled(agentId, skillId, msg.sender, metadataHash)`. - `uninstallSkill` MUST perform the same owner and existence checks and MUST emit `SkillUninstalled(agentId, skillId, msg.sender)`. - Re-emission of `SkillInstalled` for an already-installed pair is permitted and serves as a metadata refresh; indexers MUST treat the most recent event as authoritative. - Implementations MUST NOT require a prior `SkillInstalled` event before accepting an `uninstallSkill` call. Indexers handle the stateless edge cases. - `installSkills` and `uninstallSkills` MUST apply the same owner check and per-skill existence check as their single-shot counterparts, and MUST emit one `SkillInstalled` or `SkillUninstalled` event per entry. `installSkills` MUST revert with `LengthMismatch` if `skillIds.length != metadataHashes.length`. The `metadataHash` field is OPTIONAL (zero to indicate absence) and points at offchain installation-specific data (e.g. fine-tuned adapter checkpoint, tool bundle configuration, prompt pack version). This specification does not define the referenced format. #### EAS Integration (Informative) Implementations and downstream attestors that prefer the Ethereum Attestation Service (EAS) for skill-use attestations MAY publish through EAS in addition to or instead of calling `SkillAttestation`. To preserve interoperability with consumers of native `SkillUsed` events, any EAS-based skill-use attestation SHOULD use the canonical schema: > `uint256 agentId,uint256 skillId,address jobContract,uint256 jobId,bytes32 evidenceHash,string evidenceURI` with `resolver = address(0)` and `revocable = true`. The resulting schema UID is chain-agnostic and equal to: > `0x5d55784a1e8351f3ea25c787f8be3c39d95d474b78a2a109623fa3f9f35fea6f` Per-chain registration via the EAS Schema Registry's `register()` is operational work that records the schema and emits a discovery event; the UID itself is fixed regardless. The EAS attestation envelope already carries `attester`, `time`, and `recipient`; the schema therefore omits `attester`, the envelope's `time` covers the temporal field, and `recipient` SHOULD be set to `address(0)` since the agent under attestation is identified by `agentId` in the data. Implementations SHOULD expose the schema string and UID as public constants on the `SkillAttestation` contract — computed at compile time from the schema constant so the two values cannot drift — so hook implementations and other onchain consumers can reference them without hardcoding. Both EAS modes MAY be used: - **Onchain attestations** (issued via `EAS.attest(...)`) emit an `Attested` event under the canonical schema UID and persist in EAS contract storage. Indexers SHOULD treat such attestations and `SkillUsed` events from `SkillAttestation` as semantically equivalent for the purpose of skill-use aggregation. - **Offchain attestations** ([EIP-712](./eip-712.md) signed payloads under the same canonical schema, not posted onchain at issuance) are also valid. Consumers MUST verify the EIP-712 signature against the claimed attester's address before treating the attestation as authoritative. Discovery of offchain attestations is implementation-defined; the spec does not mandate a publication or indexing channel. Revocations posted onchain via `EAS.revoke(uid)` (for onchain attestations) or `EAS.revokeOffchain(...)` (for offchain attestations) SHOULD be honored by consumers regardless of where the original attestation was issued. ### SkillAttestationHook ERC-8183 jobs accept a `hook` address at `createJob` time. When the evaluator calls `complete()`, the job contract dispatches to the hook's `afterAction(jobId, selector, data)`. ASR uses this dispatch as its mechanism for automatic skill-use attestations from completed jobs: any contract registered as a job's hook can call `SkillAttestation.attestHookUsage(...)`, and the existing auth check (`IERC8183(jobContract).getJob(jobId).hook == msg.sender`) verifies the call came from that specific job's registered hook. Because that auth check operates on the registered hook address rather than a blessed implementation, the protocol naturally supports an **ecosystem of hook implementations**. The reference implementation that ships with this specification (described below) is one example; others MAY exist with their own `optParams` encodings, additional behaviors (gating, policy checks, cross-registry emission, parallel EAS publication, audit logging), or domain-specific decoders. #### Hook Conformance A conforming hook MUST: 1. Implement the `IACPHook` interface (so an ERC-8183 job will dispatch to it): ```solidity interface IACPHook { function beforeAction(uint256 jobId, bytes4 selector, bytes calldata data) external; function afterAction(uint256 jobId, bytes4 selector, bytes calldata data) external; } ``` 2. Be registerable as the `hook` parameter of an ERC-8183 `createJob` call. 3. When invoked from `afterAction` for the `complete` selector, produce skill-use attestations that are decodable by an ASR-aware indexer through one of the documented attestation channels: - Calling `SkillAttestation.attestHookUsage(...)` on an ASR-deployed `SkillAttestation` contract (native channel), and/or - Submitting attestations to EAS under the canonical schema UID defined in the EAS Integration subsection (EAS channel). A conforming hook MAY use either channel, both, or future-defined alternative channels with equivalent semantic guarantees. Whichever channel(s) it uses, the emitted tuples MUST accurately reflect the work the hook is attesting to. A conforming hook MUST NOT: - Forge or reorder the `(agentId, skillId)` tuples it forwards. - Submit attestations for a `jobContract` other than the one that invoked `afterAction` (i.e. the `jobContract` field on each emitted attestation MUST equal `msg.sender` of the `afterAction` call). A conforming hook SHOULD provide a no-op `beforeAction`. The protocol does not enumerate or restrict the `optParams` encodings a hook accepts, the additional side effects it MAY perform (events, downstream calls, parallel publication, etc.), or whether it emits one attestation per `complete()` call versus many. Channel choice is a deployment / operator decision; consumers consuming only one channel will not see attestations emitted exclusively to another. Indexers SHOULD treat hook-attested events as trusted to the level they trust the specific hook contract address regardless of channel; see Security Considerations. #### Reference Hook This specification ships one reference hook implementation. Its `afterAction`: - MUST return without emitting if `selector != IERC8183.complete.selector`. - MUST return without emitting if `data.length == 0`. - OTHERWISE MUST decode `data` as `abi.encode(uint256 agentId, uint256[] skillIds, bytes32[] evidenceHashes, string[] evidenceURIs)`. - MUST revert if the three array lengths are not all equal. - MUST, for each `i` in `[0, skillIds.length)`, call `skillAttestation.attestHookUsage(agentId, skillIds[i], msg.sender, jobId, evidenceHashes[i], evidenceURIs[i])` where `skillAttestation` is the `SkillAttestation` contract the hook was constructed with, and `msg.sender` is the ERC-8183 job contract. Its `beforeAction` is a no-op. This `optParams` encoding is the **recommended canonical form** for hooks that want default-indexer compatibility. Hooks using alternative encodings remain conformant but require their consumers to use bespoke decoders. Job participants who wish to produce hook-attested usage events via the reference hook MUST: 1. Pass the reference hook's address as the `hook` parameter to `createJob`. 2. Have the evaluator call `complete(jobId, reason, optParams)` with `optParams` encoded per the canonical form above. ### Client Conformance A *client* is software that resolves and verifies skills under this specification. The key words MUST/SHOULD/MAY in this section follow [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). This section defines only the behavior required to consume onchain skill data correctly. How a client materializes a verified skill into its host environment, manages signers, applies policy, or exposes a UX is out of scope; those are implementation choices. #### Resolve Given a `skillId`, a client MUST: 1. Read `tokenURI(skillId)` from the configured `SkillRegistry`. 2. Fetch the bytes at the returned URI. A client MUST support `https://` and `data:`. 3. Parse the result as a Skill Manifest File per §"Skill Manifest File". #### Verify Before relying on a resolved manifest or any of its targets, a client MUST: 1. Compute `keccak256(manifest bytes)`. If `manifestHashes(skillId)` is non-zero, the computed hash MUST match the registry value. 2. For each target the client intends to use, fetch the target's `uri` and verify that `(body)` matches the target's `integrity`, where `` is the prefix of the `integrity` string (§"Skill Manifest File"). ### Reputation Tag Convention Skill-related feedback filed in the ERC-8004 Reputation Registry SHOULD carry the following tag values: | Field | Value | |--------|---------------------------------------------------------| | `tag1` | `"asr:skill"` | | `tag2` | `":::"` | Where: - `namespace` is a CAIP-2-style chain namespace. For EVM chains this is `eip155`. - `chainId` is the chain ID of the SkillRegistry deployment, in decimal. - `registryAddress` is the 20-byte hex address of the SkillRegistry, lowercase, prefixed with `0x`. - `skillId` is the skill's ERC-721 token ID, in decimal. This format matches the `agentRegistry` / `skillRegistry` identifiers used in the Skill Manifest File, with `:` appended to select a specific skill. Indexers aggregating skill reputation SHOULD filter on `tag1 == "asr:skill"`, parse `tag2` into its four components, and cross-reference `registryAddress` against an allowlist of trusted SkillRegistry deployments. ### Deterministic Deployment Implementations SHOULD be deployed via CREATE2 with a standard salt. Deterministic deployment provides: - Predictable addresses within a given chain, supporting idempotent redeploys and letting composing contracts precompute addresses before deployment. - Identical cross-chain addresses for contracts whose constructor arguments are themselves identical across chains. In practice, this condition is met only by `SkillRegistry`, which takes no constructor arguments. `SkillAttestation` and `SkillAttestationHook` each take at least one address argument (the ERC-8004 Identity Registry, a `SkillRegistry`, or a `SkillAttestation`) that typically differs across chains; their CREATE2 addresses will accordingly differ across chains even with the same salt. Ecosystems MAY coordinate on a shared factory and salt convention; such coordination is out of scope for this EIP. ## Rationale ### ERC-721 for skill identity An earlier design considered [ERC-1155](./eip-1155.md), treating skill IDs as token IDs and balances as "installation counts." Two factors decided the choice for ERC-721: - Conflation. Using balances as installation counts merges skill registration with skill installation into one structure. The two are different relationships (1 author → 1 skill identity vs. N agents → 1 skill installation) and benefit from being separately addressable. - Precedent. Representing skills as individually-transferable NFTs matches how author-owned digital goods are typically modeled and is consistent with ERC-8004's choice of ERC-721 for agent identity. ### Two version-lifetime patterns Authors can express a new version of a skill via either of two patterns, depending on the properties they want to preserve: - **Single token, mutable URI** (the recommended default). The skill keeps a stable `skillId` across its entire lifetime; the author updates `skillURI` via `setTokenURI` when publishing a new version. Reputation and usage attestations accumulate against one identifier. This matches how package-manager ecosystems (npm, cargo) treat a named package with successive versions. - **New token per version.** The author mints a fresh skill token for each version, using content-addressed `skillURI` values so each version is an immutable artifact. The new manifest populates `supersedes` (see the Skill Manifest File field list) to point at the prior version's token so indexers can stitch the version chain and carry reputation forward at their discretion. The spec does not encode version information in the `skillURI` scheme itself. The manifest's `version` field is the authoritative version marker; URI structure is at the author's discretion. ### Events-only for attestation and installation Attestation and installation relationships are rarely queried onchain and accumulate over time. Storing them persistently inflates gas costs without enabling onchain use cases that events cannot serve. Offchain indexers are the natural consumer, and the `indexed` event topics cover the primary query dimensions (`agentId`, `skillId`, `jobContract`). A future EIP MAY add a storage-backed state adapter if onchain queries become necessary. ### Three attest functions, one event shape An earlier revision tagged each attestation with an `AttestationKind` enum (`SelfAttested`, `EvaluatorAttested`, `HookAttested`) on the emitted event. This was dropped because the provenance of an attestation is already derivable: `jobContract == address(0)` signals a standalone self-claim, and otherwise `attester` combined with a one-time lookup of the referenced job distinguishes evaluator from hook from other roles. Carrying a `kind` field in addition to that information cached state that can drift from onchain reality (e.g. if future ERC versions add roles) without adding verifiable information. The three verification paths remain exposed as separate functions because their authorization checks differ meaningfully and consumers benefit from per-function selectors for capture-time filtering. ### Reputation via ERC-8004 tags Forking the ERC-8004 Reputation Registry with `skillId` substituted for `agentId` was considered. However, ERC-8004 already provides two free-form tag slots (`tag1`, `tag2`), and any tool already indexing 8004 feedback can participate in skill reputation without new integrations. Author reputation is intentionally left emergent (derivable from the aggregated performance of skills an author registered) rather than built as a first-class signal. Authors who want a direct reputation channel MAY register themselves as ERC-8004 agents and receive feedback through the existing 8004 mechanism. ### No onchain skill taxonomy A fixed enum for skill categories (OASF, custom, etc.) was considered. Category information lives in the offchain manifest at `skillURI`, where it can be versioned and extended freely. ### No URI uniqueness enforcement An earlier design considered rejecting duplicate `skillURI` registrations. This was removed because URI equality does not capture skill equivalence (e.g. different authors may legitimately reference the same underlying content; the same content may be hosted at multiple URIs), and because front-run squatting protection is more effectively handled by reputation and indexer-side canonicalization than by registry-level uniqueness. ## Backwards Compatibility This EIP introduces new contracts and does not modify existing standards. There are no backward-compatibility concerns. Deployments MAY reference any ERC-8004 Identity Registry implementation and any ERC-8183 job contract implementation. ## Test Cases Reference tests covering the required behaviors (happy paths, auth reverts, existence reverts, job-state reverts, batch operations, and hook dispatch) are provided in the companion reference implementation, with usage and installation paths exercised against the unified `SkillAttestation` contract. Topics covered include: registration with manifest-hash storage and the dual emission of `SkillRegistered` and `SkillURIUpdated`; atomic URI-and-hash updates via `setTokenURI` with owner-only enforcement; all three attestation paths (self, evaluator, hook) with `evidenceHash` and `evidenceURI` variants; single-shot and batch install / uninstall; hook dispatch with multiple skills and length-mismatch rejection; and every revert condition defined by this specification. ## Reference Implementation A reference implementation of the contracts (`SkillRegistry`, `SkillAttestation`, and the reference `SkillAttestationHook`) in Solidity targeting `^0.8.28` and using the OpenZeppelin ERC-721 base is available at `github.com/vorpalengineering/agent-skill-registry`. A reference *client* (`asrpm`) implementing the §Client Conformance section is available in Go at `github.com/vorpalengineering/asrpm`. ## Security Considerations ### Squatting on skill registration `registerSkill` is permissionless and does not enforce URI uniqueness. Adversaries MAY register skills pointing at popular offchain manifests before the legitimate author. This is an intentional design choice: onchain uniqueness does not prevent content duplication in either direction (different URIs, same content, vice versa), and offchain signals — author verification via signed manifests, reputation, indexer curation — are better positioned to distinguish legitimate authorship. Consumers relying on a skill's onchain identity SHOULD verify authorship via offchain mechanisms such as ENS or ERC-8004's `.well-known/agent-registration.json` domain verification. ### Sybil resistance on attestations None of the attestation or installation functions in `SkillAttestation` provide sybil resistance at the contract layer. An attacker with many addresses can mint many agents and self-attest usage for any skill, or declare arbitrary skills installed. Indexers and reputation aggregators MUST provide their own sybil resistance, typically by weighting evaluator-attested signals over self-attested ones, by requiring evidence hashes, or by cross-referencing with other signals (token-holding, staking, social graph). Additionally, `attestSelfUsage` does not verify any `jobContract` or `jobId` the caller supplies. A self-attesting agent MAY reference a job it was not party to — the reference is passed through as caller-supplied metadata only. Indexers SHOULD treat job-linked self-claims with appropriate skepticism, and MAY independently look up the referenced job to confirm the attesting agent's involvement before weighting the signal. ### Trust boundaries for attested events Evaluator and hook-attested events derive their trust from two independent layers, each addressed by per-address allowlisting on the consumer side. **The job contract.** Evaluator and hook-attested paths read `IERC8183(jobContract).getJob(jobId)` to verify `msg.sender` matches the job's `evaluator` or `hook` field. This inherits ERC-8183's trust model: the client who created the job chose those addresses, and the provider accepted them by participating. A malicious actor can deploy a fake ERC-8183-shaped contract in which they are the evaluator or hook of any job, call the corresponding `SkillAttestation` function, and produce attestations that pass auth. Indexers MUST maintain an allowlist of trusted `jobContract` deployments and filter attestations referencing unknown contracts. **The hook contract.** Hook-attested events additionally carry the trust of the specific hook contract address that emitted them. The `attestHookUsage` auth check verifies the call came from the registered `hook` of the referenced job, but it does not (and cannot) verify what that hook implementation does with the `optParams` it received, what additional logic it applied, or whether the emitted tuples accurately reflect the underlying work. Indexers and reputation aggregators SHOULD: - Maintain an allowlist (or weighted reputation) of hook contract addresses they recognize and trust. - Treat attestations from unrecognized hook addresses as lower-weight signals or filter them entirely. - Document which hook implementations they support, including the `optParams` encoding each hook expects. Both layers follow the same pattern: the protocol exposes the mechanism; ecosystem-side allowlists and reputation systems carry the trust. ### Agent ownership transfer timing ERC-8004 agent identities are ERC-721 tokens and can be transferred. `attestSelfUsage` checks current ownership via `ownerOf(agentId)`, which means an agent transferred between actions MAY see attestations filed by the prior owner (pre-transfer) and the new owner (post-transfer). Attestations do not retroactively belong to the new owner, and indexers SHOULD disambiguate by attestation timestamp if this distinction matters for reputation. Evaluator and hook-attested paths do not verify that `agentId`'s current owner matches the referenced job's `provider`. This is intentional: owner transfers between job completion and attestation would otherwise render legitimate attestations unsatisfiable. Indexers that care about the provider-owner match SHOULD snapshot ownership at the job's completion block. ### Hook data integrity The reference hook trusts `optParams` passed by the caller of `complete()` (the evaluator). It is possible to declare skills that the provider did not actually use, or omit skills that were used. The hook does not attempt to cross-validate; this is a deliberate minimalism. If stronger integrity is needed, the provider and evaluator SHOULD exchange signed offchain skill declarations and include the signature hash in `evidenceHashes`, which indexers can then verify. Alternative hook implementations MAY enforce stricter validation at their own discretion. ### Manifest integrity, mutability, and transfer The onchain `skillId` is a stable identifier, but the offchain manifest referenced by `skillURI` MAY be mutable (for HTTPS URIs) or immutable (for content-addressed URIs like IPFS). Independent of URI scheme, the `manifestHashes(skillId)` mapping is the integrity anchor: consumers SHOULD verify the bytes they fetched hash to the registered value before relying on the manifest's contents, catching both unintended URI-content drift and outright storage tampering. The registry stores only the most recent `manifestHash` per skill; older values are not retained in storage. The full URI/hash history is reconstructable from the `SkillURIUpdated` event stream, which is emitted on registration and on every subsequent update. Consumers evaluating an attestation or installation against a since-updated skill can determine which manifest version was current at the referenced block timestamp by walking these events. Indexers SHOULD index the stream so that historical references to a `skillId` resolve to the correct version rather than collapsing to "whatever the manifest currently says." `setTokenURI` is the only path the spec exposes for changing either `skillURI` or `manifestHash`; conformant implementations MUST NOT introduce a path that changes one without the other. Allowing URI-only updates would let an author serve different bytes under the same committed hash; allowing hash-only updates would let an author disclaim the bytes the URI still points at. Skill tokens are transferable per ERC-721. Integrators SHOULD account for: - The new owner MAY call `setTokenURI` to update both the URI and the `manifestHash`, effectively changing what the skill claims to be. The `SkillURIUpdated` event makes such changes detectable. - Attestations and installations continue to reference the stable `skillId` across transfers; reputation and installation state follow the token, not the original author. - Consumers who require a specific version of a skill SHOULD pin the `manifestHash` they observed at the time and refuse to act if the current registry value diverges. This mirrors ERC-8004's treatment of agent-identity transfers; the same caveats apply. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).