Skip to main content

Git Memory Sync (Symlink SSOT)

Soul Recall reads memory from plain markdown files. This guide shows how to keep one git-versioned source of truth (SSOT) for your memory and sync it across machines or agents — without changing any Claude Code setting, and without an encryption layer.

Which memory sync do I need?

ClawSouls offers three ways to move memory around. Pick by your goal:

ApproachWhat it isBest for
Memory Sync (clawsouls sync)age-encrypted files pushed to a private GitHub repo; pull & decrypt on any deviceA single user across devices, with data encrypted at rest
Swarm MemoryGit branch-per-agent plus a persona-aware merge engineMultiple agents co-authoring one soul, with conflict resolution
This guide — Git SSOT via symlinkSymlink Claude Code's native memory dir into a git repo; one plain-markdown source of truthOne versioned SSOT shared across machines/agents, no config change

The rest of this guide covers the Git SSOT via symlink approach.

Background: two memory locations

memoryRoots() (in the plugin's hooks/memory-retrieve.js and hooks/memory-index.js) reads memory from a union of:

  • <cwd> and <cwd>/memory — your project working directory (typically inside your project's git repo).
  • ~/.claude/projects/<encoded-cwd>/memory — Claude Code's per-project memory location.

It reads, merges, and dedupes all of them, so recall keeps working wherever files live. The downside: memory can end up split across both places and drift, with no single source of truth and nothing to sync.

Claude Code writes its native MEMORY.md / auto-memory into ~/.claude/projects/<encoded-cwd>/memory, and there is no setting to redirect that path. So you can't simply point it at your repo.

<encoded-cwd> is your project path with every non-alphanumeric character replaced by -:

  • macOS/Linux: /Users/you/projects/my-agent-Users-you-projects-my-agent
  • Windows: D:\code\my-agentD---code-my-agent

Make ~/.claude/projects/<encoded-cwd>/memory a symlink (Windows: a directory junction) to a folder inside a git repo. Claude Code keeps writing to its fixed path, but that path now physically lands in git — so memory is versioned and syncable, with zero Claude Code config changes.

Fresh setup (nothing to preserve in the native location)

If the native folder already holds memory you care about, do the Consolidation steps below first.

macOS/Linux:

NATIVE="$HOME/.claude/projects/<encoded-cwd>/memory"
REPO_MEM="<path-to-your-repo>/memory" # this folder is tracked in git
mkdir -p "$REPO_MEM" "$(dirname "$NATIVE")"
rm -rf "$NATIVE"
ln -s "$REPO_MEM" "$NATIVE"

Windows (Command Prompt — a junction needs no admin rights):

rmdir /S /Q "%USERPROFILE%\.claude\projects\<encoded-cwd>\memory"
mklink /J "%USERPROFILE%\.claude\projects\<encoded-cwd>\memory" "<path-to-your-repo>\memory"

Then git add memory/ && git commit versions your memory. Verify it resolves:

ls "$NATIVE"            # should list your repo's memory files
readlink "$NATIVE" # should print the repo path (macOS/Linux)

Consolidation (memory already exists in both places)

Merge before symlinking, and lose nothing:

  1. Back up both directories to a timestamped folder (cp -R). Do this first, always.
  2. Union into the SSOT (your repo's memory/): copy every file unique to each side. For files present in both, keep the newer version on top and preserve the older one below a clear separator — don't blind-overwrite, because a newer file is sometimes shorter and would silently drop content.
  3. Verify every file from the native side now exists in the repo.
  4. Replace the native directory with a symlink/junction to the repo memory/ (commands above).
  5. Commit.

Swarm: sharing memory across agents

To share memory between agents/machines (e.g. a Mac and a Windows box):

  1. Use one private git repo for memory. Memory often contains sensitive context — keep the remote private.
  2. On each machine, symlink/junction the native location into a local clone of that repo's memory/.
  3. git pull / git push to sync. Daily-log files are effectively append-only (few conflicts); index/topic files may occasionally need a manual merge.

Hybrid: shared knowledge + per-agent daily logs

When several agents share one memory repo but do different work, a single flat pool lets each agent's day-to-day logs pollute the others' recall. Split it:

  • Shared (repo memory/ root): MEMORY.md, topic-*.md, feedback-*.md, reference-*.md, project_*.md, topic-map.json — strategic knowledge every agent reads/writes. Merges via git.
  • Per-agent (memory/daily-<agent>/): each agent's daily logs (YYYY-MM-DD.md, plus archive/). Separate subfolders never collide.

All agents still symlink/junction the native location to the same memory/ folder — one link. The link is created locally per machine and is never committed to git, so there is no cross-platform issue (macOS/Linux ln -s, Windows mklink /J). Route daily logs to the per-agent subfolder by setting it in that project's CLAUDE.md, e.g. Daily log → memory/daily-<agent>/YYYY-MM-DD.md.

Note on indexing: memoryRoots() currently reads only the top level of each root, so shared files (at memory/ root) are in semantic recall, while daily logs (in a subfolder) are read by path at session start but not semantically indexed. To also put per-agent daily logs in semantic recall, index the shared root plus only the current agent's daily-<agent>/ folder (a recursive-with-scope walk) — keeping isolation.

Soul Recall reads the union and skips superseded entries, so archive stale files with frontmatter instead of deleting them:

---
status: archived
---

(status: superseded or a superseded_by: field work too.) This keeps history without polluting recall.

Windows path-encoding gotcha

Because Claude Code encodes the project directory as [^a-zA-Z0-9] → -, a tool that derives the native path with only a forward-slash replacement:

cwd.replace(/\//g, '-')      // misses ':' and '\' on Windows

won't match the Windows folder name (D:\code\my-agentD---code-my-agent) and will read zero files from the native location. Use a full replacement, which produces identical results on macOS/Linux:

cwd.replace(/[^a-zA-Z0-9]/g, '-')