Skip to main content

Module native_memory_handlers

Module native_memory_handlers 

Source
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§

ResolvedMemoryDir 🔒

Enums§

MemoryDirProvenance 🔒
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 usize table (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_DIR to 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, so MemoryList listed 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): Ok when current still hashes to base, otherwise an error starting conflict: — 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_DIR root for an agent bound to identity_id, mirroring the spawn-time isolated-home layout (OAuthConfigDir):
current_memory_content 🔒
The content agent:memory:read_file would return for path right now — live FS first (lossy UTF-8, capped at MAX_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 by agent:memory:write_file’s base_sha256 check, which must hash exactly what the caller’s draft was based on (the frontend hashes the read_file response), 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 — the AGENTMUX_AGENT_ID routing slug (derive_slug(display_name)), NOT the record’s own instance_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.*, and bundle.self.get once it’s wired to use this too) — silently 404’d for any agent whose name wasn’t already all-lowercase. derive_slug on 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, from only), or "+ " (added, to only). 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 find CLAUDE_CONFIG_DIR. Returns an error only if the agent cannot be resolved at all — an instance row with a blank working_directory falls 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 convention agent:memory:list/read_file/write_file use (as opposed to memory_dir_for_agent’s slug-based lookup, used by different, App-API callers). Shared by those three handlers and bundle.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_directory is 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) and list_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_id from 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_DIR from a KEY=VALUE\n… env blob.
parse_frontmatter_type 🔒
Extract metadata.type from YAML frontmatter. Claude Code memory files nest the type under metadata:: metadata: type: user A top-level type: key is NOT the correct field.
parse_memory_frontmatter_type 🔒
Parse metadata.type from YAML frontmatter (re-exported for MuxAPI).
refresh_memory_mirror_from_live_fs 🔒
Refresh db_agent_native_memory from the live filesystem for agent_id — the same read-then-upsert-if-changed logic agent:memory:list performs 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 implementation list’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 a file_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 (see memory_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_file et al., which receives this id directly from the caller and resolves it via agent_def_get) already keys db_agent_native_memory_versions by.
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 🔒