@agenticprimitives/registry-kit
v0.0.0-alpha.14
Published
AP-native registry-building primitives: Smart-Agent-anchored registry entries, signed-card binding proofs, claim-slot schemas, lifecycle call builders, and event decoders for vertical registries. External registry publication/sync stays outside Ring 0.
Maintainers
Readme
@agenticprimitives/registry-kit
Part of Agentic Primitives — the open-source trust substrate for agentic applications: identity that can sign, authority checked at act time, evidence the owner carries. Developer kit · All packages
AP-native registry-building primitives — Smart-Agent-anchored registry entries, signed-card binding proofs, claim-slot schemas, lifecycle call builders, and event decoders for vertical registries.
Status: experimental (0.0.0-alpha). Specs: specs/279-agent-discovery-registry-kit.md
(binding proofs, call builders, events) + specs/346-typed-agent-naming-and-registry.md §7
(registry core: admission pipeline, receipts, lifecycle log, revalidation status).
Why
The "many-registries hypothesis" (ADR-0038): hundreds of (mostly vertical) agent registries will exist; there is no horizontal winner. So Ring 0 ships the kit registries are built FROM, not another registry to join. The Smart Agent address is the canonical identifier (ADR-0010); a registry entry, a signed agent card, and any external listing are facets that point at it.
This package's one job: let a verifier confirm a registry entry is a genuine facet of a specific Smart
Agent and a specific signed card — without trusting the registry that served it — and give registry
authors generic claim-slot schemas, lifecycle call builders, and event decoders. It does not query
directories, rank or match agents, own intent, or speak an external registry protocol; those live in
identity-directory, the intent packages, apps, or external integration repos.
Install
pnpm add @agenticprimitives/registry-kit @agenticprimitives/types viemBinding proof
A binding proof is a deterministic sha256 over the proof body
(registryId, entryId, subjectAgent, cardHash, claimHashes, issuedAt), signed by the subject
Smart Agent via ERC-1271. Both the signer and the on-chain ERC-1271 read are injected, so the
package stays a pure, network-free leaf.
import { buildEntryBindingProof, verifyEntryBindingProof } from '@agenticprimitives/registry-kit';
// build — `sign` is your SA's ERC-1271-validatable signer over the 32-byte digest
const proof = await buildEntryBindingProof(
{
registryId: 'urn:ap:registry:health-eu',
entryId: 'urn:ap:registry-entry:abc',
subjectAgent: '0xSmartAgent…',
cardHash: 'sha256:…', // hash of the agent-profile signed card bundle
claimHashes: ['sha256:…'], // hashes of the claims that fill the registry's slots
issuedAt: new Date().toISOString(),
},
sign,
);
// verify — `verifier.verifyHash` is satisfied structurally by a viem PublicClient
const result = await verifyEntryBindingProof(proof, { verifier });
if (result.ok) {
// result.subjectAgent / result.cardHash / result.claimHashes are trustworthy
}verifyEntryBindingProof is fail-closed (ADR-0013):
it recomputes proofHash from the proof's own fields (catching any field tamper that left the hash
stale) and re-checks the subject SA's ERC-1271 signature over that digest (catching a tamper that
also rewrote proofHash, since the signature committed to the original digest). Any anomaly returns
{ ok: false, reason } where reason ∈ { bad_shape, proof_hash_mismatch, signature_invalid }. There is
exactly one verification mechanism — no escalation to a second, weaker path.
Lifecycle call builders
Pure encoders that return the { to, value, data } a Smart Agent executes via its own custody / UserOp
path. registry-kit never sends transactions and never decides who may call — the contract's pluggable
membership / validation hook owns authorization.
import { buildRegisterEntryCall, buildRevokeEntryCall } from '@agenticprimitives/registry-kit';
const register = buildRegisterEntryCall({
registry: '0xAgentRegistryBase…',
registryId: 'urn:ap:registry:health-eu',
entryId: 'urn:ap:registry-entry:abc',
subjectAgent: '0xSmartAgent…',
cardHash: 'sha256:…',
bindingProofHash: 'sha256:…',
claimHashes: ['sha256:…'],
expiresAt: 0, // unix seconds; 0 = non-expiring
});
// → submit `register` through your SA execution pathURN ids (urn:ap:registry:…, urn:ap:registry-entry:…, urn:ap:registry-claim-slot:…) become their
on-chain bytes32 keys via keccak; sha256:<hex> content hashes become bytes32 (the raw 32 bytes).
Helpers urnToBytes32 / sha256ToBytes32 / reasonToBytes32 are exported. The Solidity
AgentRegistryBase peer (spec §4) lands in @agenticprimitives/contracts in a later increment and must
match AGENT_REGISTRY_BASE_ABI.
Event decoding
import { decodeRegistryKitEvent } from '@agenticprimitives/registry-kit';
const event = decodeRegistryKitEvent({ topics, data }); // one raw log → typed RegistryKitEventThe decoder is for an indexer to project events into an A-box read model (adding block / tx / log provenance itself). Product discovery reads query that read model, never chain logs (ADR-0012). The A-box is treated as evidence and discovery substrate, never as authority — and never holds private vault bodies, private delegations, or raw PII.
Projection primitives (spec 347 §6)
import … from '@agenticprimitives/registry-kit/projection' (also re-exported from the root). One canonical
agent → many projections (how selected facts are represented for one target), publications (where and
when an artifact was deployed) and external identity bindings (which external id represents the agent).
The rule that matters (ADR-0062):
- Projectors are pure and synchronous.
Projector<Config, Artifact>.project(input, config)takes a sealedProjectionInputBundleV1(JCS + sha256,sealInputBundle) and returns aProjectionResultV1— artifact, the eight digests (sourceBundle,definition,targetSpecification,adapter,configuration,artifact,canonicalProfile,selectedCard;null= honestly unavailable), diagnostics, a loss report (always present) and required actions. No fetch, timers, randomness, clock or credentials. - Publishers are effectful and approval-gated.
Publisher.planproduces a dry-runPublicationPlanV1(operations, credential references, cost ceiling, idempotency key, expiry,planDigest);execute(plan, approval)MUST passassertApprovalCoversPlan— wrong digest, wrong/missing idempotency key, expired approval or plan all throwPlanNotApprovedError. - Standards lock.
docs/standards-lock.jsonpins every target's version + source digest;isDefinitionCurrentmarks a definitionreview-requiredon any change — nothing is rewritten silently. - Drift is a pure fold.
desiredVsRemote(instance, latestDigests, remoteObservation, lock)yields oneDriftStatus; a remote change becomes anImportProposalV1(proposeImport) and never a canonical write. - State machines.
transitionProjection/transitionBindingenforce the spec 347 §3 lifecycles; illegal transitions throwProjectionLifecycleError.
import { apRegistryProjector, buildApRegistryPublicationPlan, apRegistryPublisher, sealInputBundle } from '@agenticprimitives/registry-kit/projection';
const input = sealInputBundle({ type: 'ProjectionInputBundleV1', canonicalAgent, canonicalProfile, selectedA2ACard, skillClaims, trustClaims, names, existingExternalBindings: [], disclosurePolicyId });
const result = apRegistryProjector.project(input, { chainId, registry, registryId, claimSlots, issuedAt });
// result.artifact.entry is the spec 346 RegistryEntry; result.losses says what did not make it and why.
const publisher = apRegistryPublisher({ execute: appBoundExecute, now: () => new Date().toISOString() });
const plan = await publisher.plan(result.artifact, { projectionInstanceId, credentialRefs, dryRun: false, now, idempotencyKey });
await publisher.execute(plan, approvalRef); // throws PlanNotApprovedError unless the approval names THIS planTwo first-party targets ship here: AP Registry (apRegistryProjector → RegistryEntry + unsigned
binding-proof body + card/profile refs; plan = SA signature + registerEntry call) and AP Naming
(apNamingProjector → name-record values incl. cardDigest/cardUri; encoding is agent-naming's job,
bound by the app). Every external target (OASF, ERC-8004, HCS, ANS) is an adapter in the sibling
agent-projections repo and must pass runProjectorConformance from registry-kit/projection/testkit
(determinism, purity, loss report, digests, lock currency, golden fixtures).
Boundaries
registry-kit is a generic Ring-0 primitive. It depends only on @agenticprimitives/types and viem,
and is forbidden from importing identity-directory, the intent packages, fulfillment, mcp-runtime,
or any app. Signed-card bundles belong to agent-profile; graph/read-model query to
identity-directory; GraphDB / indexer / discovery UI to apps or external integration repos
(ADR-0037).
ERC-8004 / HCS / OASF vocabularies are mappings and design inputs (see packages/ontology/mappings/),
never AP package semantics.
License
MIT
Registry core (spec 346 §7)
Everything a registry operator needs to ADMIT an entry and keep it honest afterwards — with every network read injected, so the package stays a pure leaf:
import { admitRegistryEntry, createInMemoryLog, deriveEntryStatus } from '@agenticprimitives/registry-kit';
const outcome = await admitRegistryEntry(
{ registryId, entryId, subjectAgent, subjectType: 'person', name: 'alice.me', bindingProof,
cardBundle, publication, authorityProof: { kind: 'subject-signature' }, versions: { ontologyVersion } },
{ fetchArtifact, verifyErc1271, delegationLive, readDerivedType, resolveTyped, verifyCardBundle, verifyPublication, now },
);
// outcome.receipt lists EVERY check exactly once as verified / failed / notVerified —
// an unreachable card is `notVerified: 'unavailable'`, never verified.
if (outcome.admitted) {
const receipt = await outcome.sign(operatorSigner); // RegistrationReceiptV1, ERC-1271 by the operator SA
await log.append(outcome.event); // hash-chained ENTRY_REGISTERED
}
const status = deriveEntryStatus(await log.read(0n, 1000), { intervalSeconds: 86_400, failuresToSuspend: 3 }, now());
// 'verified' | 'stale' | 'failing' | 'suspended' | 'revoked' | 'expired' — derived from history, never a flagverifyEventChain(events)recomputes every digest and link offline;buildMerkleCheckpoint(events)is the deterministic root aCheckpointHookanchors externally.- A receipt is evidence, never authority: an admitted entry grants nothing (ADR-0020/0056).
