Skip to main content
Lab Grimoire
TW EN
Coffee
Give Your AI Agent a Shared Brain Across Multiple Machines
Hands-On

Give Your AI Agent a Shared Brain Across Multiple Machines

On this page

Cover

You spent weeks training the perfect AI assistant — it remembers your preferences, knows your project context, understands your toolchain.

Then you switch computers.

Everything resets to zero.

The Problem Goes Deeper Than You Think

Most people assume "just git clone the config files." But if your workspace lives on iCloud (or Dropbox, OneDrive), things get interesting:

Machine A creates a symlink:
  MyVault → /Users/alice/Library/.../iCloud/MyVault

iCloud syncs to Machine B:
  MyVault → /Users/alice/Library/.../iCloud/MyVault
                    ↑ alice doesn't exist on B! Path broken!

Machine B fixes the symlink:
  MyVault → /Users/bob/Library/.../iCloud/MyVault

iCloud syncs back to Machine A:
  MyVault → /Users/bob/Library/.../iCloud/MyVault
                    ↑ bob doesn't exist on A!

→ Infinite ping-pong. Both machines broken.

This isn't hypothetical — I hit this exact trap setting up my second MacBook.

The Fix: .nosync + Path Resolution Fallback Chain

Before vs After .nosync

macOS has a little-known mechanism: files with .nosync in their name are excluded from iCloud sync.

By leveraging this, each machine can maintain its own symlinks independently:

# ❌ Synced by iCloud — causes cross-machine conflicts
MyVault → /Users/alice/Library/.../iCloud/MyVault

# ✅ .nosync suffix → iCloud skips it, each machine independent
MyVault.nosync → /Users/alice/Library/.../iCloud/MyVault

But renaming alone isn't enough — every script needs to find the right path. I designed a three-tier fallback chain:

# _paths.sh — single source of truth for paths
if [ -e "$WORKSPACE/MyVault.nosync" ]; then
    export VAULT="$WORKSPACE/MyVault.nosync"        # Preferred: .nosync symlink
elif [ -e "$WORKSPACE/MyVault" ]; then
    export VAULT="$WORKSPACE/MyVault"                # Legacy: old symlink name
else
    export VAULT="$HOME/Library/.../iCloud/MyVault"  # Fallback: direct iCloud path
fi

All scripts just source _paths.sh and use $VAULT — they never need to know what the symlink is called.

Horcrux Architecture: One Bootstrap, Full Agent

Architecture

I store all agent config (agents, skills, hooks) in an iCloud Vault directory called 992_Claude_Config/. A new machine just needs one bootstrap script:

iCloud Vault (992_Claude_Config/)     New Machine (~/.claude/)
├── agents/          ──── symlink ────→ agents/
├── skills/ (396)    ──── symlink ────→ skills/
├── hooks/           ──── symlink ────→ hooks/
├── CLAUDE.md.tmpl   ──── template ──→ CLAUDE.md
└── settings.tmpl    ──── template ──→ settings.json

Critical design: ~/.claude/ symlinks point directly to the iCloud container path, not through the workspace symlink. This way, renaming the workspace symlink never breaks Claude Code.

Primary / Secondary: Preventing Memory Conflicts

Two computers share memory files, but only one should run write-heavy scheduled tasks (memory consolidation, literature sync):

# .machine_role
AliceMacBook=primary      ← Full write access
WorkDesktop=secondary     ← Shared resources read-only

Scripts use require_primary() to guard:

from _paths import require_primary
require_primary("memory consolidation")  # Exits if not primary

Since each machine only reads its own hostname line, syncing the same .machine_role file across machines causes no conflicts.

Auto Memory: The Stop Hook

The final piece: automatically recording what was done when each Claude Code session ends.

I built a Stop hook that runs asynchronously after Claude finishes responding:

Claude finishes responding (Stop event)
    ↓
memory_session_log.py (async, non-blocking)
    ↓
git diff --stat → infer what changed
    ↓
≥ 2 files changed → append to episodic.jsonl
    ↓
memory_trigger_check.py → consolidate if threshold met

Auto-generated memory entries look like this:

{
  "id": "ep-2026-03-19-009",
  "type": "refactor",
  "title": "Session auto-log: _paths.py, bootstrap.sh, etc. 26 files",
  "content": "Modified 26 files. 1294 insertions, 114 deletions",
  "importance": 8,
  "source": "stop_hook"
}

source: "stop_hook" lets you distinguish auto-logged from manually-written memories. Zero manual effort — every session gets archived automatically.

Lessons Learned the Hard Way

Some hidden traps I discovered during implementation:

  1. Hardcoded paths are landmines: I found hardcoded Vault paths in 11 scripts. Even though _paths.py existed as the single source of truth, only 4 scripts actually used it. Good design that isn't enforced = no design at all.

  2. Symlink chain breakage cascades: ~/.claude/hooks originally pointed to iCloud through the workspace symlink. Renaming the workspace symlink broke the entire chain — Claude Code's hooks, skills, and agents all went dark. Fix: make ~/.claude/ point directly to the iCloud container path.

  3. Defensive validator design: The memory validator used entry["id"] — crashed on old-format entries missing the id field. Switching to .get("id") with graceful fallbacks made the system far more robust.

Try It Yourself

This system is part of the open-source Ghost In Shell framework:

Clone it, run python3 create_agent.py, and build your first agent in 3 minutes. Then run bootstrap.sh on a second machine — your agent seamlessly works across both.


Your AI assistant shouldn't be locked to one computer. Let it be the same self, wherever you are.

Found this useful?

Follow for new AI × biomedical research notes:

Or buy me a coffee to keep new content coming.

☕ Buy Me a Coffee