Field notes · Agentic AI

One AI memory for every machine

My AI coding agent had to learn the same rules again on every computer. Now it learns them once: a small sync server, three hooks and plain Markdown files.

Two laptops, a phone and a small build computer on a grey desk, each tied by an amber thread to one paper notebook in the middle.

Bosun is my personal operations hub: a self-hosted web app plus a command-line tool, bosun, that runs my task management, product management (the pipeline of retro games I build), marketing and customer management. This article is about how its server became the shared memory of my AI coding agent.

Issue

Claude Code remembers what matters between sessions, but only on the machine where it learned it. Its memory is a folder of small Markdown files, ~/.claude/projects/<folder>/memory/, with a MEMORY.md index loaded into every new session and one file per fact.

That folder is local and per project. A second Mac, or the build machine, starts from nothing: it doesn't know that deploys need a yes first, that an Amiga means an A500, or that no commit may mention the assistant. Every rule has to be taught again, and the copies drift apart the moment one machine learns something new.

Memory is small (about 175 files across 20 project folders today), but it is the part of the setup that took longest to earn. Losing it, or keeping three versions of it, costs real time.

What I wanted:

Possible solutions

Six ways to share one folder between machines were on the table. Each fixes the copy problem; they differ in what happens when two machines change the same file.

OptionHow it worksStrengthWeak spot
Only CLAUDE.mdWrite every rule into the instruction files and commit themNo new moving partsClaude's own learned facts still stay local; rules end up in repos where they don't belong
Git repo for the memory folderCommit and push the memory folders like codeHistory, diffsSomeone has to commit and pull; a forgotten pull means a stale session, a merge conflict blocks it
Cloud folder (iCloud, Dropbox)Symlink ~/.claude/projects into a synced folderZero codeSilent "conflicted copy" files, no deletion guard, sync timing unknown when a session starts
Symlink to a network shareAll machines read one folder directlyOne truth, no copiesOffline machine = no memory; a slow share holds up every session start
MCP serverMemory offered as tools (read, write, search) to any agent that speaks MCPNative for every MCP client, no files to copyMore to build; the agent must remember to call the tools, while Claude Code's built-in memory keeps writing local files
Central server + local copiesA database is the source, a small client syncs on hooksWorks offline, clear rules for conflicts and deletesNeeds a server and a client to write

Chosen solution

The Bosun database is the single source of memory; every machine's memory folder is a copy kept in step by three Claude Code hooks. Claude Code itself works exactly as before: it reads and writes plain files in its usual folder and never knows the server exists.

Every machine keeps a copy; the Bosun database decides Laptop Claude writes memory/*.md Hooks: start, write, end each runs bosun memory hook base hashes in memory-state Bosun server table memory: the source hash + version per file server wins a clash tombstones for 90 days Other Macs and build box same folders under home same three hooks pulls changes at start Web: Plan, Memory browse, search, edit, delete Bosun backup memory.json + every file sync sync
Memory sync: every machine, one server.

A session start pulls what changed on the server, every memory write pushes that one file up within seconds, and a session end does a final pass. The web and the backups read the same table.

Why

I chose the server because it syncs on its own and has a fixed rule for conflicts.

Details

The whole system is one table on the Bosun server, a sync engine inside the bosun tool and three hooks.

PartWhat it does
Memory table on the Bosun serverOne row per file: content, hash, version, deleted flag, which machine changed it and when
Sync engine in bosunThree-way compare of the local copy, the server and the last agreed hash
bosun memory sync [--force]Sync now; --force goes past the deletion guard
bosun memory statusShows what would go up, come down or clash
bosun memory ls [project]Lists the files on the server with size, time and machine
HooksSessionStart, PostToolUse (Write/Edit) and SessionEnd each run bosun memory hook
Web: Plan › MemoryBrowse, search, edit, delete
Bosun backupEvery backup carries all memory files

Project keys. Claude Code names each memory folder after the working directory, so ~/projects/bosun becomes -Users-roger-projects-bosun. The sync turns that into a key relative to the home, projects-bosun, and maps it back into the other machine's home. Same folder layout, any user name.

Sync rules, per file. The base is the hash both sides agreed on last time, stored in ~/.config/bosun/memory-state.json.

Changed whereResult
Here onlyUploaded, but only if the server still holds the agreed version
Server onlyWritten here
Deleted on one sideDeleted on the other; the server keeps a tombstone for 90 days
Both sidesServer wins; local text kept as <name>.conflict-<host>.md, merged by Claude at the next start
Over 10 deletions and over a third of filesSync stops until bosun memory sync --force

What memory is, and what it isn't. Memory holds only the facts Claude decides to keep: corrections, preferences, project context, pointers. A normal conversation leaves it untouched, which is why bosun memory ls can look the same for days. It changes when you say "remember…", correct how Claude works, or Claude learns something future sessions need.

Two traps found on day one.

How you can do it yourself

You need three things: somewhere to store files with a version, a small sync client, and Claude Code hooks that call it. Nothing in Claude Code has to change.

Steps:

  1. Store. Any server that keeps each file with its content, hash, version and a deleted flag, and refuses a write based on an outdated version. A small web route with SQLite is enough.
  2. Client. A script that lists local memory files, fetches server changes since the last version it saw, and applies the rules in the table above. Keep the agreed hash per file in a local state file; that base is what makes a three-way compare possible.
  3. Project keys. Strip the home from Claude Code's folder name so the same project matches on every machine.
  4. Safety. Keep a tombstone for deletes, keep the loser of a clash as a side file, and stop when a sync would delete a large share of files.
  5. Hooks. Add them to ~/.claude/settings.json; the || true keeps a sync error from ever blocking a session:
{
  "hooks": {
    "SessionStart": [{"hooks": [{"type": "command", "command": "my-memsync hook || true"}]}],
    "PostToolUse": [{"matcher": "Write|Edit", "hooks": [{"type": "command", "command": "my-memsync hook || true"}]}],
    "SessionEnd": [{"hooks": [{"type": "command", "command": "my-memsync hook || true"}]}]
  }
}

In the PostToolUse hook, read the written path from the hook's input and do nothing unless it sits in a memory/ folder, so ordinary edits cost no network call. Give every call a short timeout and write errors to a log, never to the session.

Next step, MCP. Put an MCP server in front of the same store, with tools such as read, write and search memory. Every MCP-capable agent then gets the same memory natively, and the hooks stay as the file-based path for Claude Code.

Cloud sessions on claude.ai/code have no local client, so they are not synced; they start from whatever the repo's CLAUDE.md says.