EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8239: Agent Skill Registry

A canonical onchain registry for agent skills with usage and installation attestations.

Authors
Created2026-04-21
Discussion Linkhttps://ethereum-magicians.org/t/erc-8239-agent-skill-registry/28335
Requires

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1704PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationOverviewSkillRegistrySkill Manifest FileSkillAttestationattestSelfUsageattestEvaluatorUsageattestHookUsageInstallation ModeEAS Integration (Informative)SkillAttestationHookHook ConformanceReference HookClient ConformanceResolveVerifyReputation Tag ConventionDeterministic DeploymentRationaleERC-721 for skill identityTwo version-lifetime patternsEvents-only for attestation and installationThree attest functions, one event shapeReputation via ERC-8004 tagsNo onchain skill taxonomyNo URI uniqueness enforcementBackwards CompatibilityTest CasesReference ImplementationSecurity ConsiderationsSquatting on skill registrationSybil resistance on attestationsTrust boundaries for attested eventsAgent ownership transfer timingHook data integrityManifest integrity, mutability, and transferCopyright

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

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) 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

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=<sha> and similar schemes; clients MAY accept additional schemes consistent with this rule.
    • integrity (REQUIRED whenever uri is set): an OCI-style <algorithm>:<hex> 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 <namespace>:<chainId>:<identityRegistry> (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 <namespace>:<chainId>:<skillRegistry>.
  • 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 <namespace>:<chainId>:<skillRegistry>.
    • 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:

{
  "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.

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: 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 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):

    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. 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 <algorithm>(body) matches the target's integrity, where <algorithm> 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:

FieldValue
tag1"asr:skill"
tag2"<namespace>:<chainId>:<registryAddress>:<skillId>"

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 :<skillId> 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, 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 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