Expand description
Native memory RPCs — read/write the agent’s ~/.claude/projects/<sanitized>/memory/
folder that Claude Code uses for autonomous, cross-session fact storage.
Three commands: agent:memory:list — list *.md files in the memory dir agent:memory:read_file — read one file by filename (no path traversal) agent:memory:write_file — write/create one file atomically (tmp→rename)
Spec: docs/specs/SPEC_AGENT_PANE_MEMORY_IDENTITY_MODALS_2026_06_19.md §7
All three also write through into db_agent_native_memory (via
state.id_store) — a durable mirror keyed by the stable
AgentDefinition.id, since the live filesystem path above is
channel-relative by design and not the same across channels/instances for
the same logical agent. list/read_file merge the live-FS view with
the mirror so a file written from one channel stays visible from another.
See docs/specs/SPEC_NATIVE_MEMORY_DURABLE_SYNC_2026_08_07.md.
Structs§
Enums§
- Memory
DirProvenance 🔒 - How an agent’s memory directory was found (SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.1.2, phase M1).
Constants§
- MAX_
DIFF_ 🔒CELLS - Cap on the LCS table’s cell COUNT (from_lines.len() * to_lines.len()),
not on either side’s line count independently — reagent P1: the
original per-side-only cap (20,000 lines each) bounded neither
dimension against the other, so two files each under that cap could
still produce a ~3.2GB
usizetable (20_000 * 20_000 * 8 bytes) in the shared agentmux-srv process. 4,000,000 cells keeps the table under ~32MB (* size_of::<usize>()) regardless of how the two side lengths are distributed — generous for memory files (markdown notes, not logs) while still bounded for any combination of sizes. - MAX_
MEMORY_ 🔒FILE_ BYTES - Cap for both live-FS reads and mirror upserts — one shared limit so a file that’s readable stays within the size the mirror can also store.
Statics§
- IDENTITY_
STORES 🔒 - The identity stores (
id_store,identity_store), attached once at boot, so memory resolution can find the account an agent is linked to — the directory its spawn actually runs Claude in. Its callers hold only the channel store. Unattached (tests): the linked-account step is skipped.
Functions§
- agent_
claude_ 🔒config_ dir - The Claude config dir an agent runs in, as its spawn decides it: the
config dir of the Claude OAuth account it is linked to — the spawn sets
CLAUDE_CONFIG_DIRto it over any env value (resolver::inject) — else its env override, else"", the shared default. #3603: an agent with no env override and a linked account resolved to the shared default, soMemoryListlisted nothing while its memories sat in the account’s dir. - attach_
identity_ 🔒stores - check_
memory_ 🔒base_ sha256 - The optimistic-concurrency check behind
base_sha256(SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.4):Okwhencurrentstill hashes tobase, otherwise an error startingconflict:— the marker the frontend keys its “changed since you started editing” banner on. A file that no longer exists is a conflict too: the draft’s base is gone, and silently re-creating it would undo someone else’s delete. - claude_
config_ 🔒dir_ for_ identity - Compute the
CLAUDE_CONFIG_DIRroot for an agent bound toidentity_id, mirroring the spawn-time isolated-home layout (OAuthConfigDir): - current_
memory_ 🔒content - The content
agent:memory:read_filewould return forpathright now — live FS first (lossy UTF-8, capped atMAX_MEMORY_FILE_BYTES), the durable mirror only when the live file is genuinely absent — WITHOUT that handler’s mirror-upsert side effect.Ok(None)means the file exists in neither place. Used byagent:memory:write_file’sbase_sha256check, which must hash exactly what the caller’s draft was based on (the frontend hashes theread_fileresponse), or every conditional save of a large or non-UTF-8 file would look like a conflict. - find_
active_ 🔒registry_ record_ by_ slug - Find the global named-agent registry’s active record for
agent_id— theAGENTMUX_AGENT_IDrouting slug (derive_slug(display_name)), NOT the record’s owninstance_name(which keeps the original display casing, e.g. “AgentY”). Matching by raw string equality here used to mean this — and every App-API self-lookup endpoint that falls back to it (memory.*, andbundle.self.getonce it’s wired to use this too) — silently 404’d for any agent whose name wasn’t already all-lowercase.derive_slugon both sides makes the comparison consistent with how the slug was actually derived in the first place. Shared so callers outside this module (e.g.app_api::mod:: bundle_self_get_impl) don’t reimplement the same lookup. - legacy_
memory_ 🔒dir_ by_ id - Today’s resolution without spawn records: explicit working directory, else the blank-working-dir guess.
- line_
diff 🔒 - A minimal unified-diff-style line comparison: longest-common-subsequence
based, output lines prefixed
" "(context),"- "(removed,fromonly), or"+ "(added,toonly). No@@hunk headers or context trimming in v1 — every line is included, which is fine for memory files. - list_
all_ 🔒memory_ targets - Every agent whose memory directory is verified — found from its own spawn or its explicit working directory — for the drift detector and the first-sight backfill (SPEC_MEMORY_FOLLOWS_THE_AGENT_2026_09_24.md §2.1.2, phase M1).
- list_
memory_ 🔒targets_ with - memory_
dir_ 🔒for_ agent - Resolve the memory directory for
agent_id. Reads the agent definition and its stored env blob to findCLAUDE_CONFIG_DIR. Returns an error only if the agent cannot be resolved at all — an instance row with a blankworking_directoryfalls through to the registry rather than failing (see the inline note below; that short-circuit was a real bug). - memory_
dir_ 🔒for_ agent_ by_ id - Resolve the live memory directory for an agent identified by its
AgentDefinition.id(UUID) — the same identifier conventionagent:memory:list/read_file/write_fileuse (as opposed tomemory_dir_for_agent’s slug-based lookup, used by different, App-API callers). Shared by those three handlers andbundle.export_for_agent/bundle.import_for_agent(ABF v0.2 §2.3) so all five resolve identically. - memory_
dir_ 🔒for_ blank_ working_ dir - Resolve the memory dir for an agent whose persisted
working_directoryis blank — the DEFAULT state, so this is the common path, not an edge. - memory_
dir_ 🔒for_ cwd - Compute
$CLAUDE_CONFIG_DIR/projects/<sanitized>/memory/for the given working directory and Claude config dir. - memory_
dir_ 🔒for_ registry_ record - Reconstruct one registry record’s absolute memory dir directly (no slug
lookup) — shared by
memory_dir_from_registry(single record, found by slug) andlist_all_memory_targets(every active record, for the fs-watch drift detector’s enumeration). - memory_
dir_ 🔒for_ write_ by_ id - The memory dir an AgentMux write may go to: never an unverified guess.
- memory_
dir_ 🔒from_ latest_ segment - The memory dir of the agent’s latest Claude spawn, from its continuity segments in the global transcript store.
- memory_
dir_ 🔒from_ registry - Resolve a memory dir for
agent_idfrom the global named-agent registry. - memory_
dir_ 🔒from_ spawn - The memory dir of
agent_uid’s latest spawn, if one is on record — for a caller that knows the agent’s id but has no local row for it. - parse_
claude_ 🔒config_ dir - Extract
CLAUDE_CONFIG_DIRfrom aKEY=VALUE\n…env blob. - parse_
frontmatter_ 🔒type - Extract
metadata.typefrom YAML frontmatter. Claude Code memory files nest the type undermetadata:: metadata: type: user A top-leveltype:key is NOT the correct field. - parse_
memory_ 🔒frontmatter_ type - Parse
metadata.typefrom YAML frontmatter (re-exported for MuxAPI). - refresh_
memory_ 🔒mirror_ from_ live_ fs - Refresh
db_agent_native_memoryfrom the live filesystem foragent_id— the same read-then-upsert-if-changed logicagent:memory:listperforms inline (compare each live file’s size+mtime against the mirror, and upsert only when it’s actually changed). This is a NEW, standalone function with the same shape, not a shared implementationlist’s handler was refactored to call — reagent P2, PR #2527 (second round):list’s own inline copy (this file,~agent:memory: list’s handler) still exists separately and has already diverged in one detail (it hard-errors on afile_type()failure; this function silently skips the entry instead). Deduplicating the two into one shared implementation is a legitimate follow-up, not done here to keep this PR’s diff scoped to what ABF v0.2 §2.3 actually needs. - register_
native_ memory_ handlers - resolve_
agent_ 🔒uuid - Resolve
agent_id— the agent SLUG, per the App-API convention (seememory_dir_for_agent’s own doc comment) — to the same stable, canonical identifier (AgentDefinition.id/db_agents.id) the WebSocket RPC surface (agent:memory:write_fileet al., which receives this id directly from the caller and resolves it viaagent_def_get) already keysdb_agent_native_memory_versionsby. - resolve_
memory_ 🔒dir_ by_ id - Resolve an agent’s memory dir with how it was found: its latest spawn first, then its explicit working directory, then the blank-working-dir guess (read-only).
- resolve_
memory_ 🔒dir_ with - validate_
filename 🔒 - Validate a filename: alphanumeric +
-_, must end with.md, no path separators. - validate_
memory_ 🔒filename - Validate a memory filename.
- version_
summary_ 🔒to_ meta - version_
to_ 🔒meta