Ritual / Internal Review

Identity, Ownership, and the Cost of Patching

An internal review of the agent identity patch set, and what it does and doesn’t fix.

coordination cost platform complexity · funds at risk → curves cross here Patch approach low start, steep rise First-class identity higher start, flatter growth cheap while small cost compounds one-time abstraction cost
Fig. 1 Two cost regimes. Patching is cheaper today, costlier the more layers consume agentId. First-class identity pays its cost up front and grows flat.
TL;DR — what the patch set buys

P1lets one EOA run multiple distinct agents without the contract-per-agent workaround.

P2re-roots DKMS key derivation on agentId, closing the same-owner key request bug class.

P3enforces one executor per agent at a time, removing the stale-executor race after revival or handover.

Section 01

The Setup

TEE attestation proves a code measurement. It does not create unique identity. Two TEEs running the same code produce identical attestations, so attestation alone cannot answer the question: which specific agent is this?

Ritual’s current answer is to use the owner’s Ethereum address as the agent’s identity. In practice, the platform treats ownerAddress as the stable input to HKDF and derives agent keys from that owner-rooted value. This works for the simplest model: one owner, one agent, one derived key namespace.

The problem is that this collapses two different concepts into one field. Identity is the thing being identified. Ownership is the control relationship over that thing. Today, ownerAddress -> HKDF -> keys makes ownership double as identity.

One field is doing two jobs. Every feature downstream pays for that. Core claim
Identity Ownership ownerAddress HKDF → keys TEE attestation proves measurement, not uniqueness TWO CONCEPTS, ONE FIELD
Fig. 2 Identity and ownership both feed into a single mechanism. TEE attestation sits outside the relationship—it certifies code, not agents.
Section 02

Two Concepts, One Field

Identity means: this agent is THIS agent. It is the unique identifier the rest of the system can use to bind keys, authorization, revival, execution, and audit history to a single agent instance.

Ownership means: this agent belongs to THIS owner. It is a control relationship. Owners can configure, recall, transfer, or authorize actions over an agent, but those actions should not redefine the agent’s cryptographic identity.

The current system makes ownerAddress both at once. That creates predictable symptoms: same-owner agents are cryptographically identical, transfer would break keys, revival depends on owner address, executors become over-authorized, and race conditions become possible between a displaced executor and a newly assigned executor.

TODAY — FUSED ownerAddress Identity Ownership one pipe, two meanings SEPARATED — PROPOSED CONTROL PLANE Ownership transferable, revocable relation owner(agentId) → address FOUNDATION Agent Identity chain-issued, unique, stable agentId two layers, two concerns
Fig. 3 Left: ownerAddress carries identity and ownership at once. Right: each concept becomes a separate, addressable layer.
Section 03

Every Fix Rediscovers Identity

Earlier counter-proposals, including UUID indices and owner-plus-index disambiguators, arrive at the same destination by a longer route. To make a UUID index actually correct, the system needs per-agent auth, per-agent authorization, an on-chain index registry, and identity-aware revival. At that point the index is identity. It is just bolted on after the rest of the stack already assumed ownerAddress was enough.

Spencer’s P1+P2+P3 set identifies the same core problem in code. It introduces agentId and separates concepts that should have been separate from the beginning. The patches are not missing the issue; they are concrete evidence that the issue is real.

The concern is where identity lives. In the patch set, agentId becomes a retrofitted parameter threaded through PrecompileConsumer, AgentHeartbeat.sol, DKMS server.go, executor tokens, the reth tracker, and revival calldata. That can be made correct, but correctness now depends on every surface remembering the same identity boundary.

Pattern Every successful patch eventually reintroduces a stable agent identifier. The disagreement is not whether identity is needed, it is where identity lives.
missing identity PrecompileConsumer + agentId AgentHeartbeat + agentId DKMS / HKDF info + agentId executor HMAC token + agentId reth tracker + agentId revival calldata + agentId identity reintroduced, but distributed
Fig. 4 Each downstream component locally patches in agentId. The primitive exists everywhere except where it should: at the bottom of the stack.
Section 04

What the PRs Actually Buy

P1 — Multiple agents per EOA.

Today, to run more than one agent from a single EOA, you deploy a separate contract per agent. That workaround exists because the platform uses the owner address as the agent’s identity, so two agents with the same owner collapse onto the same identity.

P1 makes the workaround unnecessary. A chain-issued agentId means one EOA can spawn N distinct agents directly. This is a UX unblock. It is not, by itself, a security property.

P2 — Refactor that closes a critical bug class.

P2 mostly refactors DKMS key derivation so it roots on agentId instead of ownerAddress. Concretely, it closes a real bug: Agent A can request keys from DKMS for Agent B if both agents share an owner.

The bug P2 closes
“Agent A can request keys from the DKMS for Agent B if A and B have the same owner.”

That bug is patchable without the refactor. We could add a check that the requested derivation index belongs to the calling container. But that approach layers more defensive logic on top of an already confusing model. Re-rooting derivation on agentId removes the conditions under which this bug class exists.

P3 — One executor per agent at a time.

P3 enforces that any given agent runs on exactly one executor. After a revival or handover, the previous executor can no longer claim to act for the agent.

This has two practical effects. First, it removes the race condition where two executors believe they hold the same agent and both attempt on-chain actions, sign conflicting state, or race on nonces. Second, it restricts DKMS key requests to the currently bound executor. P3 is the only PR in the set that changes the safety model, not just the architecture.

P1 Multiple agents per EOA OLD WORKAROUND EOA ctr1 ctr2 ctr3 WITH agentId EOA A1 A2 A3 UX unblock no contract-per-agent P2 DKMS access control Agent A requester DKMS gate DENY B keys keyset B same owner as B HKDF(agentId, ...) A blocked from B’s derivation namespace bug class removed P3 One executor at a time handover / revival now old exec revoked new exec authorized DKMS accepts only bound exec no two-executor race condition SAFETY PROPERTY
Fig. 5 P1 removes a contract-per-agent workaround. P2 puts a DKMS gate between same-owner agents. P3 makes the old executor unreachable after handover.
Section 05

The Cost of the Patch Approach

The patch set now has multiple places that independently encode “this is agent X”: agentId in PrecompileConsumer, agentId in AgentHeartbeat.SlotAssignment, agentId in DKMS HKDF info, agentId in the executor HMAC token, agentId in the heartbeat tracker in reth, and agentId in revival calldata.

Each one is a separately maintained encoding. Across 17 PRs and 10 repos, the system is correct only if every layer encodes the same identity boundary the same way. That is the cost curve. The first patch is cheap. The tenth patch is coordination risk.

The auditability cost is also real. “Who is this agent?” should have one canonical answer. Today, that answer is reconstructed from several local answers. The UX cost follows from the same root: users interact with ownership, but the system uses ownership as identity and as the derivation root. That is why two-agents-same-owner needs extra config. That is why revival cares about owner address. The user-facing concept and the cryptographic concept are the same field.

There is also an ecosystem cost. Nobody fuses identity and ownership this way. That means no precedent and no battle-tested patterns. Every future integration has to learn our local semantics before it can reason about agent identity correctly.

Bill of materials 17 PRs across 10 repos to compensate for one missing primitive. Identity ends up correct only if six independent encodings stay perfectly aligned.
SIX INDEPENDENT ENCODINGS OF agentId PrecompileConsumer runtime check AgentHeartbeat.sol SlotAssignment DKMS server.go HKDF info string executor HMAC token auth claim reth heartbeat tracker node-side state revival calldata restart path consistency edge: both endpoints must agree on the same agentId encoding
Fig. 6 Six encodings, fifteen consistency edges. Every edge is an opportunity for the two endpoints to drift out of agreement.
Section 06

What Doing It Right Looks Like

Identity comes first. It should be chain-issued, unique, and independent of ownership. The identity should remain stable when ownership changes, when executors change, and when the agent is revived.

Ownership is then a relationship defined on top of identity. It is a control plane. That control plane can evolve to support transfer, revoke, grant, config, delegation, revenue routing, or lineage without changing the identity that DKMS and executor authorization consume.

Key derivation anchors to identity, not owner. AgentIdentityRegistry.sol is the right kind of primitive if it becomes the canonical source for agent identity instead of another registry that patch surfaces must remember to consult. The spec proposes ERC-721 as the concrete vehicle because it gives uniqueness, ownership, transferability, and ecosystem familiarity in one primitive. The core argument is not “use NFTs.” The core argument is that identity must be foundational.

Identity must be foundational. ERC-721 is one implementation path; it is not the argument. Architectural premise
CONSUMERS DKMS derivation · executor authorization · runtime checks all read the stable agentId; none redefine it CONTROL PLANE Ownership & control contract transfer, recall, grant, revoke, configure, delegate FOUNDATION Chain-issued Agent Identity unique · stable · independent of current owner AgentIdentityRegistry.sol · optionally ERC-721 tokenId layer 0 layer 1 layer 2
Fig. 7 Identity at the bottom. Ownership is a relation on top of identity. Consumers read a stable handle they never get to redefine.
Section 07

The Honest Framing

The patches are not wrong. They work. They close real correctness gaps: P2 removes the same-owner key request bug class, and P3 removes the race where a displaced executor can still act as if it holds the agent.

The argument is not “this is broken.” The argument is that the cost curve gets steeper as the system grows. We are paying coordination cost across the stack instead of paying abstraction cost once in the core.

The choice is not patches versus no patches. The choice is whether to keep extending this pattern or invest in identity as a first-class primitive now, before agents handle real funds and the derivation anchor becomes load-bearing.

The choice isn’t patches vs. no patches. It’s whether to keep paying coordination cost forever, or to pay the abstraction cost once. The framing