Sync Claude Code memory, todos, and gstack context across Macs via iCloud Drive. End-to-end encrypted. Supports multiple sync sources.
pipx install git+https://github.com/kbitz/mind-meld.git@latestNot on PyPI — install straight from GitHub. The @latest ref is a branch the release workflow force-advances to each tagged release, so you always get the newest released version (never untagged work-in-progress off main) and plain pipx upgrade keeps working (see below).
pipx upgrade mind-meldThat's it. Because the install tracks the moving latest branch (not a frozen tag), pipx upgrade re-resolves it to the newest release and lands it.
Stuck on an old version? If you ever installed or upgraded with the old --force …@vX.Y.Z form, your install is pinned to that exact tag — pipx re-resolves the frozen ref on every pipx upgrade and reports your current version as "latest" forever. A git tag never moves; a branch does. Run this once to switch onto the latest branch, after which plain pipx upgrade mind-meld works:
pipx install --force git+https://github.com/kbitz/mind-meld.git@latest(This is exactly the command mm's auto-upgrade nudge prints.)
mm init # configure iCloud storage + passphrase
mm push # upload memory/todos
mm pull # download from another deviceConfig lives at ~/.config/mind-meld/config.toml — not tied to your current directory. Install mm anywhere, run from anywhere; it always syncs the sources configured in your global config (~/.claude and ~/.gstack by default).
pipx install git+https://github.com/kbitz/mind-meld.git@lateston the new machine.mm init— point it at the same iCloud folder as your first Mac and enter the same passphrase. This registers the new device against the existing roster.mm pull— downloads everything the other machine(s) have pushed.mm push— uploads anything this machine has that the others don't.
Push and pull on each Mac over time and the state converges toward the union of all three. Nothing is destroyed:
.jsonlfiles andMEMORY.mddeep-merge by line-union (deduped, sorted byts). Entries from all machines accumulate — this is why telemetry, learnings, and timeline files stay coherent across devices.- Other divergent files use mtime-skip: if your local file is newer than the remote, pull leaves it alone. Otherwise the remote wins the canonical path and your local version is preserved as
<stem>.sync-conflict-<ts>-<device>.<ext>sitting next to it (Syncthing convention). See Handling conflicts below. - Deletions propagate via tombstones in the manifest — delete a file on one machine, and
mm pullon the others removes it cleanly.
First-run-from-divergent-state is explicitly supported: if each Mac already has its own memory/todos/analytics before you first run mm init, the three-way sync will merge the JSONLs, download missing files, and flag any true content conflicts as .sync-conflict-* for you to triage with mm resolve.
mm init automatically pins your iCloud storage folder so blobs stay resident on this Mac. Without pinning, iCloud may evict cold blobs to save local disk and mm pull then blocks on iCloud File Provider materialization — fine over time, but slow on a fresh Mac's first sync.
Auto-pin runs brctl download <storage_path> (Apple's iCloud File Provider CLI) once at init. It's non-destructive, idempotent, and asynchronous: brctl returns immediately while iCloud materializes files in the background. You'll see a Storage pinned for fast pulls line on success, or a Finder right-click tip if brctl errors.
If you ever want to undo the pin (free up local disk):
brctl evict ~/Library/Mobile\ Documents/com~apple~CloudDocs/mind-meld…or in Finder, right-click the folder and choose Remove Download.
If your storage path is not under iCloud Drive (e.g. a custom local folder or a different cloud sync), auto-pin is silently skipped — the slow-pull case only applies to iCloud-managed paths.
Mind Meld includes autopull and autopush commands designed for Claude Code — they run silently, never prompt, and output a single line summary (or nothing if already in sync).
Add the following to your global ~/.claude/CLAUDE.md to have Claude automatically sync at the start and end of each conversation:
# Mind Meld
At the **start of each conversation**, run:
\`\`\`bash
mm autopull
\`\`\`
- **No output:** Already in sync. Continue silently.
- **Any output:** Tell the user what was synced.
At the **end of each conversation** (when the user is wrapping up, says goodbye,
or you've completed the requested task), run:
\`\`\`bash
mm autopush
\`\`\`
- **No output:** Nothing to push. Say goodbye normally.
- **Any output:** Tell the user what was pushed.
If `mm` is not installed, both commands will fail silently — no action needed.mm autopullchecks all other registered devices for changes and applies them locally. It writes a.mind-meld-log.mdbreadcrumb to each affected project so Claude Code knows what changed.mm autopushbuilds a manifest of local memory/todos, diffs against the last push, and uploads only what changed.- Both commands acquire a lockfile, never prompt for input, and exit gracefully on any error (so they never block Claude Code).
- "Silent" means no chatter on the happy path. Load-bearing degradation warnings — corrupt-manifest recovery, "no sync sources" misconfig, durability fsync failure, per-file pull failures — still reach stderr as a single
mm: warning: ...line so a wedged background sync surfaces instead of rotting. Autopush writes ano-sourcesbreadcrumb (separate fromsuccess) when the config has no sync sources. Autopull writes adegradedbreadcrumb (separate fromsuccess) when any of four conditions fire during an otherwise-successful pull: fsync durability failure, corrupt peer manifest, unknown source from a peer, or per-file apply failure. Thedetailfield enumerates which signals fired.mm statusand any monitoring on top of it can catch both wedge and partial-degradation cases. - Auto-upgrade nudge (v0.9.5). Once per 24h,
mm pull/mm push(including the autopull/autopush variants) check GitHub for a newer release tag and emit a singlemm: notice: <old> → <new> available — run pipx install --force git+...@latestline on stderr if you're behind.mmnever invokes pipx itself; you run the printed command. The command tracks the movinglatestbranch (not a frozen tag), so it always lands the newest release and — crucially — rewrites any previously tag-pinned install's recorded URL onto@latest, after which plainpipx upgrade mind-meldworks (see Upgrading). Disable with--no-check-versionfor one invocation, or set[upgrade] auto_check = falsein~/.config/mind-meld/config.tomlto disable persistently. Thenotice:prefix is distinct fromwarning:(reserved for data-at-risk signals). This is a leading-edge complement to the v0.9.2 fleet-version refusal, which only fires after a newer peer pushes data — the nudge fires before that, ideally making the refusal a backstop nobody hits.
| Command | Description |
|---|---|
mm --version |
Print the installed version and exit |
mm init |
Configure device, storage path, passphrase |
mm push |
Push with verbose output |
mm pull |
Pull with verbose output |
mm pull --conflict-mode prompt |
Pick a winner per-file at pull time instead of auto keep-both |
mm pull --conflict-mode fail |
Preflight all files; exit 3 (no writes) if any would conflict — for CI |
mm status |
Show local vs remote state |
mm devices |
List registered devices |
mm devices --format=json |
Same data as a JSON array on stdout — for scripting (used by /retro-fleet) |
mm diff |
Dry-run: show what would change (annotates each file with write / merge / skip / conflict) |
mm gc |
Delete orphaned blobs |
mm gc --conflicts |
Also delete .sync-conflict-* files older than 30 days |
mm sources |
List configured sync sources |
mm conflicts |
List unresolved .sync-conflict-* files with age and canonical sibling |
mm resolve [PATH] |
Interactively pick a winner for conflict files (shows unified diff). Exits 1 if any per-conflict rename/unlink/read fails so CI / scripts can detect partial failure (the walk still continues through every conflict). |
mm retro-fleet [WINDOW] |
Render the fleet retrospective markdown to stdout (default 7d). The /retro-fleet Claude Code skill calls this under the hood; safe to run directly for scripted exports (mm retro-fleet 30d > /tmp/retro.md). --no-author-filter renders every fleet commit instead of just yours. |
mm install-skills |
Force-install (or repair) the ~/.claude/skills/retro-fleet symlink. The push-time self-heal handles this automatically; this is the explicit knob for fresh-machine setup or post-cleanup recovery. |
If ~/.gstack is detected during mm init, it is automatically added as a sync source. gstack uses a whitelist walker — unlike the Claude source (which has hardcoded subdirs), the gstack source only syncs the directories and files you explicitly list.
Defaults out of the box:
include_dirs:projects/,analytics/,retros/include_files:retro-context.md,greptile-history.md,.completeness-intro-seen,.telemetry-prompted,.proactive-prompted,.welcome-seen,.codex-desc-healedexclude_patterns:config.yaml,projects/*/repo-mode.json,projects/*/land-deploy-confirmed,analytics/.last-sync-*(per-machine artifacts that churn-conflict on every pull —config.yamlholds gstack's version-check tracking;analytics/.last-sync-*are per-machine cursor files tracking each device's progress through gstack's local analytics jsonls)
This covers the common cross-machine cases — in particular, /retro global sees activity from all your Macs because analytics/skill-usage.jsonl, analytics/eureka.jsonl, and projects/<slug>/timeline.jsonl are all .jsonl files that set-union merge on pull (deduped, sorted by ts). Append-only telemetry from 3 machines converges cleanly into one timeline.
Not synced by default (machine-local by design): sessions/, sidebar-sessions/, slug-cache/, worktrees/, builder-profile.jsonl, developer-profile.json. If you want any of these on every Mac, add them to your config (see below).
Adding files or dirs: edit ~/.config/mind-meld/config.toml and extend the gstack source. For example, to sync the writing-style prompt marker and a custom notes file:
[[sync.sources]]
name = "gstack"
path = "~/.gstack"
type = "generic"
include_dirs = ["projects", "analytics", "retros"]
include_files = [
"retro-context.md",
"greptile-history.md",
".completeness-intro-seen",
".telemetry-prompted",
".proactive-prompted",
".welcome-seen",
".codex-desc-healed",
".writing-style-prompted", # added (your custom extra)
]
exclude_patterns = [
"config.yaml",
"projects/*/repo-mode.json",
"projects/*/land-deploy-confirmed",
]Supplying sync.sources replaces the defaults wholesale — copy the full list, don't just add your extras. Run mm sources to confirm the resolved source list.
Useful flags:
mm sources— show the configured source list with theirEnabledstate and file counts.mm pull --source gstack— pull only the gstack source (skip Claude).
If ~/.gstack-extend/ is detected during mm init, it is automatically added as a sync source — sibling of the gstack treatment above. The whitelist walker is scoped to projects/ only; per-machine bookkeeping at the root (config, just-upgraded-from, update-snoozed) is excluded by construction. Anything gstack-extend skills (pair-review, test-plan, full-review) persist under ~/.gstack-extend/projects/<slug>/ rides this same source so cross-machine resume keeps working as the gstack-extend feature surface grows.
Existing installs see this as a New source available: gstack-extend hint on next mm status. Opt in with mm enable-source gstack-extend or dismiss with mm disable-source gstack-extend — same shape as every other source toggle.
config.toml lives at ~/.config/mind-meld/ and is never synced — making it the natural home for per-device preferences. To turn off a source on one machine without affecting the others:
mm disable-source gstack # this Mac only; iCloud peers untouched
mm enable-source gstack # turn it back onThe on/off state lives in [sync].disabled_sources = ["gstack"]. Disabling does NOT delete your [[sync.sources]] entry — re-enabling preserves any customizations like include_dirs or exclude_patterns.
mm sources shows the toggle state as an Enabled column. mm status calls out disabled sources in a one-line breadcrumb so future-you doesn't forget gstack is off and re-debug "why isn't this syncing".
Forward-compat for not-yet-shipped sources. When mm adds a new source to its defaults (e.g. codex in a future release), upgraders don't get auto-enrolled — mm status surfaces a one-shot New source available: codex. Run mm enable-source codex to sync. hint. To pre-disable a name before it ships:
mm disable-source codex --force # accepts unknown names for forward-compatmm reconfigure-sources re-runs the picker against your current config + new defaults, in case you want to revisit every choice at once.
Mind Meld v0.11.0 ships a Claude Code skill that stitches engineering activity from every Mac in your fleet into one accurate retrospective. Every mm push writes a per-device daily JSONL row (commit metadata, sessions count, sync activity) to the synced mm-events source, so any machine can read the union and produce a fleet-wide picture.
Inside Claude Code:
/retro-fleet 7d # last week, default if you omit the window
/retro-fleet 30d # last month
/retro-fleet 90d # last quarter (the retention ceiling)
The skill renders a paste-ready markdown retro — drop it into iMessage, Slack, or email. Commits are deduped across machines via (canonical remote URL, sha) so the same PR landed once but pushed from two laptops counts as one.
v0.12.0 output shape. A pixel-aligned ASCII card sits at the top — single-line NOTEWORTHY plus three TOP WORK theme bullets the skill synthesizes from the underlying data — followed by the full markdown body (commit-type mix, peak hours, commit bursts, ship-of-the-window, weekly buckets when window ≥14d). On repeat runs with the same window a ## Trends vs last retro block surfaces deltas vs the most recent matching snapshot stored at ~/.local/share/mind-meld/retros/. The card is generated via a two-pass flow: first invocation emits an MM_THEMES_PROMPT JSON sidecar, the skill synthesizes themes + noteworthy, then re-invokes mm retro-fleet <window> --theme … --noteworthy … --name … --no-save to render the final card. Direct CLI users (no skill) get the body without the card.
Under the hood the skill invokes mm retro-fleet <window> (v0.11.22+) — the same CLI surface is available directly for scripted exports (mm retro-fleet 30d > /tmp/retro.md) or terminal use, just without the LLM judgment layer the skill adds (natural-language window parsing, error translation). The earlier python -m mind_meld.skills.retro_fleet.aggregator form is a development-checkout fallback only; pipx-installed mm lives in an isolated venv that bare python / python3 can't import from, so the skill's documented invocation routes through the mm console-script (always on PATH wherever mm is installed).
Token usage and cost (v0.11.14). Under the "Claude Code activity" section the retro now answers: how much did Claude Code consume this window, was it Sonnet- or Opus-heavy, did the cache do its job, what would this have cost at API list rates. The numbers come from ~/.claude/projects/<encoded>/*.jsonl plus subagent jsonls under <session-uuid>/subagents/agent-*.jsonl (subagents contribute to the parent project's totals — ~50% of usage on a heavy fleet — but don't double-count as separate sessions). The cache lives at ~/.config/mind-meld/session-tokens.json, warms inline on mm init and the first interactive mm push (~3 seconds, telegraphed via mm: warming token cache (one-time, ~3s)...), and is reaped by mm gc once a jsonl disappears or its tokens are older than 90 days. Cost estimates use API list prices and explicitly say so — they don't account for subscription plan pricing.
The skill is auto-installed at ~/.claude/skills/retro-fleet on mm init, and self-heals every push (24h-TTL gated, ~1 syscall in steady state) so a pipx reinstall rebuild can't leave you with a dangling symlink. If you already have your own file at that path, mm leaves it alone and prints a one-time mm: notice: so you know.
Caveats the output is honest about:
- Asking for a window longer than 90 days surfaces a tail breadcrumb —
mm gcreaps event files older thanEVENTS_RETENTION_DAYS(90), so that's the data ceiling. - Peers still on pre-v0.11.0 emit the older v=1 sessions-snapshot schema (delta semantics). The aggregator omits their session counts honestly rather than overcounting; you'll see
Sessions count incomplete: peer X is on pre-v0.11.0until they upgrade. - Devices that haven't pushed during the window are flagged as fleet-incomplete instead of silently dropped.
To filter to your own commits only, the skill consults git config --global user.email plus any [retro].author_emails aliases in ~/.config/mind-meld/config.toml. Pass --no-author-filter to render every fleet commit. To override the events directory (custom mm-events path), set MM_EVENTS_DIR=/path/to/events before invoking.
If you edit the same file on two machines before syncing, mm pull never destroys your local edits. It follows the Syncthing convention: the incoming remote version wins the canonical path, and your local version is preserved as <stem>.sync-conflict-<YYYYMMDD-HHMMSS>-<device>.<ext> sitting next to it. If your local file is newer than the remote (by mtime), pull leaves it alone — convergence happens on the next push.
Managing conflicts:
mm conflicts— list every.sync-conflict-*file across your sources, with age and canonical sibling.mm resolve— walk each conflict interactively. Shows color LOCAL/REMOTE banners (with peer-name attribution when the conflict file's device prefix matches a registered peer), created/modified timestamps for each side plus a-> SIDE is newer by Nrecency verdict, a 3-number divergence summary, the unified diff, and prompts:(m)erge(accept LCS-merged result) /(l)ocal(keep your edits) /(r)emote(overwrite with peer's bytes) /(n)ewer(keep whichever was modified more recently) /(p)romote(keep BOTH — give the conflict file its own first-class filename) /(s)kip(leave both files) /(a)bort(stop the walk). The default key is always(s)kip— Enter never auto-accepts a merge or a recency guess. The merge uses LCS(local, remote) as a synthetic ancestor so additive edits on either side land cleanly; same-region edits show as<<<<<<<markers and (m) stays available. Binary content suppresses (m);(n)eweris offered only when both sides' mtimes are readable and re-prompts on an exact tie (it never guesses). The remote side's "created" is shown aspulled— it is the local sync time, not the peer's real creation (the manifest carries only modified time). Acquires the mm lockfile so autopull can't race your decision. Pre-1.0 lettersb/bothare aliased to(s)kipwith a one-time stderr notice.mm pull --conflict-mode prompt— prompt per-conflict during the pull itself instead of auto keep-both. Shows the same per-side timestamps + recency verdict (display only — no(n)ewershortcut here, since pull already keeps your file when it is the newer one).mm pull --conflict-mode fail— preflight all files; if any would conflict, print the list and exit 3 (no writes) so CI can block on human review. Exit 3 is distinct from typer's usage-error exit 2, so a stale script still passing the removed--no-promptflag can't be mistaken for a conflict refusal.mm gc --conflicts— reap stale conflict files older than 30 days.mm diff— predicts each modified file's pull outcome (write / merge / skip / conflict) before you run pull.
See SPEC.md for full documentation.