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:
- 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.
- 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. - 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
SkillRegistrycontract implementingISkillRegistryand ERC-721. - A
SkillAttestationcontract implementingISkillAttestation. 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
SkillRegistryused 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 whosekindthey 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 viaintegrity. Future revisions MAY addgit+https://...?ref=<sha>and similar schemes; clients MAY accept additional schemes consistent with this rule.integrity(REQUIRED wheneveruriis set): an OCI-style<algorithm>:<hex>digest of the bytes served aturi. The algorithm portion is lowercase (e.g."keccak256:97837f988678ef34cf4ea778f062a25e334d896d73bcf70bf81bfd7c9243f01e","sha256:4a8b3c2d..."). Reserved algorithms in this revision:keccak256(default convention; matches the onchainmanifestHash) andsha256(broad ecosystem compatibility). Clients MUST reject targets declaring an unrecognized algorithm. Even whenuriis itself content-addressed (ipfs://,data:),integrityis REQUIRED β it provides a uniform verification path and a cheap defense-in-depth checksum.format(OPTIONAL): how to interpret the bytes fetched aturi. 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 overallversion. 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 aturi. MAY be a raw command sequence (e.g."pip install foo") or a natural-language prompt intended for an LLM to execute. Distinct fromuri/integrity, which carry the runtime content itself. Clients are NOT REQUIRED to executeinstallationautomatically; 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:
urireferences publicly accessible content (e.g. an IPFS CID, a pinned-SHA GitHub raw URL). - Inline:
uriis adata:URI carrying the content directly. - Onchain: the manifest itself is encoded as a
data:URI in the registry'stokenURI, 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 ifagentIdis 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 anytargets[].urivalue that may happen to point at the same repo:repositoryis the canonical dev source, whereastargets[].urireferences 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 populatesupersedesso 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)ifidentityRegistry.ownerOf(agentId) != msg.sender. - MUST verify
skillIdexists in the referencedSkillRegistry(e.g. by checkingownerOf(skillId) != address(0)). - MUST revert with
InvalidJobReferenceifjobContract == address(0)andjobId != 0. The pair(address(0), 0)denotes a standalone attestation. - MUST NOT verify
jobContract/jobIdwhen 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
InvalidJobReferenceifjobContract == address(0). - MUST verify
skillIdexists in the referencedSkillRegistry. - MUST verify
agentIdexists in the referenced Identity Registry. - MUST read the job via
IERC8183(jobContract).getJob(jobId). - MUST revert with
NotJobEvaluator(...)ifjob.evaluator != msg.sender. - MUST revert with
JobNotCompleted(...)ifjob.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
InvalidJobReferenceifjobContract == address(0). - MUST verify
skillIdandagentIdexist in their respective registries. - MUST read
IERC8183(jobContract).getJob(jobId). - MUST revert with
NotJobHook(...)ifjob.hook != msg.sender. - MUST revert with
JobNotCompleted(...)ifjob.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:
installSkillMUST revert withNotAgentOwnerifidentityRegistry.ownerOf(agentId) != msg.sender, MUST verifyskillIdexists, and MUST emitSkillInstalled(agentId, skillId, msg.sender, metadataHash).uninstallSkillMUST perform the same owner and existence checks and MUST emitSkillUninstalled(agentId, skillId, msg.sender).- Re-emission of
SkillInstalledfor 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
SkillInstalledevent before accepting anuninstallSkillcall. Indexers handle the stateless edge cases. installSkillsanduninstallSkillsMUST apply the same owner check and per-skill existence check as their single-shot counterparts, and MUST emit oneSkillInstalledorSkillUninstalledevent per entry.installSkillsMUST revert withLengthMismatchifskillIds.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 anAttestedevent under the canonical schema UID and persist in EAS contract storage. Indexers SHOULD treat such attestations andSkillUsedevents fromSkillAttestationas 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:
-
Implement the
IACPHookinterface (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; } -
Be registerable as the
hookparameter of an ERC-8183createJobcall. -
When invoked from
afterActionfor thecompleteselector, 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-deployedSkillAttestationcontract (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.
- Calling
A conforming hook MUST NOT:
- Forge or reorder the
(agentId, skillId)tuples it forwards. - Submit attestations for a
jobContractother than the one that invokedafterAction(i.e. thejobContractfield on each emitted attestation MUST equalmsg.senderof theafterActioncall).
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
dataasabi.encode(uint256 agentId, uint256[] skillIds, bytes32[] evidenceHashes, string[] evidenceURIs). - MUST revert if the three array lengths are not all equal.
- MUST, for each
iin[0, skillIds.length), callskillAttestation.attestHookUsage(agentId, skillIds[i], msg.sender, jobId, evidenceHashes[i], evidenceURIs[i])whereskillAttestationis theSkillAttestationcontract the hook was constructed with, andmsg.senderis 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:
- Pass the reference hook's address as the
hookparameter tocreateJob. - Have the evaluator call
complete(jobId, reason, optParams)withoptParamsencoded 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:
- Read
tokenURI(skillId)from the configuredSkillRegistry. - Fetch the bytes at the returned URI. A client MUST support
https://anddata:. - 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:
- Compute
keccak256(manifest bytes). IfmanifestHashes(skillId)is non-zero, the computed hash MUST match the registry value. - For each target the client intends to use, fetch the target's
uriand verify that<algorithm>(body)matches the target'sintegrity, where<algorithm>is the prefix of theintegritystring (Β§"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 | "<namespace>:<chainId>:<registryAddress>:<skillId>" |
Where:
namespaceis a CAIP-2-style chain namespace. For EVM chains this iseip155.chainIdis the chain ID of the SkillRegistry deployment, in decimal.registryAddressis the 20-byte hex address of the SkillRegistry, lowercase, prefixed with0x.skillIdis 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.SkillAttestationandSkillAttestationHookeach take at least one address argument (the ERC-8004 Identity Registry, aSkillRegistry, or aSkillAttestation) 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
skillIdacross its entire lifetime; the author updatesskillURIviasetTokenURIwhen 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
skillURIvalues so each version is an immutable artifact. The new manifest populatessupersedes(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
optParamsencoding 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
setTokenURIto update both the URI and themanifestHash, effectively changing what the skill claims to be. TheSkillURIUpdatedevent makes such changes detectable. - Attestations and installations continue to reference the stable
skillIdacross transfers; reputation and installation state follow the token, not the original author. - Consumers who require a specific version of a skill SHOULD pin
the
manifestHashthey 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.
