Claude Code's Org Memory: the Memory Stores REST API#
Companion to Claude Code v2.1.227. | Part of the Claude Code Version Tracker series. | Official Changelog
v2.1.227 added two env vars, CLAUDE_CODE_MEMORY_API_BASE_URL and CLAUDE_CODE_MEMORY_API_TOKEN, that point Claude Code's memory-sync client at a server. That server speaks the Managed Agents Memory Stores REST API. The v2.1.227 binary carries the SDK client for it and the full Markdown reference for it, under the managed-agents-2026-04-01 public-beta header. This post lists every route, payload field, and status code, then the env vars and gate that let you run it against a server you choose.
A memory store is a workspace-scoped set of small text files that survives across sessions. Sessions are otherwise ephemeral: when one ends, what the agent learned is gone. Attach a store and the agent reads and writes it with ordinary file tools; every mutation is kept as an immutable version, so you get an audit trail and point-in-time rollback.
Object model#
Three objects, each with its own ID prefix.
| Object | ID prefix | Scope | Notes |
|---|---|---|---|
| Memory store | memstore_... | Workspace | Attached to sessions via resources[] |
| Memory | mem_... | Store | One text file, addressed by path; ≤ 100 KB each — prefer many small files |
| Memory version | memver_... | Memory | Immutable snapshot per mutation; operation ∈ created / modified / deleted |
Endpoint reference#
Every call carries ?beta=true and the anthropic-beta: managed-agents-2026-04-01 header. The SDK sets the header automatically on client.beta.memory_stores.*.
| Resource | SDK method | HTTP | Path |
|---|---|---|---|
| Stores | create | POST | /v1/memory_stores |
list | GET | /v1/memory_stores | |
retrieve | GET | /v1/memory_stores/{memory_store_id} | |
update | POST | /v1/memory_stores/{memory_store_id} | |
delete | DELETE | /v1/memory_stores/{memory_store_id} | |
archive | POST | /v1/memory_stores/{memory_store_id}/archive | |
| Memories | create | POST | /v1/memory_stores/{memory_store_id}/memories |
list | GET | /v1/memory_stores/{memory_store_id}/memories | |
retrieve | GET | /v1/memory_stores/{memory_store_id}/memories/{memory_id} | |
update | POST | /v1/memory_stores/{memory_store_id}/memories/{memory_id} | |
delete | DELETE | /v1/memory_stores/{memory_store_id}/memories/{memory_id} | |
| Memory versions | list | GET | /v1/memory_stores/{memory_store_id}/memory_versions |
retrieve | GET | /v1/memory_stores/{memory_store_id}/memory_versions/{version_id} | |
redact | POST | /v1/memory_stores/{memory_store_id}/memory_versions/{version_id}/redact |
The binary's own "Raw HTTP base path" block lists the memory update as PATCH /v1/memory_stores/{id}/memories/{memory_id}; the compiled SDK client issues it as a POST. The SDK verb is what goes on the wire.
Create and attach a store#
create takes a name and a description; the description is fed to the agent, so it is written for the model, not for humans.
store = client.beta.memory_stores.create(
name="User Preferences",
description="Per-user preferences and project context.",
)
# store.id -> memstore_01Hx...
Stores support retrieve / update / list (with include_archived and created_at_{gte,lte} filters) / delete / archive. Archive is one-way: the store goes read-only, existing session attachments keep working, new sessions cannot reference it, and there is no unarchive.
A store attaches to a session through the resources[] array, at session-create time only — there is no add-after-create path for a memory store.
session = client.beta.sessions.create(
agent=agent.id,
environment_id=environment.id,
resources=[
{
"type": "memory_store",
"memory_store_id": store.id,
"access": "read_write", # or "read_only"; default read_write
"instructions": "User preferences and project context. Check before any task.",
}
],
)
| Field | Required | Notes |
|---|---|---|
type | yes | "memory_store" |
memory_store_id | yes | memstore_... |
access | no | "read_write" (default) or "read_only" — enforced at the filesystem level on the mount |
instructions | no | Per-session guidance for this store, added to its name/description; ≤ 4,096 chars |
A session takes at most 8 memory stores. Split them by owner or lifecycle — a read-only shared-reference store next to a read-write per-user store, for example.
How the agent sees it#
Each attached store mounts in the session container at /mnt/memory/<store-name>/. The agent uses the standard file tools (bash, read, write, edit, glob, grep); there are no dedicated memory tools. read_only makes the mount read-only at the filesystem level. A short description of each mount — name, path, instructions, access — is injected into the system prompt automatically, so the agent knows the store is there without being told. Writes the agent makes under the mount persist back to the store and produce versions, exactly like a host-side update.
Manage memories host-side#
Use these to seed a store, review it, or fix a bad memory out of band.
List returns Memory and MemoryPrefix entries — a MemoryPrefix (type: "memory_prefix", just a path) is a directory-like node. Scope with path_prefix (include the trailing slash: /notes/ matches /notes/a.md but not /notes_backup/old.md) and bound the walk with depth. view="full" includes content; the default "basic" returns metadata only — path, content_sha256, content_size_bytes, updated_at.
Create vs. update differ in how they address the memory:
| Operation | Addressed by | Semantics |
|---|---|---|
memories.create(store_id, path=, content=) | Path | Create at path. 409 memory_path_conflict_error (with conflicting_memory_id) if the path is taken. |
memories.update(mem_id, memory_store_id=, ...) | mem_... ID | Change content, path (rename), or both. Rename onto an occupied path returns the same 409. |
Optimistic concurrency. update accepts a precondition; the only supported type is content_sha256. Read → modify → write back, passing the sha you read, and a concurrent writer that moved the memory first triggers 409 memory_precondition_failed_error — re-read and retry.
client.beta.memory_stores.memories.update(
mem.id,
memory_store_id=store.id,
content="CORRECTED: 2-space indentation.",
precondition={"type": "content_sha256", "content_sha256": mem.content_sha256},
)
Delete takes an optional expected_content_sha256 for a conditional delete.
Audit and rollback: memory versions#
Every mutation creates an immutable memver_... snapshot. create at a new path → created; an update or an agent-side write to the mount → modified; a delete → deleted. Each version records created_by — an actor with type ∈ session_actor / api_actor / user_actor — and, after redaction, redacted_at and redacted_by.
list is newest-first and paginated, filterable by memory_id, operation, session_id, api_key_id, and created_at_{gte,lte}. retrieve returns one version.
Redact scrubs a historical version while keeping the audit trail: it clears content, content_sha256, content_size_bytes, and path, and leaves actor and timestamps in place. It is the path for a leaked secret, PII, or a user-deletion request.
client.beta.memory_stores.memory_versions.redact(version_id, memory_store_id=store.id)
Status codes and server-enforced limits#
The write path is validated server-side, and the errors are specific:
| Condition | Status / code |
|---|---|
| Path already occupied (create, or rename-onto) | 409 memory_path_conflict_error (+ conflicting_memory_id) |
content_sha256 precondition mismatch | 409 memory_precondition_failed_error |
| Memory or store not found | 404 |
| Content larger than 100 KB | 400 content_too_large — "content must be at most 102400 bytes" |
| Store at its memory-count or size limit | 400 store_full |
| Content looks like a credential | 400 content_secret |
| Write against an archived store | 400 store_archived |
The credential check is real string-matching on write: "memory content appears to contain a credential or API key; remove it before writing. If the credential is real, rotate it." The reference is blunt about why — never store secrets in a memory store. A key written once is replayed verbatim into every later session that mounts the store; use vault environment-variable credentials instead, and if a secret already landed, delete the memory and redact the affected versions.
Point it at your own server#
Two env vars aim the memory-sync client, and both are grounded in the v2.1.227 binary:
| Variable | What It Does |
|---|---|
CLAUDE_CODE_MEMORY_API_BASE_URL | Base URL for the memory API instead of the default. Routes hang off /v1/memory_stores/… under the managed-agents-2026-04-01 beta. |
CLAUDE_CODE_MEMORY_API_TOKEN | Bearer token for that server. When set, the client sends Authorization: Bearer and skips its OAuth refresh. Treated as a credential and masked in logs, like CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY. |
CLAUDE_CODE_DISABLE_ORG_MEMORY | Kill switch. Turns the whole org-memory sync off regardless of the above. |
The sync path is gated by tengu_haze_glass and an allow_memory_sync capability; without both, the client reports no_oauth / no-auth and stays parked. Two user settings expose it in the UI: orgMemoryRead ("Synced project memory (this directory; applies next session)") and orgMemoryWrites ("Synced project memory writes"), and writes require reads to be enabled first — both apply on the next session, not the current one.
The client is a real sync engine, not a thin fetch wrapper. It carries maxConcurrentWrites, maxLineLength, stallTimeoutMs, and deadlineMs knobs, and a typed error set — parse_failed, write_failed, oversized_line, too_many_entries, stream_error, stream_truncated, count_mismatch, decrypt_errors. Its delete behaviour is selectable through tengu_mem_push_delete_mode (corroborate / immediate / never), so a remote deletion does not blindly wipe local state.
What this tells us#
Claude Code's org memory is not a bespoke feature bolted onto the CLI. It is a client for a first-class Managed Agents resource that sits next to /v1/agents, /v1/environments, and /v1/files in the same beta, with immutable versioning, optimistic concurrency, server-side secret scanning, and per-memory audit actors. The two env vars are the seam: point the base URL and token at your own implementation of these routes and Claude Code will sync against it. None of this is in the v2.1.227 changelog — the two env vars ship one release before any note names them, and the full API reference is compiled into the binary as documentation the CLI never surfaces.
This analysis is conducted for educational and research purposes under fair use principles. All trademarks and software referenced belong to their respective owners. This content is not intended to infringe on any intellectual property rights, circumvent any protections, or encourage unauthorized access to proprietary systems.
Sources#
- Managed Agents Memory Stores reference and SDK client, compiled into the Claude Code v2.1.227 binary (
darwin-x64, published August 10, 2026). - Claude Code Official Changelog.
Related Reading#
- Claude Code v2.1.227: A Comment-Thread Triage Agent, an Org Memory Endpoint, and Per-Spawn Bash Clamps. The release these env vars shipped in.
- Claude Code v2.1.218: Quieter Reviews, a Guard on Team Memory, and Two Knobs the Notes Skip. The team-memory write guard that predates org memory.
- Claude Code v2.1.221: Sandbox Credential Masking, a Bash Permission-Bypass Fix, and Silent Artifact Comment Threads. Credential masking in the sandbox.
- Claude Code Version Tracker. Every release analyzed.