Skip to main content
A MemoryStore is durable knowledge an agent can read and write across Sessions. It is a versioned file tree with an ownership boundary, an approval path, and an audit trail, not a vector index bolted onto a chat log.

Two surfaces on one store

This is the first thing to get straight, because the same store ID serves both: Document Memory has alpha / hosted source posture; confirm its executable state in the live capability status. The versioned surface has a qualified durable Postgres adapter for production and local development, while the in-memory adapter remains its test and conformance oracle. Hosted alpha still leaves MEMORY_VERSIONED_SURFACE disabled until an operator-approved target completes the D7 enablement sequence; versioned operations and Dream creation currently fail closed there with 503 memory.backend_unavailable. Existing Dream metadata remains readable for recovery and audit. The distinction matters because the surfaces answer different questions: reach for the document surface when you just need a file at a path, and the versioned surface when you need history, review, or a per-Session branch.

The model

Store

One ownership, history, and visibility boundary. Its owner decides lifecycle and the default PermissionAssignments minted at creation, never who may access it. Access is always PermissionAssignments.

Document

Content at a normalized absolute path. Capped at 100 KB each.

Revision

An immutable snapshot of the store’s whole tree, not one document’s version. Content-hashed so every storage backend produces byte-identical values.

Ref

A named pointer at an exact revision. Refs are the only mutable part; they advance by compare-and-swap, never last-write-wins.
canonical is the shared truth. A Session gets its own working ref so it can write without disturbing anyone else, and a checkpoint seals that Session’s accumulated changes at a turn boundary: at most one revision per changed store per turn. A turn that changed nothing produces nothing. Ownership is one of workspace, principal, agent_definition, session, or surface_scope. At creation, every owner kind except workspace mints one PermissionAssignment giving the owner read and write. A Workspace-owned store mints none, so access has to be granted deliberately. POST /v1/memory-stores/{id}/fork creates a new store from one exact source revision. You choose the target name, owner, and optional description; Checkfu copies that immutable tree, proves the target canonical tree_hash matches, and then the two stores evolve independently. The endpoint requires an Idempotency-Key: an exact retry returns the same fork, while reusing the key with different input is a conflict. Forking retained content is unavailable in a zero-data-retention Workspace and remains behind the same versioned-surface rollout fence.

Portable exports

Two export routes exist, and they are different shapes rather than two sizes of the same one. GET /v1/memory-stores/{id}/export returns the whole store as a single complete aggregate and is kept unchanged for the dated v1 response, so a large store arrives as one unbounded body. GET /v1/memory-stores/{id}/export-pages is the additive bounded route and the one to reach for: a normalized, resumable graph stream of revision, path_fence, document_version, revision_entry, and ref items in canonical order. Read a page, then pass its next_page value back as the page query parameter until next_page is null. That cursor is authenticated and opaque, and it pins every page to one Workspace, source store, container generation, scope, retention instant, and the earliest included body expiry. A concurrent mutation or an elapsed expiry returns memory.conflict instead of mixing snapshots or extending custody. A page carries at most 200 items (ask for fewer with limit) and 1 MiB of canonical item payload bytes; the manifest and response envelope sit outside that byte count, and an importer should recompute both the count and the canonical ordering rather than trust the page’s declaration. Structural history survives erasure: every revision/path pair remains a revision_entry, while its referenced document version carries either an available body with content and its immutable expiry ceiling or an explicit erased body. Erasure replaces raw provenance with its structural digest, retaining tree hashes and document counts without containing or reconstructing shredded bytes or source identifiers.

How canonical actually moves

Canonical advances differently depending on who owns the store, and that is what keeps shared memory safe:
  • A personal store (principal- or session-owned) advances canonical directly. When such a store is mounted read_write, the agent’s file changes seal into its durable documents at turn settlement (see Writeback at settlement).
  • A shared store (environment-, agent-definition-, or surface-scope-owned) advances canonical only through an approved Proposal. A direct canonical write returns 403 memory.read_only, and a shared store never opens a read_write mount in the first place: the clamp is structural, so a stray write PermissionAssignment cannot turn a sandbox into a live writer. There is therefore no shared writeback to defer. A settlement capture naming a shared store matches no admitted writable mount and is refused outright, mutating nothing and producing no skip event.
An agent may only write its own Session’s working ref. Writing another Session’s ref is refused.

Writeback at settlement

When a personal store is mounted read_write, the agent works its memory as an on-disk file tree, and at turn settlement Checkfu seals what changed: the runner diffs its live sandbox tree against the frozen staged manifest and the Session seals a bounded, idempotent pass into the store’s durable document surface (putDocument / removeDocument, content-addressed per document). One memory.change_checkpointed records the changed paths and the base/new tree digests; one memory.accessed records the read. The seal fails closed: on any write error, an unreadable store, or ZDR denial it writes nothing partial. It is idempotent on replay. Because a personal store advances directly, a second Session the same principal opens reads back what the first one wrote. (The versioned revision graph of revisions, diffs, and working refs remains locally implemented but preview / rollout_fenced behind MEMORY_VERSIONED_SURFACE; the settlement seal advances the independently modeled durable document surface, so the digests above are content addresses, not graph revision IDs. See capability status.) Writeback needs the execution provider to enumerate the mounted tree, because a delete is inferred from a path that is no longer there — a partial listing would read as deletions. Checkfu therefore enumerates through a platform-owned reader, never a command run inside the sandbox image, whose binaries the agent controls. A provider that cannot enumerate does not silently drop the edits: a writable memory mount is refused at admission with runtime.harness_incompatible, so the Run fails visibly rather than settling a wrong revision. A read_only memory mount needs no enumeration and is unaffected. The seal is also single-store: one turn may checkpoint at most one MemoryStore. A store’s aggregate, its whole document tree, and its replay receipt commit in one durable transaction, and there is no cross-store coordinator that could make two independently owned stores one transaction. So a capture naming two stores is refused before any mutation rather than committing a prefix, and a terminal turn that changed two writable stores cannot settle. Admit one writeback-capable store per Run and mount the rest read_only.

Proposals

A Proposal freezes its base_revision, head_revision, and diff hash at creation. Approving it compare-and-swaps canonical from that frozen base, so a proposal can never silently rebase onto work it was not reviewed against. You respond with approve or reject. superseded is not something a reviewer chooses: it is what an approval becomes when canonical moved on beneath it. Handle it as “re-propose against the new base,” not as a rejection. The stored status values (open, approved, rejected, superseded) differ from the imperative verbs you send (approve, reject). An empty proposal is unrepresentable: base and head must differ.

Mounting memory into a Session

A Session declares its Memory mounts at creation, requesting read_only or read_write plus optional agent-facing instructions. Admission then clamps the request against the acting Principal’s PermissionAssignments: Two structural write caps then apply on top of the PermissionAssignment clamp:
  • A shared-owner store (Workspace, AgentDefinition, or SurfaceScope) never resolves to read_write, however the PermissionAssignments read. Shared canonical state advances through governed proposals, not a live sandbox writer.
  • A principal-owned personal store is read_only if explicitly mounted into a shared public/private place. A personal/DM place is not shared, so the store remains read_write-eligible there.
Composition is stricter than that second mount clamp: the standard shared-place mount set excludes personal memory entirely from public and private places. It is included only for a personal/DM place or a direct non-place Session. These owner policy, place policy, and PermissionAssignment checks are independent; a mis-minted write PermissionAssignment cannot bypass any of them. The degradation is recorded, not silent: memory.mount_resolved carries both the requested and the effective access, and each Run repeats the effective access in session.mounts_resolved. An archived store stays readable through an existing read-only mount but rejects a read-write plan.

Retrieval

Search is lexical, deterministic, and revision-pinned. Omitting revision resolves canonical once and then searches that exact revision, never a moving target, so results within one search are internally consistent. Reads are recorded as memory.accessed, batched once per store per turn. Under zero-data-retention the record downgrades from listing document paths to an aggregate count before it is persisted.
Memory events carry IDs, paths, hashes, and counts, never document content. The event log tells you what was touched, not what it said.

Erasure

POST /v1/memory-stores/{id}/redactions takes a list of paths and erases them across every surface the platform owns for that store, including the search index. It reports counts only (revisions, paths, and hashes affected) and never returns erased content. Redaction requires an administrator key and is itself recorded as a revision, so erasure appears in history rather than quietly rewriting it. Deleting a store fences writers first, erases its complete versioned container and projections, and only then records the terminal tombstone. A delayed fork or change-set retry cannot recreate the store or replay paths from a stale receipt.

Curated memory (Dreams)

A Dream is a batch job that distills what agents learned across many Sessions into a clean MemoryStore. One input store and 1–100 Sessions go in; one new Workspace-owned output store comes out. The inputs are never mutated: curation is copy-on-write.
Dreams are preview / rollout_fenced; see capability status. The example below runs against local development with Postgres and MEMORY_VERSIONED_SURFACE=true; the in-memory adapter is only the test and conformance oracle. Hosted requests still fail closed until an operator-approved rollout enables the versioned Memory prerequisite.
The job seeds the output store from the input’s current documents, then runs one curation pass over the selected Sessions’ event logs and lands the result as a proposal:
  • Omit model_routing_profile_key and curation is a deterministic dedupe pass: no inference, zero usage.
  • Supply model_routing_profile_key and one schema-constrained model call, steered by instructions, does the curation.
  • review: true leaves the proposal open for a human decision instead of auto-approving it (safe to auto-approve by default, because the fresh output store has no other writers).
status moves pending → running → completed (or failed/canceled), and a completed Dream names its output_memory_store. The create call returns as soon as the Dream is pending: execution runs on the platform’s own work queue, so you poll (or cancel) a Dream that is genuinely still going rather than one that already finished inside your request. Cancel therefore lands mid-pipeline on a pending or running Dream, and a Dream can never strand pending with nothing to execute it — a failed enqueue settles the row failed at create time. To run curation on a schedule, point a schedule-triggered Automation at a dream target; each firing curates only the Sessions it has not seen before.
Dreams run on the rollout-fenced versioned Memory surface. A zero-data-retention Workspace cannot dream: creation is rejected with policy.denied. A deterministic Dream makes no model call and reports zero usage; a model-curated Dream records its exact provider token usage on the Dream, including when curation fails after the tokens were spent.

One trap worth knowing

Memory uses three different concurrency mechanisms, and picking the wrong one is the most common integration mistake: All three fail with 409 memory.conflict when the precondition misses. None of them fall back to last-write-wins.

Next steps

Sessions and runs

How mounts resolve at admission and per Run.

Tenancy and governance

The PermissionAssignments that decide what a mount is clamped to.