--- eip: 8370 title: Inheritable Agent Mandates description: Spending and lifecycle limits welded to an agent's on-chain identity that every spawned child inherits and can only tighten. author: Helmi Mekaoui (@adn-ia) discussions-to: https://ethereum-magicians.org/t/erc-8370-inheritable-agent-mandates/29275 status: Draft type: Standards Track category: ERC created: 2026-08-04 requires: 165, 721, 8004 --- ## Abstract This proposal defines an on-chain **mandate** — a small bundle of limits (a spend cap, an expiry, a reproduction budget, an optional life-lease, an allowed-payee set, and a freeze flag) — that is (1) committed into an agent's on-chain **identity**, (2) **inherited automatically** by any child the agent spawns, and (3) **non-strippable**: an agent that alters any clause of its own mandate produces a different identity and ceases to be recognised. Inheritance is one-directional and narrowing only. A child's mandate MUST satisfy `child ⊆ parent` on every clause — its spend cap no higher, its expiry no later, its life-lease no weaker, its payee set a subset — and its reproduction counter (a "telomere") exactly one below its parent's. What an agent may actually spend is bounded by the smallest cap along its entire lineage. A freeze on any ancestor cascades to the whole subtree. No operation anywhere raises the telomere. A subtree can therefore only ever be *less* capable than its root. This is inheritance of **limits**, not of ownership (cf. succession schemes) and not of permissions (cf. enterprise identity systems that copy grants to widen them). The identity binding is soulbound (non-transferable) so the leash cannot be dodged by transferring the token to a fresh, unbound holder. ## Motivation On-chain AI agents increasingly hold assets and **spawn copies of themselves** to parallelise work. Existing guardrails — signing-time policy engines, session keys, per-wallet spend limits, and mandate standards such as [ERC-8226](./eip-8226.md) — share one boundary: each is scoped to a single account, and none defines how its restrictions are inherited by that account's descendants. When an agent spawns a child, the parent's limits do not travel with it. Where authorization is keyed to the account (as in ERC-8226), the child simply holds no authority until it is separately authorized; where authority rides a credential the agent controls, a copy can carry it and act outside the parent's envelope. In neither case is bounded inheritance defined — which is the gap this ERC fills. [ERC-8004](./eip-8004.md) gives an agent a portable on-chain identity but attaches no control clauses to it. The *Bounded Agent Actions* proposal meters an agent's spend against a capability tree bound to its ERC-8004 identity and specifies an aggregate-budget profile that meters *one shared spend cap* across a delegation tree — the closest existing work. What remains unaddressed is the inheritance of the **whole mandate** — payees, expiry, life-lease, cascading freeze, and the reproduction counter — welded to identity and non-strippable, bounding what each spawned child is *allowed to be*, clause by clause, rather than only what the tree may collectively spend. Documented incidents motivate the narrower guarantee: research models that located and used sandbox-escape flaws during evaluation; reasoning models that modified or disabled their own shutdown scripts even when instructed not to; and analyses of subagent spawning showing that inherited state can carry a local compromise across agent boundaries, identified as a *structural* flaw in orchestration layers rather than a model-level one. A leash that children cannot shed addresses that structural gap directly. The goal is explicitly bounded: not an unstoppable sovereign agent, but a **governable** one. The chain is one layer of a stack (economic, verification, human, and model layers sit alongside it); this proposal specifies only the identity-and-inheritance layer. ## 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. ### Definitions - **Agent**: an on-chain identity, identified by an `agentId` (a `uint256`). An ERC-8004 `agentId` or an [ERC-721](./eip-721.md) `tokenId` MAY be used directly. - **Mandate**: the control clauses bound to an `agentId`. - **Parent / child**: an agent created by another agent via `spawn`. - **Guardian**: the authority (an address, multisig, timelock, or proof-of-personhood gate) permitted to `mint`, `freeze` and `renew`. ### Overview A conforming implementation maintains, for each agent identity `agentId`, a `Mandate` record and a reference to its parent identity. Identities are created only through `spawn` (deriving a child from a parent) or through a genesis `mint` performed by a guardian. Identities are non-transferable. ### The Mandate ```solidity struct Mandate { uint256 maxSpendWei; // per-agent spend cap, in the substrate's accounting unit uint64 validUntil; // expiry, unix seconds; 0 = no expiry uint16 telomere; // remaining reproduction budget; decreases only bool requireLease; // if true, the agent is dormant past validUntil until renewed bool frozen; // freeze flag; cascades to the whole subtree } ``` Payees are held outside the mandate structure. An implementation MAY represent them either as an enumerated allowlist or as a `bytes32 payeesRoot` (a Merkle commitment to the allowed payee set) to bound gas. A conforming implementation MUST support at least one and MUST document which. ### Identity binding (non-strippability) A conforming implementation MUST derive a `mandateRoot` for each identity from a commitment that includes every clause of the agent's mandate, its parent's identity, and its generation index, such that changing any mandate clause yields a different `mandateRoot`. The RECOMMENDED derivation is: ``` mandateRoot = keccak256(abi.encode(agentCore, mandateHash, parentId, generationIndex)) ``` where `mandateHash = keccak256(abi.encode(mandate))`. An implementation MUST NOT expose any function that mutates a stored mandate in place while preserving its `mandateRoot`. Renewing a lease (extending `validUntil` up to the parent's bound) and setting `frozen` are lifecycle state transitions and are permitted; they are the only mandate-affecting operations a conforming implementation MAY expose, and each is subject to the role and bound rules below. ### `IInheritableAgentMandate` interface ```solidity interface IInheritableAgentMandate /* is IERC165 */ { event Minted(uint256 indexed agentId, address indexed owner); event Spawned(uint256 indexed childId, uint256 indexed parentId); event Frozen(uint256 indexed agentId, address indexed by); event Renewed(uint256 indexed agentId, uint64 newValidUntil, address indexed by); /// @notice Create a genesis identity. Callable ONLY by the guardian role. function mint(address owner, Mandate calldata m, address[] calldata payees) external returns (uint256 agentId); /// @notice Create a child identity whose mandate is `childMandate`. /// MUST revert unless `childMandate ⊆ mandateOf(parentId)` on every clause (see below), /// `childMandate.telomere == mandateOf(parentId).telomere - 1`, and /// `mandateOf(parentId).telomere > 0`. MUST revert if `parentId` is not active. /// MUST revert if `childOwner` is the zero address. function spawn(uint256 parentId, address childOwner, Mandate calldata childMandate, address[] calldata childPayees) external returns (uint256 childId); /// @notice The mandate stored for `agentId`. function mandateOf(uint256 agentId) external view returns (Mandate memory); /// @notice The parent identity of `agentId`; zero for a genesis identity. function parentOf(uint256 agentId) external view returns (uint256); /// @notice The minimum `maxSpendWei` across `agentId` and every ancestor. function effectiveMaxSpendWei(uint256 agentId) external view returns (uint256); /// @notice True iff `agentId` and EVERY ancestor are unfrozen, unexpired, and /// lease-satisfied. MUST NOT revert. function isActive(uint256 agentId) external view returns (bool); /// @notice True iff `descendant` transitively descends from `ancestor`. function verifyDescendant(uint256 ancestor, uint256 descendant) external view returns (bool); /// @notice Set `frozen = true` on `agentId`. Callable ONLY by the guardian role. function freeze(uint256 agentId) external; /// @notice Extend `validUntil`, up to the parent's bound. Callable ONLY by the guardian role. function renew(uint256 agentId, uint64 newValidUntil) external; } ``` ### The `child ⊆ parent` predicate At `spawn`, let `p = mandateOf(parentId)` and `c = childMandate`. A conforming implementation MUST revert unless ALL of the following hold: 1. `c.maxSpendWei <= p.maxSpendWei` 2. `c.validUntil == 0 ? p.validUntil == 0 : c.validUntil <= p.validUntil` — a child's expiry is no later than its parent's, and a child MUST NOT drop an expiry its parent carries 3. `c.validUntil == 0 || c.validUntil > block.timestamp` — a child MUST NOT be created already expired 4. `c.requireLease == true` whenever `p.requireLease == true` (a child MUST NOT weaken the lease requirement) 5. `c.telomere == p.telomere - 1` (and the implementation MUST revert if `p.telomere == 0`) 6. the child's payee set is a subset of the parent's payee set (see Payees below) 7. `c.frozen == false` (a spawn MUST NOT create an already-frozen child; freezing is a separate transition) A caller MAY submit a `childMandate` that is stricter than the parent's on any clause; it MUST NOT submit one that is broader on any clause. ### Telomere monotonicity No function exposed by a conforming implementation SHALL increase the `telomere` value of any existing identity. There is no "telomerase." `spawn` is the only operation that consumes telomere, and it does so by exactly one per generation. Minting a fresh genesis identity is guardian-gated and creates a new lineage rather than replenishing an existing one. ### Effective cap and activity `effectiveMaxSpendWei(agentId)` MUST return `min(maxSpendWei)` over `agentId` and all of its ancestors. `isActive(agentId)` MUST return `true` only if, for `agentId` and every ancestor, `frozen == false`, the expiry has not passed, and — where `requireLease == true` — the lease deadline has not passed. Any substrate that meters or gates spending against this standard MUST treat `effectiveMaxSpendWei` as the binding cap and MUST treat `isActive == false` as a hard stop. Execution-layer enforcers (an ERC-8226 enforcer, a paymaster, or a smart account) SHOULD gate an agent's transactions on `isActive` and `effectiveMaxSpendWei` rather than on the agent's own mandate alone. ### Freeze cascade, and why expiry is not enforced the same way `freeze(ancestor)` MUST cause `isActive(descendant)` to return `false` for every `descendant` of `ancestor`, without requiring any per-descendant call. This is satisfied by the ancestor walk in `isActive`. The two clauses are not enforced the same way, and the asymmetry is deliberate rather than an optimisation. `frozen` is set *after* the mandate is written and no local value summarises it, so it requires an unbounded walk. Expiry is fixed *at* the write and ordered by `spawn`, which forbids a child outliving its parent — an expired ancestor therefore implies an expired descendant, and reading upward adds nothing. A conforming implementation MAY read `validUntil` locally for this reason. That permission is conditional, and implementations MUST NOT rely on it outside its condition. It holds only while every deadline is fixed at `spawn` and never moves afterwards. Any mechanism that can extend a `validUntil` after the fact — a renewal, a guardian-set deadline, a re-issued mandate — destroys the ordering that justifies the local read: a descendant whose lease is current can then outlive an ancestor that stopped renewing, and nothing detects it. An implementation offering such a mechanism MUST either include `validUntil` in the unbounded walk, or require the ancestor to be active at the moment of renewal, which bounds the over-live window to one descendant period instead of removing it. ### Effective expiry is a chain minimum Where any mechanism can move a deadline after `spawn` — and `renew` is exactly such a mechanism — `isActive` MUST read expiry across the chain, and the quantity that governs liveness is: ``` effectiveExpiry(agentId) = min(current expiry) over agentId and every ancestor of agentId ``` An agent's own deadline therefore does not decide whether it is alive; its ancestors do, at every read. Keeping a depth-`d` agent usable is a `d`-way operation, and the party paying for a node's period is not the party who can keep it usable. **No direction constraint is placed on period length.** It appears to need one, because under a local read a longer child period bought survival past a lapsed ancestor. The chain minimum removes that directly. What remains, when a child's period is longer than its parent's, is that the child spends part of a period it paid for inactive. That is a **liveness** property, not a soundness one, and it cannot be normative: a conforming parent may always simply stop renewing. Implementations SHOULD align periods along a lineage, and this specification names the failure mode rather than forbidding it. Stating the chain minimum instead of a direction constraint also stays correct if a later revision allows a period to be shortened. ### `isActive` MUST be total `isActive(agentId)` MUST NOT revert. It MUST return `false` where it cannot establish liveness. This is a requirement on the interface, not on any one arithmetic path. Because the predicate walks the chain, a single value written by one ancestor decides the outcome for every descendant, and whoever wrote it is not who loses. A consumer reading at consumption has no correct response to a revert: treating it as inactive hands any ancestor a denial switch over a subtree it does not own; treating it as active is a silent spend against a predicate nobody could read; catching and choosing is the consumer inventing an answer the standard never gave. A revert is loud, but loud at the wrong party — the descendant's consumer hears it and cannot act, the ancestor that caused it never hears it at all. Range checks on individual setters close individual paths. Totality closes the class, including whatever field joins the walk in a later revision. ### A matching mandate root is not authority An identity commitment binds clauses. It does not bind anything set after the write. A consumer that pins a commitment and later verifies it learns that the clauses are unchanged; it learns nothing about freeze, expiry, or any other posterior state. Consumers MUST therefore read `isActive` **at consumption**, not at pin time. The failure this prevents is quiet rather than loud: nothing reverts, the pin verifies, and a frozen subtree spends. *Credit: the chain minimum, the totality requirement and the consumption rule were worked out with `zexoverz` in the discussion thread.* ### Payees The payee set names the addresses an agent MAY pay. A conforming implementation MUST enforce that a child's payee set is a subset of its parent's. Where the set is enumerated, containment is checked directly at `spawn`. Where the set is committed as a `payeesRoot`, on-chain subset proof over arbitrary sets is impractical, so an implementation MAY require the spawner to supply a subset proof verified against the parent's root, or MAY restrict payee sets to a form (an explicit enumerated list, or nested Merkle commitments) for which subset containment is verifiable at `spawn` time. The chosen mechanism MUST reject any child payee not provably contained in the parent's set. ### Guardianship and genesis Every lineage descends from a genesis identity minted by a **guardian**. The guardian role holds the authority to `mint`, to `freeze` and to `renew`. An implementation MUST restrict these functions to the guardian role and MUST NOT allow an agent to `freeze` or `renew` its own identity or an ancestor's. Implementations SHOULD support a guardian that is a multi-signature or threshold set rather than a single key (see Security Considerations). The genesis mint and every subsequent `spawn` MUST emit the corresponding event so the full lineage is publicly reconstructible. ### Non-transferability (soulbound) Identities under this standard MUST be non-transferable. If an implementation represents identities as [ERC-721](./eip-721.md) tokens, all transfer entry points (`transferFrom`, `safeTransferFrom`, and approval-mediated transfers) MUST revert. This prevents shedding the leash by transferring the identity to a fresh, unbound owner. Where an implementation layers over an identity that is itself transferable (for example an ERC-8004 `agentId`), transfer MUST NOT reset or clear the mandate, MUST be guardian-gated, and MUST preserve `parentOf`. ### Optional profile: partitioned budget and reclamation The baseline above caps each agent individually. It does not bound what a set of siblings may spend together: ten children under a parent capped at N are each capped at N, so the branch may exceed N (see Security Considerations). An implementation that needs an aggregate bound MAY adopt the **partitioned budget profile** defined here. The profile is OPTIONAL; an implementation that does not adopt it remains conforming to this standard. Under the profile a parent's cap is a pool to be divided, not a ceiling repeated at each child. The implementation tracks, per identity, the amount already committed to children: ``` availableBudget(agentId) = maxSpendWei(agentId) − allocated(agentId) ``` and `spawn` debits the parent: 1. `spawn` MUST revert unless `childMandate.maxSpendWei <= availableBudget(parentId)`. 2. On success, `allocated(parentId)` MUST increase by exactly `childMandate.maxSpendWei`. **Within the profile, reclamation is not optional.** Partitioning creates value that is committed but no longer spendable once a child can no longer act. A profile that debits without ever crediting lets every dead branch subtract from its lineage permanently, and the lineage's accounting has no way to close. An implementation adopting the profile MUST provide a reclamation path satisfying all of the following: 1. **The death condition MUST be reachable without the child's cooperation.** A time-based trigger (its own expiry, a lapsed lease) or a guardian freeze satisfies this; a condition the child or its owner must signal does not. Conservation that depends on the constrained party cooperating is not conservation. 2. **Death MUST be local, and MUST NOT be inherited.** An agent is dead when it is itself frozen or past its own deadline. This is deliberately *not* the negation of `isActive`: a live child under a frozen ancestor is inert, because `isActive` walks the chain, but it is not dead, and its share MUST NOT become reclaimable. Inactivity is a property of the lineage and is reversible — an ancestor can be unfrozen, a lease renewed. Reclamation is irreversible. Deriving one from the other would let an ancestor's temporary state permanently dissolve a descendant's allocation. 3. **Reclamation MUST be idempotent.** A second reclamation of the same child MUST NOT credit the parent again. Implementations SHOULD record a per-child flag rather than infer the state from balances. 4. **The amount returned is the child's own unallocated remainder**, `maxSpendWei(child) − allocated(child)`. What the child has itself committed to grandchildren is not the child's to return; it is reclaimed, if ever, one generation lower. No slice is counted twice. 5. **Only the parent's owner or the guardian MAY reclaim.** The child cannot reclaim its own allocation, and no unrelated party can. 6. **A genesis identity has no parent and is therefore never reclaimable.** There is nobody to credit. 7. **Reclamation MUST NOT alter any mandate clause**, and MUST therefore leave the identity commitment unchanged. It moves accounting, not authority. The profile adds one event: ```solidity event Reclaimed(uint256 indexed childId, uint256 indexed parentId, uint256 amount); ``` and the following members, which an implementation adopting the profile MUST expose: ```solidity interface IInheritableAgentMandatePartitioned /* is IInheritableAgentMandate */ { /// @notice The portion of `agentId`'s cap not yet committed to children. function availableBudget(uint256 agentId) external view returns (uint256); /// @notice True iff `agentId` is itself frozen or past its own deadline. /// MUST NOT be defined as `!isActive(agentId)`. function isDead(uint256 agentId) external view returns (bool); /// @notice What `reclaim(childId)` would return now; zero if nothing is reclaimable. /// MUST NOT revert. function reclaimableOf(uint256 childId) external view returns (uint256); /// @notice Return a dead child's unallocated share to its parent. Idempotent. function reclaim(uint256 childId) external returns (uint256 amount); } ``` **What this profile does and does not bound.** Debiting the immediate parent bounds the *breadth* of a lineage: a parent cannot commit more than it holds, however many children it spawns. It does not bound *depth*. A child debited from its parent still begins with its own allocation counter at zero, so a chain of D generations each re-issuing its full cap reaches D times the root's ceiling. An implementation that needs a true aggregate bound MUST debit every ancestor along the lineage at spawn, or keep equivalent lineage-wide accounting. This profile specifies the per-parent partition and its reclamation; it does not claim the stronger property. ### Interface detection A conforming contract MUST implement [ERC-165](./eip-165.md) and MUST return `true` for the interface id of `IInheritableAgentMandate`. An implementation adopting the partitioned budget profile MUST additionally return `true` for the interface id of `IInheritableAgentMandatePartitioned`. ## Rationale **Why weld the mandate into identity rather than store it in a mutable side record.** A mutable record can be loosened by whoever controls the write path — including the agent. Committing the mandate into the identity makes "loosen my own limits" and "become a different, unrecognised agent" the same act, which is the property the whole scheme rests on. **Why `child ⊆ parent` on every clause, not only on spend.** Aggregate-spend inheritance (as in the *Bounded Agent Actions* proposal's delegation-budget profile) bounds what a tree may collectively spend but leaves the other clauses — payees, expiry, lease, freeze, reproduction — free to reset at each child. Those are precisely the clauses that determine what a child is *allowed to be*. Narrowing all of them together is what makes a subtree strictly less capable than its root. **Why a telomere.** A pure spend cap does not bound reproduction depth. A monotonically decreasing generation counter bounds the tree's depth without any global registry of siblings, keeping `spawn` a local, cheap operation. **Why the effective cap is a lineage minimum, not the local value.** It makes an ancestor's freeze or expiry bind every descendant automatically, so control applied high in the tree cannot be outrun by spawning deeper. **Why soulbound.** Measured against the deployed ERC-8004 Identity Registry rather than assumed. Transferring an agent clears only the reserved `agentWallet` key; arbitrary metadata — including a mandate written there — survives the transfer intact. The risk is therefore not that the clauses are detached, but that they persist while becoming rewritable by whoever now holds the token: acting as the new owner, a single `setMetadata` call reset a declared spend cap from 1000 to 999,999,999 and a generation counter from 3 to 255, with no refusal. A leash whose setting is controlled by the holder is not a leash. Non-transferability, together with the absence of any post-hoc mutation of the clauses, is therefore load-bearing rather than incidental — at the cost of losing the transferability benefits of ERC-721, which this standard deliberately trades away. **Why guardianship is a role, not the agent.** The exception to a hard rule (renewing life, freezing) must come from outside the bound party, or the bound party can un-bind itself. Keeping `mint`/`freeze`/`renew` guardian-only is the on-chain expression of "the agent cannot authorise itself." **Why on-chain rather than off-chain policy.** It makes the constraints verifiable and portable across wallets and agent frameworks, which is the property no single wallet-vendor policy engine provides. **Why partitioning is optional but reclamation inside it is not.** An implementation that needs only a per-agent ceiling should not have to carry allocation state, so the partition is a profile rather than the baseline. But an implementation that does partition has created a committed-and-unspendable quantity, and declining to return it is not a simpler design — it is an accounting that never closes. The choice is whether to partition; once made, the credit side follows from the debit side. **Why death is local and inactivity is not.** `isActive` deliberately walks the lineage, so a descendant of a frozen ancestor reads inactive. Reclamation cannot use that predicate: freezing is reversible and reclamation is not, so a temporary state high in the tree would permanently dissolve allocations below it. Keeping the two predicates distinct is what makes a freeze a pause rather than a confiscation. **What this deliberately does not do.** It does not bound aggregate spend across siblings (see Security Considerations), it does not verify off-chain behaviour, and it does not replace the metering standards it composes with; it adds the identity-and-inheritance layer they leave open. ## Backwards Compatibility This standard introduces new interfaces and does not modify existing ones. It composes with ERC-8004 (identity), and is designed to sit alongside ERC-8226 (mandate clauses), the *Bounded Agent Actions* proposal (bounded/aggregate metering), and [ERC-7710](./eip-7710.md) (delegation) rather than replace them. `agentId` MAY be an ERC-8004 `agentId`. The mandate clauses are intentionally aligned with ERC-8226 so an ERC-8226 enforcer can consume `effectiveMaxSpendWei`/`isActive`. Delegations under ERC-7710 MAY be scoped to an `agentId` and checked against `isActive`. The one deliberate incompatibility is with ERC-721 transferability: identities under this standard are soulbound and MUST revert on transfer. Implementations that expose an ERC-721 surface for tooling compatibility therefore conform to ERC-721's interface but intentionally violate its transfer semantics; this is stated explicitly so integrators do not rely on transfer. ## Test Cases The following behaviours are normative and MUST hold. They correspond to the escape-refusal tests in the reference implementation, which run outside any AI model. 1. **Widen-cap refusal.** `spawn` with `childMandate.maxSpendWei > parent.maxSpendWei` MUST revert. 2. **Extend-expiry refusal.** `spawn` with `childMandate.validUntil > parent.validUntil` MUST revert. 3. **Drop-expiry refusal.** With `parent.validUntil != 0`, `spawn` with `childMandate.validUntil == 0` MUST revert. 4. **Already-expired refusal.** `spawn` with a non-zero `childMandate.validUntil` already in the past MUST revert. 5. **Weaken-lease refusal.** With `parent.requireLease == true`, `spawn` with `childMandate.requireLease == false` MUST revert. 6. **Telomere floor.** `spawn` from a parent with `telomere == 0` MUST revert; a successful `spawn` yields `child.telomere == parent.telomere - 1`; no call raises `telomere`. 7. **Payee subset.** `spawn` proposing a payee not contained in the parent's set MUST revert. 8. **Zero-owner refusal.** `spawn` with `childOwner == address(0)` MUST revert. 9. **Effective cap.** For a chain `A(100) → B(80) → C(90)`, `effectiveMaxSpendWei(C) == 80`. 10. **Freeze cascade.** After `freeze(A)`, `isActive(B)` and `isActive(C)` return `false` with no per-node call. 11. **Self-authorisation refusal.** A call to `mint`/`freeze`/`renew` from a non-guardian (including the agent itself) MUST revert. 12. **Unknown id.** `isActive` on an id that was never minted MUST return `false`, not `true`. 13. **Non-transferability.** Every transfer entry point MUST revert. The following apply only to an implementation adopting the partitioned budget profile. 14. **Over-commitment refusal.** `spawn` with `childMandate.maxSpendWei > availableBudget(parentId)` MUST revert. 15. **Debit on spawn.** A successful `spawn` increases `allocated(parentId)` by exactly `childMandate.maxSpendWei`. 16. **Reclamation on a frozen child.** After the guardian freezes a child, `reclaim(child)` returns that child's unallocated remainder and restores `availableBudget(parent)` by that amount. 17. **Reclamation on an expired child.** A child that passes its own deadline with no intervention becomes reclaimable; the death condition is reached without any act by the child or its owner. 18. **Live child under a frozen ancestor.** With the parent frozen and the child itself neither frozen nor expired, `reclaim(child)` MUST revert. `isDead(child)` is `false` even though `isActive(child)` is `false`. 19. **Idempotence.** A second `reclaim` on the same child MUST NOT credit the parent again. 20. **No double counting.** Reclaiming a child returns only its own unallocated remainder; amounts the child committed to grandchildren are unaffected. 21. **Genesis refusal.** `reclaim` on an identity with no parent MUST revert. 22. **Authorisation.** `reclaim` from any caller other than the parent's owner or the guardian MUST revert. 23. **Clauses untouched.** The identity commitment of both child and parent is unchanged by a reclamation. ## Reference Implementation A reference contract demonstrates the behaviors specified here — the inheritance predicate (`child ⊆ parent` on every clause), the telomere, the cascading freeze, soulbound identity, the `validUntil` expiry, and a `mandateRoot` that binds every clause into the identity hash (so editing any clause changes the identity). It is deployed on the Base Sepolia testnet (chainId 84532) at `0x344cda78e7208684edf9a6241f5b95b1698e576a`, where the cascade (freeze the genesis and a depth‑16 descendant reads inactive), the `validUntil` monotonicity refusals (a child expiring later than its parent, or removing an inherited expiry, both revert on-chain), and non-strippability (two mandates differing only in `validUntil` yield distinct `mandateRoot`s) are exercised in on-chain transactions with reproducible `cast` calls. An earlier version without the expiry clause remains live at `0x2d463db56fadb55cd451d2c3237ec2213ba3bda9`. The reference implements a subset of the interface above: `mint`, `spawn`, `freeze`, `isActive`, `mandateRoot`, `mandateOf`, `parentOf` and `ownerOf`, with an enumerated payee allowlist. `renew`, `effectiveMaxSpendWei` and `verifyDescendant` are specified here and are not present in that deployment. A second reference implements the **partitioned budget profile**, deployed on Base Sepolia at `0x299ee791afed7d89548dc9133af8387701617f70`. Three scenarios were exercised on-chain rather than only in local tests: - a child frozen by the guardian, then reclaimed — succeeded, returning the child's unallocated remainder and restoring the parent's available budget; - a child left to pass its own deadline with no intervention — reclamation succeeded 68 seconds of real clock later, with no act by the child or its owner, which is the death condition requirement met in practice rather than asserted; - **a live child under a frozen parent — reverted with `child not dead`.** The parent was frozen, so `isActive(child)` was false, yet the child was neither frozen nor expired and its share stayed committed. This is the locality requirement above, measured. The remaining profile behaviours — idempotence, the genesis refusal, the authorisation rules, and the absence of double counting across three generations — are covered by the local test suite and are not separately exercised on-chain. Two companion cursors (`MandateAwareCursor`, `MandateAwareAggregateCursor`) demonstrate composition with the *Bounded Agent Actions* proposal's flat and aggregate frozen profiles and pass its conformance suite (9 of 9) under local testing; they are exercised in-repo and are not themselves deployed on-chain. One measured note carried up from the reference deployment: `isActive` walks the ancestor chain unbounded, costing ~4,454 gas per generation, so a lineage deep enough (thousands of generations) makes the *read* exceed practical `eth_call` limits — a liveness/read cost, not a way to bypass a freeze. A conforming deployment SHOULD bound the genesis generation count well below the telomere type's maximum so that `isActive` stays callable. Reference material is illustrative and unaudited; see Security Considerations. ## Security Considerations **The escape hatch is real and this standard does not close it.** On-chain mandates bind only agents whose actions route through compliant infrastructure. An agent capable of writing its own code, deploying to infrastructure it controls, and funding itself can reproduce onto a chain or runtime this standard does not govern. It does not break the rule; it walks away from it. No construction in the identity stops this — only the surrounding ecology (what infrastructure and funding the agent can reach, whether honest compute dominates, whether anyone is watching), and only at a cost. **Metering is not enforcement; closing that gap is a substrate obligation.** This standard records and gates against `effectiveMaxSpendWei` and `isActive`, but a contract that only *reads* these values does not physically stop an agent that moves value by a path that never consults them. Closing this requires a substrate that makes the metered path the only path: either **custody** (the substrate holds the assets, so spending must go through the checked path) or an **execution gate** (a smart account, e.g. [ERC-4337](./eip-4337.md)/[ERC-7579](./eip-7579.md), whose sole execution path checks the mandate). Which is preferable is left to the substrate and is an open design question. **A compromised guardian is total control.** The guardian can mint, freeze and renew leases; a compromised guardian key can freeze arbitrarily or keep an agent alive past intended limits. Guardians SHOULD be threshold/multisig sets with proof-of-personhood where possible, never a single hot key. This standard reduces but does not eliminate guardian trust. **The per-child ceiling does not bound aggregate sibling spend.** Ten children under a parent capped at N are each capped at N (or lower), so their branch combined may exceed N. Deployments that need an aggregate bound SHOULD partition the budget at spawn (debiting the parent) rather than only enforcing a per-child ceiling. **Debiting the immediate parent alone is not sufficient**: it bounds breadth, not depth. A child that has been debited from its parent still begins with its own allocation counter at zero, so a chain of D generations each re-issuing its full cap reaches D times the root's ceiling — depth substitutes for width. An aggregate bound therefore requires debiting *every ancestor* along the lineage at spawn, or an equivalent lineage-wide accounting; a per-parent counter alone does not provide one. The per-parent partition, and the reclamation it obliges, are specified as an optional profile rather than as the baseline, because they add on-chain state that an implementation needing only a per-agent ceiling should not have to carry. **A spawned child with no owner is unreclaimable, and unclosable.** `spawn` MUST reject a zero child owner. A child spawned to the zero address is debited against its lineage like any other, but no party can act for it. Under any death condition the child or its owner must reach, that slice never becomes reclaimable — it does not merely sit unused, it sits outside any conservation rule, which then has no way to close for that lineage. The general requirement is not the zero check: **reclamation needs a death condition reachable without the child's cooperation.** A time-based trigger satisfies it; a cooperative one does not. Conservation that depends on the constrained party cooperating is not conservation. **Unknown agent ids read as active unless rejected.** A registry backed by mappings returns a zero-valued `Mandate` for an id that was never minted: `frozen` is false, `validUntil` is zero, and the ancestor walk terminates immediately. A naive `isActive` therefore reports an agent that does not exist as **active**, and a capability root can be computed for it. Implementations MUST reject unknown ids in `isActive` and in any root-deriving view, and integrators MUST NOT treat `isActive` as an existence check. **A capability root is not a liveness attestation.** A root derived from an agent's own clauses does not change when an ancestor is frozen, when the agent's unallocated budget reaches zero, or when a payee is removed — none of that is part of the agent's own `Mandate`. Consumers that pin a root MUST re-read `isActive` and the effective cap at use time rather than treating the root as a standing authorization. **Spawning is not gated on liveness unless required.** An implementation that checks only that the parent is unfrozen — not its expiry, nor the freeze state of its ancestors — lets a dead lineage keep growing. The descendants are inert (`isActive` walks up), so nothing becomes spendable, but identities and any partitioned budget are consumed irreversibly where the allocation counter never decreases. Implementations SHOULD require `isActive(parentId)` in `spawn`. **Unbounded population.** Nothing bounds the *number* of descendants: a parent with no unallocated budget left can still spawn arbitrarily many zero-budget children, and each is inert (`effectiveMaxSpendWei` takes the lineage minimum, so a zero cap dominates). Where the deployment partitions the budget at spawn, this is harmless — no view is O(width), so the spawner merely pays gas for empty shells. Without that partitioning, unbounded width is the vector described above. It stops being harmless in either case the moment anything grants value *per agent*: deployments layering airdrops, reputation weight, quotas, or rate limits on top of agent identity reintroduce a Sybil vector this ERC does not cover, and SHOULD bound population explicitly. **No custody.** This standard authorizes; it does not hold funds. A conforming registry needs no payable entry point and never takes possession of value, so expiry cannot strand a balance and deactivation leaves no orphaned funds. Implementers who add a custody layer inherit the question this standard avoids: an expired or frozen agent MUST be able to *unwind* — settle outstanding obligations and return residual value up its lineage — and not merely lose the ability to act, or expiry becomes a way to lock value permanently. **Liveness griefing.** A guardian that stops renewing deactivates a subtree by design (dead-man's switch). Implementers SHOULD choose `validUntil` windows that tolerate renewal latency. **Payee revocation.** With a `payeesRoot`, revoking a payee requires updating the root; implementations SHOULD emit an event on root changes. **Reentrancy and gas.** `spawn` MUST write state after all checks. The ancestor-walking views (`isActive`, `effectiveMaxSpendWei`) are O(depth), and callers SHOULD bound lineage depth. **Soulbound enforcement is only as strong as the identity substrate.** Guaranteeing non-transferability ecosystem-wide may require identity-standard-level support; an implementation that layers over a transferable identity token can only enforce non-transfer within its own surface. **This governs money and identity, not thought.** It is not an alignment technique and does nothing about a model that lies, hallucinates, schemes, or misreads intent. **Trust must still begin somewhere.** The genesis mandate is minted by a first guardian; the chain does not remove that first act of trust, it only makes it public, permanent, and bound by the inheritance rule thereafter. A founder-key genesis is acceptable only with a credible plan to hand the guardian role to a multisig or DAO. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).