---
eip: 8350
title: Agent Memory State Registry
description: Authorized commitments to private agent memory state transitions.
author: Everest An (@everest-an), XiaoHai (@XiaoHai67890), Luo (@lucas1968), Eric
discussions-to: https://ethereum-magicians.org/t/erc-8350-agent-memory-state-registry/29098
status: Draft
type: Standards Track
category: ERC
created: 2026-07-14
requires: 712, 1271, 7702
---

## Abstract

This ERC defines a minimal registry interface for committing authorized state
transitions of autonomous-agent memory without placing raw memory in calldata.
Each transition is represented by an `ExperienceDelta`, identified by an
[EIP-712](./eip-712.md) struct hash, and applied to a linear per-space state
machine. A memory space has a controller and a replaceable authorizer. Both
externally owned accounts and [ERC-1271](./eip-1271.md) contract accounts are
supported. Payload semantics, storage, memory taxonomies, inference proofs,
deletion attestations, and markets are intentionally outside the core interface.

## Motivation

Agent systems commonly keep long-lived memory in private databases while using
Ethereum for identity, payment, or execution. Existing applications can publish
an opaque content hash, but a hash alone does not establish:

1. which state it advances;
2. whether the transition is the unique successor of the current state;
3. who controls the namespace and who may authorize updates;
4. whether all externally meaningful references were signed; or
5. whether independent implementations derive the same transition identifier.

Publishing raw prompts, embeddings, preferences, policies, or latent state is
incompatible with privacy and is often uneconomical. This ERC therefore commits
only to a private delta, optional provenance, an interpretation profile, and an
optional private locator. The registry verifies authorization and state-machine
continuity while remaining agnostic to the underlying memory engine.

Agent operators, wallet and application developers, auditors, and counterparties
need a shared way to refer to an agent's memory history across implementations
without disclosing the memory itself. They can verify that a declared state
advanced through an authorized, gapless sequence while prompts, embeddings,
policies, and other private witness data remain off-chain.

Example uses include carrying a continuity checkpoint when an agent moves between
providers, selectively opening evidence to an auditor against a committed
transition, and detecting rollback or substitution relative to a known on-chain
head. This ERC does not prove that an agent actually used the committed memory,
that the private witness is true or available, or that an agent's decisions were
correct. It makes the declared state history tamper-evident and attributable.

## 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](https://www.rfc-editor.org/rfc/rfc2119) and
[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

### Definitions

- **Memory Space**: a namespace with one linear state history.
- **Controller**: the account authorized to replace the controller or authorizer.
- **Authorizer**: the account authorized to approve state transitions.
- **Experience Delta**: the fixed-width transition claim defined below.
- **Private witness**: off-chain data required to open a commitment, including
  payloads, salts, encryption metadata, and locators.
- **Transition ID**: the EIP-712 `hashStruct` of an Experience Delta.
- **State root**: the accumulator produced from the previous state root and the
  accepted Transition ID.

### Memory Space identifier

The initial controller MUST choose a `bytes32 salt`. The Memory Space identifier
MUST be derived as:

```text
MEMORY_SPACE_TYPE =
  "MemorySpace(address initialController,bytes32 salt)"

spaceId = keccak256(abi.encode(
  keccak256(bytes(MEMORY_SPACE_TYPE)),
  initialController,
  salt
))
```

`initialController` MUST NOT be the zero address. This derivation prevents an
unrelated account from pre-registering an identifier selected by another
controller. The salt MAY remain private until registration.

### Experience Delta v1

`v1` names the wire format fixed by the `ExperienceDelta` and `MemoryState`
type strings, the EIP-712 signing-domain version, and the baseline commitment
domain tags defined below. Changing any of these values changes transition
identifiers, state roots, signing digests, or baseline commitments and is
therefore not wire-compatible with v1.

The following struct and field order are normative:

```solidity
struct ExperienceDelta {
    bytes32 spaceId;
    uint64 sequence;
    bytes32 prevStateRoot;
    bytes32 deltaCommitment;
    bytes32 provenanceCommitment;
    bytes32 profileId;
    bytes32 locatorCommitment;
}
```

The fields have these meanings:

- `spaceId` identifies the Memory Space.
- `sequence` is a strictly increasing counter beginning at `1`.
- `prevStateRoot` is the current state root before this transition and is zero
  for the first transition.
- `deltaCommitment` binds the private memory operation or encrypted delta and
  MUST NOT be zero.
- `provenanceCommitment` optionally binds inputs, inference attestations, or
  other causal material. Zero means absent.
- `profileId` identifies the off-chain interpretation and commitment profile and
  MUST NOT be zero.
- `locatorCommitment` optionally binds a private off-chain locator. Zero means
  absent. A raw locator is not part of this interface.

Raw memory, salts, encryption keys, and raw locators MUST NOT be supplied to the
registry through this struct or any other required core argument.

### Transition ID

The exact EIP-712 type string is:

```text
ExperienceDelta(bytes32 spaceId,uint64 sequence,bytes32 prevStateRoot,bytes32 deltaCommitment,bytes32 provenanceCommitment,bytes32 profileId,bytes32 locatorCommitment)
```

The Transition ID MUST be calculated as:

```text
transitionId = keccak256(abi.encode(
  keccak256(bytes(EXPERIENCE_DELTA_TYPE)),
  delta.spaceId,
  delta.sequence,
  delta.prevStateRoot,
  delta.deltaCommitment,
  delta.provenanceCommitment,
  delta.profileId,
  delta.locatorCommitment
))
```

There is no alternate JSON, JCS, CBOR, or application-specific Transition ID.

### State transition

The exact state type string is:

```text
MemoryState(bytes32 prevStateRoot,bytes32 transitionId)
```

After accepting a transition, the registry MUST calculate:

```text
nextStateRoot = keccak256(abi.encode(
  keccak256(bytes(MEMORY_STATE_TYPE)),
  delta.prevStateRoot,
  transitionId
))
```

For an empty space the current state root and sequence are zero. A registry MUST
accept a transition only if `sequence == currentSequence + 1` and
`prevStateRoot == currentStateRoot`. It MUST then atomically store the Transition
ID, next state root, and sequence.

### Immutability of the enforcing logic

The linearity guarantee above is a property of the deployed code, not of the
recorded data. A registry whose implementation can be replaced can be made to
accept a transition violating `sequence == currentSequence + 1`, while still
reproducing every test vector in this document; passing the vectors and providing
the guarantee are therefore distinct claims.

A conforming registry MUST NOT be deployed behind an upgrade mechanism able to
replace the logic enforcing sequence linearity, state root chaining, or signature
validation, and MUST expose no upgrade authority over that logic. Extensions and
off-chain tooling MAY be upgraded independently, provided they cannot alter the
acceptance rules above.

### Signing domain

All registration, authorization-update, and transition signatures MUST use the
EIP-712 domain:

```text
name              = "AgentMemoryState"
version           = "1"
chainId           = current chain ID
verifyingContract = registry address
```

The signed digest is:

```text
keccak256(0x1901 || domainSeparator || structHash)
```

The domain prevents a signature from being replayed on another chain or another
registry. A `transitionId` remains a chain-independent content identifier; its
signature does not.

### Referencing a transition

A `spaceId` is derived from the controller and salt only, so it is stable across
chains. The same Memory Space identifier can therefore be used for histories on
more than one chain. Two chains MAY hold different histories under the same
`spaceId`, so `(spaceId, transitionId)` alone does not determine which history is
meant.

A conforming transition reference MUST identify the registry address and the
chain on which that registry is deployed, in addition to `spaceId` and
`transitionId`. The chain context MAY be implicit when the reference is emitted
on the same chain as the registry.

This is a verification requirement, not only a resolution convenience. The
signing domain sets `verifyingContract` to the registry address, so the signed
digest cannot be reconstructed — and the authorizer's signature therefore cannot
be checked — without knowing which registry accepted the transition. An otherwise
complete reference that omits the registry or chain context is not independently
verifiable.

### Space registration

Registration MUST bind the Space to non-zero `controller` and `authorizer`
addresses. The supplied `spaceId` MUST equal `deriveSpaceId(controller, salt)`.
The registration struct is:

```text
SpaceRegistration(bytes32 spaceId,address controller,address authorizer)
```

The controller MUST authorize its EIP-712 struct hash. A Memory Space MUST be
registered at most once. A relayer MAY submit the authorization without becoming
controller or authorizer.

### Authorization updates

The controller MAY atomically replace both controller and authorizer. Each Space
maintains a `uint64 configNonce`, initially zero. An update MUST use
`nonce == currentConfigNonce + 1` and the struct:

```text
SpaceAuthorization(bytes32 spaceId,address newController,address newAuthorizer,uint64 nonce)
```

The current controller MUST authorize the update. Both replacement addresses
MUST be non-zero. The nonce prevents replay of an earlier configuration.

### Signature validation

A registry MUST NOT select between ECDSA and ERC-1271 validation by account code
presence alone. An externally owned account delegated under [EIP-7702](./eip-7702.md)
carries code of the form `0xef0100 || delegate`, and many delegates implement no
signature policy; branching on code presence would reject those accounts outright.

A signature satisfying either scheme MUST be accepted, evaluated in this order. When
the authorizer has non-empty code, the registry MUST first call `isValidSignature` as
specified by ERC-1271 and accept the signature on the `0x1626ba7e` magic value.
Otherwise, or when that call reverts, returns fewer than 32 bytes, or returns any other
value, the registry MUST recover the signer from a canonical, non-malleable 65-byte
ECDSA signature and require equality with the configured account. A signature of any
other length MUST be rejected without attempting recovery.

A registry MAY accept an empty signature when `msg.sender` is exactly the account
whose authorization is required. It MUST NOT treat an empty signature submitted
by any other caller as authorized.

### Baseline private commitments

Profiles MAY define stronger schemes, including zero-knowledge commitments. A
conforming implementation SHOULD support the following domain-separated baseline:

```text
DELTA_DOMAIN      = keccak256(bytes("AgentMemoryState.deltaCommitment.v1"))
PROVENANCE_DOMAIN = keccak256(bytes("AgentMemoryState.provenanceCommitment.v1"))
LOCATOR_DOMAIN    = keccak256(bytes("AgentMemoryState.locatorCommitment.v1"))

deltaCommitment = keccak256(abi.encode(
  DELTA_DOMAIN,
  profileId,
  deltaSalt,
  keccak256(payloadBytes)
))

provenanceCommitment = keccak256(abi.encode(
  PROVENANCE_DOMAIN,
  provenanceSalt,
  keccak256(provenanceBytes)
))

locatorCommitment = keccak256(abi.encode(
  LOCATOR_DOMAIN,
  locatorSalt,
  keccak256(bytes(locator))
))
```

Each salt is 32 bytes and SHOULD be independently sampled. For low-entropy
plaintext, the salt MUST remain secret or `payloadBytes` MUST be ciphertext
produced with a fresh high-entropy key and nonce. A public salt does not prevent
targeted dictionary attacks against low-entropy content.

### Interface

```solidity
interface IAgentMemoryState {
    struct ExperienceDelta {
        bytes32 spaceId;
        uint64 sequence;
        bytes32 prevStateRoot;
        bytes32 deltaCommitment;
        bytes32 provenanceCommitment;
        bytes32 profileId;
        bytes32 locatorCommitment;
    }

    event SpaceRegistered(
        bytes32 indexed spaceId,
        address indexed controller,
        address indexed authorizer
    );

    event SpaceAuthorizationUpdated(
        bytes32 indexed spaceId,
        address indexed controller,
        address indexed authorizer,
        uint64 configNonce
    );

    event TransitionCommitted(
        bytes32 indexed spaceId,
        bytes32 indexed transitionId,
        uint64 indexed sequence,
        bytes32 prevStateRoot,
        bytes32 nextStateRoot,
        bytes32 deltaCommitment,
        bytes32 provenanceCommitment,
        bytes32 profileId,
        bytes32 locatorCommitment,
        address authorizer
    );

    function deriveSpaceId(address initialController, bytes32 salt)
        external pure returns (bytes32 spaceId);

    function registerSpace(
        bytes32 spaceId,
        address controller,
        address authorizer,
        bytes32 salt,
        bytes calldata controllerSignature
    ) external;

    function updateSpaceAuthorization(
        bytes32 spaceId,
        address newController,
        address newAuthorizer,
        bytes calldata controllerSignature
    ) external;

    function commitTransition(
        ExperienceDelta calldata delta,
        bytes calldata authorizerSignature
    ) external returns (bytes32 transitionId, bytes32 nextStateRoot);

    function head(bytes32 spaceId) external view returns (
        bytes32 transitionId,
        bytes32 stateRoot,
        uint64 sequence
    );

    function spaceAuthorization(bytes32 spaceId) external view returns (
        address controller,
        address authorizer,
        uint64 configNonce
    );
}
```

Implementations MAY expose additional read methods but MUST NOT change the
meaning of the normative functions, hashes, or events.

### Observation time

An implementation MAY record `block.timestamp` as the time at which a transition
was observed on-chain. Application-supplied timestamps MUST NOT determine
transition ordering. Timestamps inside private payloads remain profile data.

### Out of scope

This ERC does not define agent identity, raw memory storage, data availability,
retrieval, a universal memory taxonomy, inference verification, branching or
merge rules, deletion claims, licensing, payment, tokenization, cross-chain
migration, or machine consciousness. Such systems MAY reference a Space,
Transition ID, or state root without becoming a dependency of the core.

## Rationale

### One fixed-width Delta

A fixed-width struct is straightforward for independent implementations while
binding state, private change, provenance, interpretation, and location.
Removing authors and timestamps from the Delta avoids confusing self-asserted
metadata with registry authorization or chain observation.

### State roots instead of previous record pointers

A previous-record pointer proves only list linkage. Binding the exact prior state
root and computing the next root makes the transition relation explicit and
prevents an update from claiming an unrelated prior memory state.

### Controller and authorizer separation

The controller is an administrative recovery boundary. The authorizer may be a
hot EOA, multisignature account, smart account, or policy contract. Rotation
does not change the Space identifier or its state history.

### Committed locator instead of URI calldata

A public URI can leak storage topology, tenant identifiers, or access tokens. A
locator commitment is signed, cannot be replaced by a relayer, and can be opened
selectively to an authorized retriever.

### Profiles instead of a fixed memory enum

Text, vectors, tool traces, policies, and model-specific latent representations
evolve independently. `profileId` lets applications define these semantics
without freezing one product taxonomy into the core registry.

### Extensions are separate

Deletion attestations and memory markets have different trust, authorization,
and security requirements. Keeping them outside this interface allows the state
primitive to be reviewed and implemented without adopting those claims.

## Backwards Compatibility

This ERC introduces a new interface and does not change an existing standard.
Early prototypes that used JCS identifiers, unsigned URI arguments, fixed memory
enums, or `previousDelta` pointers are not wire-compatible with v1 and require an
explicit migration checkpoint into a new Space.

## Test Cases

For the canonical v1 vector:

```text
ExperienceDelta typehash:
0x4f020f86bc06d852f1fde17853b4d92a70214eeab8e09718028124af097d070d

MemoryState typehash:
0xf3148762556cbf851baf4b9a205e18ff4e6b366a58a3a1ef58e8626ba41beadb

MemorySpace typehash:
0x9ae5478f084ad3b841da58a9cb2354d153cddec59ee64d0cb741fa9d08884531

transitionId:
0xdd00dd6eb3aec704b5455502647a0caacf23be6c724eda4a60d9645291e7f4e5

nextStateRoot:
0x9684a8d3571c5cd9c1e3abb1b0c0797b9fef6965e9002aeefba91e8cb1163754
```

The complete input, private commitment witnesses, domain separator, signing
digest, registration hash, and authorization-update hash are provided in
[the machine-readable test vector](../assets/eip-8350/test-vectors-v1.json).

## Reference Implementation

The reference implementation contains a Solidity registry
([`AgentMemoryStateRegistry.sol`](../assets/eip-8350/AgentMemoryStateRegistry.sol),
implementing [`IAgentMemoryState.sol`](../assets/eip-8350/IAgentMemoryState.sol)),
which reproduces the golden vector linked above.

## Security Considerations

### Dictionary attacks

`keccak256(rawMemory)` is not a hiding commitment for low-entropy values. A salt
prevents precomputation but does not prevent targeted guessing when the salt is
public. Sensitive or low-entropy payloads therefore need encryption or a secret
salt, as required in the Specification.

### Equality and metadata leakage

Even private commitments expose timing, update frequency, Space relationships,
and profile identifiers. Reusing salts, ciphertext, or locator witnesses can
also reveal equality. Profiles can mitigate this with padding, batching, and
salt or key rotation where the leakage matters.

### Authorization compromise

A compromised authorizer can append valid-looking state. A compromised
controller can replace the authorizer or controller. Deployments are advised to
use contract-account policies, threshold authorization, spending or rate limits,
and operational recovery procedures appropriate to the value of the Space.

### Contract signature behavior

ERC-1271 validation is external code execution through `STATICCALL`. The
Specification requires the exact magic value, handling of reverts and malformed
return data, and completing authorization before state is mutated; an
implementation that relaxes any of these accepts forged authorization.

### Delegated accounts

An account delegated under EIP-7702 carries code while its underlying key stays valid,
so code presence alone does not separate a contract account from an externally owned
one. Deciding validation by code presence locks out delegated accounts whose delegate
implements no signature policy. Conversely, because delegation does not revoke the key,
a delegate policy is not the only authorization path: a valid ECDSA signature from the
underlying key still authorizes a transition. Deployments that rely on a delegate's
threshold, spending, or session policy need to account for that residual path, and
should treat key custody as equally sensitive after delegation.

### Replay and relayers

The EIP-712 domain prevents cross-chain and cross-registry signature replay.
`spaceId`, sequence, prior root, and locator commitment prevent a relayer from
moving or modifying a signed transition. A relayer can still withhold a valid
transaction or race another submission of the same transition.

### Availability and truth

A valid commitment proves neither data availability nor truth of the committed
memory. It proves that the configured authorizer approved a state transition.
Applications requiring availability, inference correctness, or provenance truth
need separate mechanisms, and cannot infer them from this registry alone.

### Deletion claims

Neither revocation nor key-destruction evidence can prove universal erasure of
data already copied by another party. Extensions are expected to describe
deletion records as attestations with a stated scope, not as absolute proofs of
deletion.

### Upgradeability

An upgradeable registry can change hashing or authorization semantics after users
sign transitions. Because a replaced implementation can accept a transition that
violates sequence linearity while still reproducing every published test vector,
non-upgradeability of the enforcing logic is a precondition of the non-forgeability
property rather than a deployment preference. It is stated normatively under
"Immutability of the enforcing logic" in the Specification, and repeated here
because a requirement stated only in this section is read as informative.

Evidence that no upgrade authority exists can include empty
[ERC-1967](./eip-1967.md) implementation and admin slots, plus deployed source
that exposes no owner, admin, initializer, or proxy. These checks assess
deployment immutability; vector conformance alone does not establish it.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
