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.
Plain HTTP against the Checkfu API needs no private package. A Checkfu source checkout is for contributors and authorized evaluators; customers do not need one for this page. The runtime this flow depends on is not yet qualified for every deployment and configuration. Confirm capability status before making availability part of your application’s contract.
Native harness automatic-memory files and continuation state are separate from a mounted Checkfu MemoryStore. Retaining that state does not make it shared knowledge or authorize another Session to read it. To reuse knowledge through Checkfu Memory, select the destination store and use its authorized write or proposal path; source-read, destination-write and adoption permissions still apply. Check the selected harness’s supported retention and resume behavior separately. The native Session adapter has a source-integrated read-only Memory path and a governed read_write path through the canonical versioned writer. A writable mount requires the original write offering and current write authority; it is not silently downgraded or enabled by mount metadata alone. Result publication separately rechecks the original Principal and selected Agent’s READ authority. Refusing the result does not undo a committed write. The owned native customer case has demonstrated a Memory read, governed write, public checkpoint and exact-version fork reuse in a distinct read-only Session. That case does not qualify READ revocation, cold recovery, compaction or erasure; check the selected runtime’s capability status separately. Its Memory input path accepts an API-key or Principal-access issuer with an acting Principal; Automation issuers remain unavailable. Native reads also require active store metadata; archived, deleting or pending lifecycle state does not satisfy this native gate, even where the generic read-only surface remains readable. Reads use the exact retained revision selected for that input or eligible round. A retained Memory plan without a revision is unavailable rather than resolved against today’s canonical state. Source integration alone does not establish availability or qualify other native journeys. A fresh accepted user input retains its original Memory selection. In the source-integrated native path, an eligible later round can advance through that same input’s retained, committed write checkpoints; its selected revision and write provenance are then fixed for that round and its retries. This is not a new selection of arbitrary latest state. Reading the advanced revision still requires current Principal and Agent READ authority. The source-integrated first-tool path supports a single offered call; a parallel batch with multiple calls remains unavailable. These later-round controls still need their own runtime qualification. A subsequent accepted input can select the advanced Session working ref with fresh authority. A second distinct write against the same frozen base can conflict after the first write advances that ref; do not silently rebase it or treat an exact-call replay as a new selection.

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 enables the versioned surface; versioned operations currently fail closed there with 503 memory.backend_unavailable. The versioned surface itself is preview / rollout_fenced; see capability status. 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.

Read and retain an exact revision

Read a document with GET /v1/memory-stores/{id}/document?path=/notes.md. Omitting version selects canonical and the response includes its exact resolved version. Retain that identifier with the returned path and content digest; a later request with version=memver_… selects the same retained revision even if canonical advances. An unavailable selected revision refuses rather than falling back to canonical. Every later read still needs current authority and retained content.

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.

MemoryVersion

An immutable snapshot of the store’s whole tree, not one document’s version. Its id is the memver_ identifier you pass as a version precondition; its tree_hash is content-hashed so every storage backend produces byte-identical values.

Ref

A named pointer at an exact MemoryVersion. 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 MemoryVersion per changed store per turn. A turn that changed nothing produces nothing. Ownership is one of workspace, principal, agent_definition, session, or collaboration_space. 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_version. 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. It admits at most 10,000 normalized graph items and 8 MiB of acquired item JSON and expanded response JSON, including metadata, JSON escaping and a body repeated across retained versions. A larger export refuses with non-retryable validation.range_too_large (HTTP 422); it never returns a truncated history. Use paged export to transfer a larger store. If the original request authority cannot be evaluated, document and export reads refuse with auth.authorization_unavailable (HTTP 503, retryable). Retry the same request. Revoked authority remains a denial; an actual Memory backend outage remains memory.backend_unavailable. Document and export reads keep their original caller and selected content through final publication. If revocation, replacement or erasure wins before that publication, the read refuses instead of releasing the earlier acquired body. A read already admitted at that boundary may finish while a later erasure proceeds; erasure cannot recall bytes already delivered. Selecting an older revision does not bypass this check or extend the body’s retention deadline. GET /v1/memory-stores/{id}/export-pages is the additive bounded route and the one to reach for: a normalized, resumable graph stream of version, path_fence, document_version, version_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 version/path pair remains a version_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 principal-owned personal store keeps each Session’s working changes isolated. Settlement seals a MemoryVersion on that Session’s working ref; it does not advance canonical. Another ordinary Session starts from canonical and does not automatically see those changes.
  • A session-owned store follows its owning Session’s checkpoint when canonical is still at the checkpoint’s base. A separately advanced canonical is preserved. Authorized direct canonical writes are also available for personal stores.
  • A shared store (workspace-, agent-definition-, or collaboration-space-owned) follows its canonical_writes setting, chosen when the store is created or patched:
    • review (the default) advances canonical only through an approved Proposal. A direct canonical write returns 403 memory.read_only.
    • direct also lets any Principal or API key that holds a write PermissionAssignment on the store append a canonical version directly. Without that grant the write is refused either way.
    The setting belongs to the store, so whoever authors an Automation or other writer cannot skip a review the store requires. A Session agent never writes shared canonical under either setting, 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 Automation’s Memory write step acts as its service Principal and needs that Principal’s write PermissionAssignment. On a direct store it appends a canonical version; on a review store it opens a Proposal and settles as proposed with the Proposal id, and the Proposal waits for the ordinary approval. A retried step finds its original version or Proposal and never writes twice. An agent may only write its own Session’s working ref. Writing another Session’s ref is refused. For the public working-ref checkpoint endpoint, retry the same body with the same Idempotency-Key after a transient failure. Recovery returns the original revision and changed paths. canonical_advanced records whether that logical checkpoint advanced canonical when it committed; a retry preserves that result even if canonical has since moved again. Recovery still requires current write authority and a retention policy that permits durable Memory. Older incomplete checkpoints for the owning Session without a recorded canonical outcome fail closed instead of guessing that result. Native write-response recovery is separate from this public checkpoint retry. The source-integrated recovery path retains the original call and selected base, then reads its exact canonical receipt under fresh original Principal and selected Agent READ authority. A missing receipt is unavailable, not proof that nothing changed or permission to write again. Refusing the recovered result leaves any committed write intact. This source path still needs its own native runtime qualification; it does not establish cold recovery or cross-store atomicity.

Writeback at settlement

An admitted read_write mount does not grant permission to change another Session’s working ref or substitute a newer base. Governed writeback seals changes against the selected base as a real MemoryVersion on the original Session’s working ref. A settlement checkpoint records memory.change_checkpointed with the working_ref, base_version, new_version, tree_hash, diff_hash, and changed_paths; this event describes a checkpoint, not every individual write-tool call. The version fields are memver_ IDs; the hash fields are content digests. An exact replay recovers the original revision instead of sealing another one. Revision metadata can outlive its document bodies. Keeping a MemoryVersion ID, hash, or checkpoint event does not extend body retention or restore expired or erased content. Current permissions and retention still apply when another Session reads or forks that revision. When canonical advances, superseded bodies without a current ref can be retired. A working ref that still selects the original version keeps those bodies reachable, but does not override expiry, redaction, erasure or current READ permission. Plan that retention before advancing canonical; a later exact-version selector cannot recreate retired bytes. Manage public working refs explicitly. To retain the current canonical revision for an already-created reader Session, an authorized store writer can call POST /v1/memory-stores/:id/working-refs with { "session": "sess_0123456789abcdef0123456789abcdef" } and an Idempotency-Key. The response is a MemoryRef. Verify that its version equals the revision the reader selected before advancing canonical. This operation starts from canonical; it does not accept a version selector or recover an already-retired historical body. If canonical moved before the ref was created, do not silently substitute the returned revision for the selected input. Store write authority is required to manage this ref; the reader Agent can remain read-only. When a retained ref is no longer needed, an authorized store writer can release it with DELETE /v1/memory-stores/:id/working-refs/:session. The Session ID does not guarantee that deleting the Session retires a ref in an independently retained store. Removing a ref releases that retention root; it does not erase versions still retained elsewhere or certify that body cleanup has finished. Session erasure fences new writes to Session-owned Memory stores and drains their retained bodies before acknowledging the Memory cleanup sink. It does not treat a working ref in another owner’s store as evidence that those bodies belong exclusively to the erased Session. If shared working refs, authored proposals or curation references remain unresolved, Memory cleanup stays pending rather than claiming deletion or erasing another owner’s retained content. Releasing a public working ref is a separate store operation, not proof that Session erasure has completed. For tree-capture writeback, the execution provider must enumerate the mounted tree, because a delete is inferred from a path that is no longer there — a partial listing would read as deletions. The native Memory write-tool path instead applies an explicit change against its selected base and retains a checkpoint with the original tool result; it does not infer deletions from a partial listing. 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 tree. A read_only memory mount needs no enumeration and is unaffected. A turn that changed several writable stores settles one checkpoint per changed store. A store’s aggregate, its whole document tree, and its replay receipt commit in one durable transaction, but there is no cross-store coordinator that could make two independently owned stores one transaction. So the seal first authorizes and compare-and-swap checks every store’s capture, and a stale or unauthorized capture refuses the whole turn before any store mutates. The stores then commit independently, never atomically across stores: a store that committed keeps its new state and replays through its own receipt when the settlement is resent. The Runner’s terminal submission carries at most 32 store captures and stays within the Runner request size limit.

Carry a sealed revision into another Session

To inspect a sealed document through the API, use GET /v1/memory-stores/:id/document?path=/path.md&version=memver_.... The optional version selects exact retained bytes; omitting it reads canonical. The response includes its exact resolved version. Save that value with the store, path and returned content hash, then pass it explicitly when another authorized Session needs the same input. Current store READ and retention still apply. Take new_version from the writer Session’s memory.change_checkpointed event. A reader Session can mount that exact revision with access: "read_only" and selection: { kind: "version", version: new_version }. Its Agent and acting Principal still need permission to read the store. Where the native versioned writer is installed, an explicit writable selection freezes the base of that Session’s working ref; it does not edit the immutable selected revision. An existing working ref at a different version refuses that base. Mount paths without this writer refuse explicit selections on an effective read_write mount. Omit selection to prepare the current Session working ref; use read_only for exact retained-byte reuse. In the Console’s new Session form, add the Memory store, choose Exact version under Revision selection, and enter that new_version ID. The selection switches the mount to read-only. Current state leaves the revision unselected; it does not automatically restore write access after you clear a selection. You can also choose a canonical, Session, Proposal, or Curation ref. Enter the owning object’s ID for a non-canonical ref. A named ref freezes when each Run starts, so use an exact version when later movement of that ref must not change the reader’s input. Selecting an ID does not grant access or extend retention. The following shell example creates a reader Session without starting a Run. Set CHECKFU_API_KEY, CHECKFU_AGENT_ID, CHECKFU_ENVIRONMENT_ID, CHECKFU_MEMORY_STORE_ID, CHECKFU_MEMORY_VERSION_ID, and CHECKFU_ACTED_AS_PRINCIPAL_ID to existing authorized resources and the writer’s sealed new_version. It needs curl; use a deployment where the versioned Memory prerequisite is available as described above, replacing the URL with your local API URL when needed. Keep CHECKFU_HANDOFF_KEY for an exact retry. The acting Principal and selected Agent each need Memory-store read; the Principal also needs act_as on that Agent. An API key identifies the requester and does not replace those grants or the acting Principal.
cURL
For an independent copy, fork the MemoryStore with POST /v1/memory-stores/{id}/fork from that same source_version, then mount the returned store and version in the reader Session. Grant the reader access to the new store when needed. A Memory-store fork copies the selected retained tree into a new ownership and history boundary; it does not fork the Session event log or copy a live Run. Shared canonical knowledge still moves only through an approved Proposal.

Proposals

A Proposal freezes its base_version, head_version, 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. An Agent without store READ cannot attach that store: Session creation refuses with 403 policy.denied. After attachment, execution also checks the acting Principal’s current permissions. Missing Principal READ refuses the mount before use. The created Session remains available, but refusal does not guarantee that an input or Run was accepted; inspect the request’s typed error and the Session’s retained history. The following table describes the acting Principal’s effective access: Two structural write caps then apply on top of the PermissionAssignment clamp:
  • A shared-owner store (Workspace, Agent, or CollaborationSpace) 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: the initial Session admission records memory.mount_resolved with both the requested and the effective access. Each Run records its resolved resources separately in session.resources_resolved; the initialization event is not repeated for every Run. An archived store stays readable through an existing read-only mount but rejects a read-write plan.

Retrieval

Search is lexical, deterministic, and version-pinned. Omitting version resolves canonical once and then searches that exact MemoryVersion, never a moving target, so results within one search are internally consistent. The response names the version it searched; to read the next page of the same tree, send that version again with the response’s next_cursor as cursor. memory.accessed with method: mount records the selected version and mounted path/digest manifest, batched once per store per turn. This is resolved-context provenance; it does not prove that the agent consumed every document. Under zero-data-retention, supported Memory access records persist one mounted-store aggregate containing counts, with no store IDs, versions, paths or digests.
Memory events carry IDs, paths, hashes, and counts, never document content. A mount receipt describes the selected context; actual use needs separate tool or output evidence.

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 (version_count, path_count, and hash_count) and never returns erased content. Redaction requires an administrator key and is itself recorded as a redaction MemoryVersion, 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. Native continuation state is a separate retained copy. Its selection instructions can contain customer text, and its paths, versions, identifiers and write lineage can identify customer data even when document bodies are absent. Treat those records as retained content, not content-free metadata. Erasing the store or exporting a Session event bundle does not prove that this native state, backups or recovery copies have been erased. The current native erasure path requires the original owner to stop, entered work to join, and namespace readback to confirm local removal. It still leaves erasure pending while backup, point-in-time recovery, replica and restore-branch scope remains unverified. An unavailable original owner also refuses cleanup; local close or a successful store deletion alone is not completed native erasure.

Scheduled learning

Scheduled learning is an ordinary Automation step program, not a separate resource: a schedule trigger, a bounded Session-history query, an Agent that analyzes it, governed Memory writes or Skill proposals, and a processing checkpoint that records which history was consumed. Each step uses the Automation’s own identity and resource permissions, so Session reads and Memory writes are authorized independently. Incremental curation runs on a Session-owned store remain available for single-Session curation.

One trap worth knowing

Memory uses three different identifiers for “which state am I editing”, and picking the wrong one is the most common integration mistake:
  • A content hash (sha256:…) names one document’s bytes. You send it as If-Match on the document surface and as expected_sha256 on each versioned operation.
  • A resource version is the integer version on the MemoryStore resource itself. It is the precondition for changing the store resource (its name, description, metadata, or archive state), not for editing documents.
  • A MemoryVersion ID (memver_…) names one immutable whole-tree snapshot on the versioned surface. Change sets, checkpoints, search, fork, and proposals speak in these.
Each operation takes exactly one of them as its precondition: All of them fail with 409 memory.conflict when the precondition misses. None of them fall back to last-write-wins. A change set names its target ref and the MemoryVersion that ref must still point at. Each operation also carries the content hash it expects at its path: null on a put means the path must not exist yet.
cURL
The response carries the resulting MemoryVersion object as version (null when the change set altered nothing, with unchanged: true), plus changed_paths and whether the request was replayed. Search takes the MemoryVersion to read as version; omit it to pin canonical at request time:
cURL
Creating a proposal returns its frozen base and head as MemoryVersion IDs, never as content hashes or the store’s resource version:
Proposal response

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.