Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-8350: Agent Memory State Registry

Authorized commitments to private agent memory state transitions.

Authors Everest An (@everest-an), XiaoHai (@XiaoHai67890), Luo (@lucas1968), Eric
Created 2026-07-14
Discussion Link https://ethereum-magicians.org/t/erc-8350-agent-memory-state-registry/29098
Requires EIP-712, EIP-1271, EIP-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 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 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 and RFC 8174.

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:

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:

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:

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

The Transition ID MUST be calculated as:

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:

MemoryState(bytes32 prevStateRoot,bytes32 transitionId)

After accepting a transition, the registry MUST calculate:

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:

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

The signed digest is:

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:

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:

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 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:

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

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:

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.

Reference Implementation

The reference implementation contains a Solidity registry (AgentMemoryStateRegistry.sol, implementing 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 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 and related rights waived via CC0.

Citation

Please cite this document as:

Everest An (@everest-an), XiaoHai (@XiaoHai67890), Luo (@lucas1968), Eric, "ERC-8350: Agent Memory State Registry [DRAFT]," Ethereum Improvement Proposals, no. 8350, July 2026. Available: https://eips.ethereum.org/EIPS/eip-8350.