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:
- Works between all my computers. Multiple laptops, build machine, office machine and phones share one memory.
- Easy to use with any AI agent. I use Claude Code, but memory stays plain Markdown files and plain commands, not locked into one tool.
- Simple first. An MCP integration would be the cooler solution, but hooks are easier; MCP is the second step.
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.
| Option | How it works | Strength | Weak spot |
|---|---|---|---|
Only CLAUDE.md | Write every rule into the instruction files and commit them | No new moving parts | Claude's own learned facts still stay local; rules end up in repos where they don't belong |
| Git repo for the memory folder | Commit and push the memory folders like code | History, diffs | Someone 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 folder | Zero code | Silent "conflicted copy" files, no deletion guard, sync timing unknown when a session starts |
| Symlink to a network share | All machines read one folder directly | One truth, no copies | Offline machine = no memory; a slow share holds up every session start |
| MCP server | Memory offered as tools (read, write, search) to any agent that speaks MCP | Native for every MCP client, no files to copy | More to build; the agent must remember to call the tools, while Claude Code's built-in memory keeps writing local files |
| Central server + local copies | A database is the source, a small client syncs on hooks | Works offline, clear rules for conflicts and deletes | Needs 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.
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.
- Bosun was already there. My tasks, products, marketing and customers live on the Bosun server, with a login and backups. Memory is one more table.
- No manual step. Hooks sync when a session starts, when Claude writes a memory file and when a session ends. With Git I would have to commit and pull, and a forgotten pull means a session with old rules.
- Hooks first, MCP later. An MCP server would give any agent memory as tools, and I plan to add one. Hooks were quicker to build: Claude Code already reads the files, so a sync client and three hooks were enough.
- Works with other agents. Memory is plain Markdown and the commands are plain CLI. Another agent can read the files or run
bosun memoryitself. - Works offline. Each machine has a full local copy, so Claude Code reads memory without network. The hook stops after 8 seconds, so a session never waits on it.
- Conflicts. If both sides changed a file, the server version wins and the local version is saved next to it as a conflict file. At the next session start, Claude merges the two. A cloud folder would just leave a "conflicted copy" nobody sees.
- Protection against mass deletes. If a sync would delete more than 10 files and more than a third of all files, it stops until I force it. A wiped machine can't empty the server.
- Web view and backups. I can read, search and edit memory in the browser, and every Bosun backup includes it.
Details
The whole system is one table on the Bosun server, a sync engine inside the bosun tool and three hooks.
| Part | What it does |
|---|---|
| Memory table on the Bosun server | One row per file: content, hash, version, deleted flag, which machine changed it and when |
Sync engine in bosun | Three-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 status | Shows what would go up, come down or clash |
bosun memory ls [project] | Lists the files on the server with size, time and machine |
| Hooks | SessionStart, PostToolUse (Write/Edit) and SessionEnd each run bosun memory hook |
| Web: Plan › Memory | Browse, search, edit, delete |
| Bosun backup | Every 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 where | Result |
|---|---|
| Here only | Uploaded, but only if the server still holds the agreed version |
| Server only | Written here |
| Deleted on one side | Deleted on the other; the server keeps a tombstone for 90 days |
| Both sides | Server wins; local text kept as <name>.conflict-<host>.md, merged by Claude at the next start |
| Over 10 deletions and over a third of files | Sync 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.
- The PostToolUse hook watches Claude's Write and Edit tools only. A memory file changed through a shell command waits for the next session end, or a manual
bosun memory sync. - Memory is per project folder. A rule saved in a session started in
~/projectsis not loaded by a session started in~/projects/bosun. Rules that must hold everywhere belong in~/.claude/CLAUDE.mdor the project's ownCLAUDE.md.
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:
- 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.
- 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.
- Project keys. Strip the home from Claude Code's folder name so the same project matches on every machine.
- 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.
- Hooks. Add them to
~/.claude/settings.json; the|| truekeeps 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.