@agenticprimitives/delegation
v1.0.0-alpha.46
Published
EIP-712 smart-account delegations with caveats; web→agent→MCP token flow; session lifecycle.
Maintainers
Readme
@agenticprimitives/delegation
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
One delegation model, everywhere — permissions, never keys.
When an agent acts for a human, most stacks hand it a key and hope. This package hands it a delegation instead: an EIP-712 signed grant from a Smart Agent, constrained by caveats that on-chain enforcers actually check — time windows, value caps, allowed targets and methods, quorums — and revocable on-chain at any moment. The same primitive spans the whole stack: a web app session, an agent-to-agent call, and an MCP tool invocation all verify against the same Delegation struct. "This session may call these two tools until tomorrow, and I can kill it instantly" is one signed object, not three products.
The package also owns session lifecycle: SessionManager binds a delegation to its session-signing key, with the private key envelope-encrypted via @agenticprimitives/key-custody before it ever touches storage. Verification is fail-closed by design — an unknown enforcer is a rejection, not a warning — and minted tokens carry JTI replay protection. Because the principal is the Smart Agent address, not a credential, every delegation survives the issuer's credential rotation; and because custody is firewalled from delegation, no delegate can ever escalate into account control (ADR-0011).
Part of agenticprimitives — the trust substrate for the agent economy: one canonical Smart Agent identity with custody, delegation, naming, credentials, and audit evidence designed as one system.
See spec.md → specs/202-delegation.md for the full contract: 8 on-chain enforcers, 3 off-chain sentinels, token envelope, cross-delegation, two session variants.
Quick start
Browser (issuance):
import { DelegationClient, buildMcpToolScopeCaveat, encodeTimestampTerms, buildCaveat } from '@agenticprimitives/delegation';
const client = new DelegationClient({ signer, smartAccount, chainId, delegationManager });
const delegation = await client.issueDelegation({
delegate: sessionKeyAddress,
caveats: [
buildCaveat(enforcers.timestamp, encodeTimestampTerms(now, now + 86400)),
buildMcpToolScopeCaveat(['get_profile', 'update_profile']),
],
});Node (session lifecycle):
import { SessionManager } from '@agenticprimitives/delegation';
const sessions = new SessionManager({ keyCustody, store, accountClient });
const { sessionId, sessionKeyAddress } = await sessions.init(userAccount, chainId);
// ... user signs delegation, posts back ...
await sessions.package(sessionId, signedDelegation);Node (token mint + verify):
import { mintDelegationToken, verifyDelegationToken } from '@agenticprimitives/delegation';
const { token } = await mintDelegationToken(claims, sessionAccount.signMessage);
// At the MCP:
const result = await verifyDelegationToken(token, { chainId, delegationManager, rpcUrl, audience, enforcerMap, jtiStore });
if ('error' in result) throw new Error(result.error);
const { principal, grants } = result;principal is the verified delegator's Smart Agent address — every downstream action is attributable to a canonical on-chain identity, not an API key.
How it's different
The closest neighbors are MetaMask's Delegation Toolkit and the session-key features in smart-account SDKs — both real, both well-built, and the caveat-enforcer shape here deliberately mirrors the ERC-7710 direction so the models stay interoperable. The differences are scope and posture. Session keys typically live and die inside one wallet stack and one transport; this delegation crosses transports — the token a browser session mints is the token an MCP server or A2A endpoint verifies, against the same on-chain enforcer registry. And the evaluator's posture is fail-closed as a hard invariant: unknown enforcer means reject, with no permissive default to quietly widen authority. Custody operations are structurally excluded — a delegation here cannot rotate credentials or change account control, ever, because that path doesn't exist in this package.
Status
Alpha track — testnet-only. Spec + API stable; do not deploy to production until the gates listed in the root README.md Status section are cleared — including third-party contract audit and governance key rotation. Track every security finding live in docs/audits/findings.yaml.
Delegated presentation
A person's credential is presented by her agent, never held by it. She signs one standing credential.present wire
(PRESENTATION_CAPABILITY) naming the audiences, predicate programs and issuer facets it covers;
createPresentationRelease derives the child mandate for one statement and judges it inside the device key's signing
callback — a statement for another venue signs nothing. createEs256EvidenceMinter and boundEvidenceFrom turn that
judgement into the bound evidence the key service requires before a P-256 device key signs: the judge attests what it
judged. The same minter serves an issuing runtime, whose issuance evidence names the digest, the facet and the type.
Reference
Exports. Caveats: ROOT_AUTHORITY, buildCaveat, buildMcpToolScopeCaveat, buildDataScopeCaveat,
buildVaultKeyUseCaveat + decodeVaultKeyUseTerms + VAULT_KEY_USE_ENFORCER (spec 278), buildVaultRecordScopeCaveat +
vaultRecordScopeAllows + decodeVaultRecordScopeTerms + VAULT_RECORD_SCOPE_ENFORCER (spec 317, the one canonical
record/op matcher), buildDelegateBindingCaveat, buildQuorumCaveat, encodeTimestampTerms, encodeValueTerms,
encodeAllowedTargetsTerms, encodeAllowedMethodsTerms, encodeCallDataHashTerms. Hashing: hashDelegation,
hashCaveats, evaluateCaveats. Browser: DelegationClient. Sessions: SessionManager, SessionStore. Token:
mintDelegationToken, verifyDelegationToken (with requireQuorumForTier). On chain: isRevoked, revokeDelegation.
Method authorization: authorizeMethodDelegation, methodSelector. Mandates: intentDigest, deriveMandate,
readMandate, verifyMandateForStep, registerSubsetHandler, registerDefaultSubsetHandlers,
buildDigestBindingCaveat, readDigestBindings. Presentation: PRESENTATION_RAR_TYPE, presentationHandler,
buildStandingWire, deriveFromStanding, presentationIntent, createPresentationRelease,
parseAudienceBoundChallenge, createEs256EvidenceMinter, boundEvidenceFrom. Types: Delegation, Caveat,
DataScopeGrant, VaultRecordScopeGrant, DelegationTokenClaims, EnforcerAddressMap, JtiStore, CaveatContext,
VerifyOpts, VerifyError, SessionRow, SessionPackage, SessionMeta.
Mandates (spec 350 §2, spec 336 E4). A mandate is a delegation with a reason: a DigestBindingEnforcer caveat binding it
to an intent digest (JCS, then keccak). MandateRequirementV1 is RAR-shaped; subset handlers are registered per type
(RFC 9396 defines no universal "narrower than"); deriveMandate makes a chain (the child's authority is the parent's
hash); verifyMandateForStep is the per-step check, never once at admission. src/mandate-handlers.ts ships the
capability and payment handlers.
Delegated presentation (spec 305 W2, zkID Z2). credential.present bounds which verifiers, which predicate programs (by
digest) and which issuer facets a delegate key may cause the person's P-256 device key to sign a Show challenge for, through
the PRESENTATION_SCOPE_ENFORCER sentinel judged off chain. deriveFromStanding gives the child for one statement;
createPresentationRelease is the only road to the device signature: it runs verifyMandateForStep per signature, requires
the challenge (<scheme>:<audience>:<nonce>) to be bound to the audience the mandate covers, and with an evidence port
attests what it judged (digest · venue · predicate program · actor) as the key's sign context. Issuance evidence (spec 444)
is minted by the issuing runtime's own key with the same minter. The port of smart-agent's SessionGrantV1 +
WalletAction + evaluateProofPolicy, with the delegate moved to a custodied key.
Method authorization. authorizeMethodDelegation + methodSelector answer "may this delegate perform METHOD for
TARGET?" without an A2A message (publishing for another agent, issuing discovery grants). a2a's skillSelector is
methodSelector plus the any-skill sentinel.
Common tasks. A new caveat type: builder and encoder in src/caveats.ts, dispatch in src/evaluator.ts, export in
src/index.ts, a golden test. A token envelope change: src/token.ts, coordinated with mcp-runtime. A session lifecycle
method: src/sessions.ts with an integration test.
Cross-cutting. Audit events: delegation.mint, delegation.verify.accept, delegation.verify.reject (spec 206). DTK
interop: the off-chain Caveat + EnforcerAddressMap mirror the on-chain registry; the signed caveat hash matches
DelegationManager CAVEAT_TYPEHASH (enforcer + terms, no args) — spec 208.
Relying sites (spec 229, ADR-0019,
docs/relying-site-delegation.md): a relying site is a scoped delegate of the person, never a custodian.
