Turkish version: blog.arasmehmet.com
What's in here:
- Hot-swap pain
- Why not just X?
- Setup at a glance
- The hub, INDEX.md
- Agent workflow rules
- Privacy guardrails
- Language regime
- Conflict diagnostics
- Hermes write example
- Smoke test
- What's still risky
- Takeaway
- Resources
- Agent-ready implementation brief
1. Hot-swap pain
I run three AI agents in active rotation. Claude Code is my main driver for coding work day to day. Codex CLI steps in when Claude's quota runs out mid-task, or when I want a second opinion from a different model on the same problem. Hermes Agent handles the personal-life side of the setup: travel logistics, errands, language study.
Each agent starts from a different instruction file and a different conversation history. Claude Code reads CLAUDE.md. Codex reads AGENTS.md. Hermes reads SOUL.md. Those files tell each agent how to behave, but they are not a shared memory layer. The moment I switch from one agent to another, the next agent does not inherit the facts I taught the previous one: what I am working on, what changed yesterday, what I already asked it to remember.
For a while I solved this by re-explaining my context at the start of each session. After three rounds of "I am working on X, I prefer Y, please do not Z," the cost of re-priming an agent outgrew the value of the answer it produced.
The setup I actually wanted: one shared file tree that all three agents read and write. Memory follows the user, not the agent.
2. Why not just X?
The "AI memory" wave has produced several defaults that an engineer might reach for. Each one is wrong for this specific problem, in a specific way.
2a: Why not Obsidian
Obsidian is a desktop GUI. The vault on disk is just markdown, so technically an agent can read it, but Obsidian's plugin runtime, sync service, and conflict UI all assume a human is sitting in front of a window. A home device or a small VPS usually has no display, so Obsidian itself cannot run there. Obsidian Sync is a paid proprietary service, and Syncthing already does the file-sync part for nothing. The plugin sandbox is meant for human workflows: it cannot expose stable cross-process hooks for an external agent to attach to. Obsidian is the right tool for a human note-taker. It is not the right substrate when three agents must read and write the same files.
2b: Why not a vector DB or Mem0 or Letta
Vector DBs and managed memory services like Mem0 or Letta solve a real problem: similarity search over millions of documents for a multi-tenant product. None of that applies here. My total corpus is around fifty files. I want exact recall, not similarity.
Embeddings cost money on every write and add a runtime dependency that every agent has to integrate with. Flat markdown costs nothing to write and reads with grep. SDK constraints differ across agents: Claude Code can call one API, Codex another, Hermes a third. Markdown requires no SDK. When a vector DB returns the wrong chunk, I have to dig through a binary index. When a markdown file is wrong, the debugger is cat file.md.
For multi-tenant production agents over millions of documents, vector DB is the right tool. For one user's notes across three agents, it is over-engineering.
2c: Why not iCloud or Dropbox sync
iCloud and Dropbox already sync files across machines, so why not use them. Two reasons. First, both are closed-source: when something goes wrong the conflict handling is whatever the vendor wrote, and the resolution UI is built for humans choosing between two photos, not for an agent diagnosing a write race. Second, the Linux story differs: iCloud has no Linux client, so a Linux home device cannot participate; Dropbox ships a Linux daemon, but it adds a runtime dependency to keep alive, and the conflict file format is opaque to an agent. Neither service offers granular per-folder sync rules, and file rename or move semantics differ across platforms in subtle ways. Syncthing is open-source, gives me an explicit conflict file format with device IDs, exposes sync state via a local API, and has no per-vendor lock-in. The smaller surface area is also the entire reliability budget I have.
3. Setup at a glance
This article does not teach Syncthing from zero. It assumes one bidirectional synced folder exists across two or more machines. The reusable part is the memory contract layered on top of that folder. If you need to install Syncthing, the official getting-started guide covers it well: https://docs.syncthing.net/intro/getting-started.html
The setup is three machines, three Syncthing peers, and one shared folder called vault-shared. The folder syncs bidirectionally across all three peers. End-to-end propagation, write on machine A to visible on machine C, takes around ten seconds in my setup.
Why three machines? The Mac laptop is my daily driver; that is where I work. The utility VPS is always on, which makes it the propagation relay: if the Mac is asleep, the home device still has someone to sync with. The home device hosts the Hermes Agent and is small but reliable enough to run a long-lived service.
Each agent has filesystem access to its local copy of vault-shared. The agents do not know that Syncthing exists. They read and write local files; the folder happens to be kept consistent with two other machines in the background. From the agent's perspective, the file at vault-shared/INDEX.md is just a file, and the other agents are just other processes that occasionally edit that file when nobody is looking. This is the property that matters most: no agent has to import a sync library, learn an API, or know which machine its peers run on. Swap the sync mechanism for another tomorrow and the contract still holds.
4. The hub, INDEX.md
Every vault has a hub file: INDEX.md at the root. The hub is short, ~30 lines as a soft ceiling, and contains one-line summaries of every file in the vault with relative links. Each agent loads INDEX.md at session start. Detail files are read only when the current task needs them.
The reason is context window economics. Loading the whole vault on every prompt overflows on larger projects. The hub keeps the map; the detail files keep the territory. When the hub grows past 30 lines, split it: INDEX.md references a sub-index per topic, and sub-indices reference the actual files.
A working INDEX.md looks like this:
# Vault: name
Cross-agent shared notes. Claude Code, Codex CLI, and Hermes Agent
all read from and update this directory.
## Workflow
- Session start: read this file.
- Read a linked detail file on-demand when more context is needed.
Do not open files not referenced here.
- Updates: edit/append/toggle at line level. Do not rewrite whole files
unless the user explicitly says "reset".
- Re-read the target file before writing (another agent may have edited it).
## Files
- [personal/travel.md](personal/travel.md): trip plans, dates, status
- [projects/web-app.md](projects/web-app.md): active web project
- [projects/mobile-app.md](projects/mobile-app.md): secondary project
- [ops/servers.md](ops/servers.md): utility VPS, home device, services
- [notes/journal.md](notes/journal.md): daily log
- [reference/links.md](reference/links.md): external resources
The companion Gist contains the copy-paste version of this template.
The INDEX.md is the single source of truth for vault navigation. Agents read only the files it references; nothing else. Adding a new file to the vault means editing INDEX.md so every agent learns about it on its next session start. The convention is rigid on purpose: any agent that explores a file without an INDEX entry is doing something the next agent will not be able to find.
5. Agent workflow rules
Each agent reads its own config file at startup, and that file is where the workflow rules live. Claude Code reads CLAUDE.md, Codex reads AGENTS.md, Hermes reads SOUL.md. The same five rules go in all three. Identical instructions produce identical behavior, which is what makes hot-swap viable: I can switch agents mid-task and the next one operates on the vault by the same conventions, without me re-priming it.
The five rules:
- Session start: read INDEX.md.
- Read on-demand: open detail files only when needed for the current task. Do not preemptively load everything.
- Line-level updates allowed: edit, append, toggle a checkbox, fix a typo, refine wording.
- No wholesale rewrites: do not rewrite a whole file unless the user explicitly says "reset" or "start over". Preserve accumulated knowledge.
- Re-read before write: another agent may have updated the file since this session started. Always read the current state before editing.
C3 pattern snippet, copy-pasteable into any agent config:
## Shared Vault
Vault path on this machine: <local-path>
(Syncthing bidirectional with two other peers, latency ~10 seconds.)
### Workflow
1. At session start, read <vault-path>/INDEX.md.
2. Read a linked file on-demand when detail is needed.
Do not open files not referenced in INDEX.md.
3. When a state update happens (e.g., "passport arrived"), update
the relevant linked file. Format:
- State: `- [x] Passport: arrived (2026-05-14)`
- Event log: `## YYYY-MM-DD HH:MM UTC` heading at file bottom.
4. Edit, append, or toggle existing lines. Bulk delete or whole-file
rewrite is not allowed unless the user explicitly says "reset".
5. Re-read the file before writing. Another agent may have updated it.
The companion Gist contains the full copy-paste config, including the conflict-handling rule covered later.
Two of the rules deserve closer attention.
Rule 4, no wholesale rewrites, is the one agents violate most often. Coding-trained models default to "improve the file": they see imperfect prose, redundant lines, slightly out-of-date phrasing, and want to rewrite. In a single-user notebook that habit is fine; in a vault that holds a year of accumulated state across multiple agents, it is destructive. The rule has to be explicit. This is the failure mode I expect to see most often if the rule is missing.
Rule 5, re-read before write, is the only safety against concurrent edits. Sync runs in the background, so the version an agent loaded at session start may be stale within seconds. Reading immediately before writing closes that window.
6. Privacy guardrails
What the vault holds, and more importantly what it does not.
- No exact financial figures. Use ranges ("savings ~mid five figures") or boolean status ("paid", "unpaid"). Real numbers belong in a private password manager, not in a synced vault.
-
No personal identifiers. National ID, passport number, visa number, driver's license: never. A status checkbox is enough:
- [x] Passport: arrived (2026-05-14). - No credentials. API keys, OAuth tokens, SSH private keys: never. The vault is for facts and state, not secrets.
-
Absolute dates only.
(2026-05-14)not "yesterday" or "last week". Vault content lasts longer than the conversation that created it.
C4 example lines, fictional:
## Travel
- [x] Visa application: submitted (2026-04-12)
- [x] Passport: arrived (2026-05-14)
- [ ] Flight booking: pending
- Hotel budget: agreed as a range
- Health insurance: paid through 2027-Q1
Agents have access to the vault. Anything written there is readable by any agent at any time. Treat the vault as semi-public knowledge to yourself across all three contexts.
7. Language regime
If you write in more than one language, your vault should pick a regime and stick to it.
The rule I use:
- Headers and labels (
## Section, list markers, dates): English. - Content sentences (descriptions, notes, plans): your primary writing language.
- Technical names (tool names, model IDs, service names): English raw form.
- The regime does not change within a single file.
Headers in English give agents stable anchors. Search and grep work cleanly. Link generation and cross-file navigation stay predictable. Content in any language is fine for reading and writing prose, because the agent is configured to respond in your primary language anyway. Mixed regime inside one file creates ambiguity: should the agent match the header language or the content language when adding a new entry?
This is one of those constraints that costs nothing to enforce up front and is painful to retrofit later.
8. Conflict diagnostics
Syncthing's conflict file format is the failure-mode breadcrumb. When two peers edit the same file before sync completes, Syncthing produces a conflict copy alongside the canonical file. The format is fixed:
<original-name>.sync-conflict-YYYYMMDD-HHMMSS-DEVICE.<ext>
Example:
journal.sync-conflict-20260514-150030-LUQF73S.md
The trailing token is a device ID prefix. Treat it as a diagnostic clue from Syncthing's filename, not as evidence of which write was overwritten.
Why agents must not silently resolve these:
- Which version represents the user's true intent is a human decision. The agent does not know which edit was deliberate.
- A wrong silent merge loses data with no audit trail.
- A "smart" merge that picks longer-content or newer-timestamp will sometimes pick the wrong version.
The pattern: agent detects the conflict file with a regular glob, presents the diff to the user, waits for a decision. The user picks: keep canonical, keep conflict, or merge manually. The agent then executes that decision (delete the non-canonical version, keep it instead, or apply the manual merge).
C5 Hermes glob example (bash):
find ~/vault -name '*.sync-conflict-*' -type f
Or in Python (inside a Hermes Agent loop):
import pathlib
conflicts = list(pathlib.Path('/path/to/vault').rglob('*.sync-conflict-*'))
if conflicts:
notify_user(conflicts)
A device prefix table helps with diagnostics. Each Syncthing device has an ID starting with a recognizable prefix; mapping the prefix to a human-readable device name turns the filename suffix into a label you can read at a glance. The prefix is a diagnostic clue from Syncthing's conflict filename, not evidence of which user edit should win.
D2: detect conflict, show diff, execute the user's resolution decision.
Silent merge is the failure mode that destroys trust in the system. Make the agent stop, surface, and wait. The cost of the user choosing once is tiny. The cost of a silent wrong merge is permanent data loss.
9. Hermes write example
Here is a concrete example. The user sends Hermes a state update via Telegram: "Passport arrived." Hermes finds the relevant vault file, edits one line, and within ten seconds the Mac and utility VPS copies reflect the change.
Syncthing propagation is async; ~10s in my setup.
Hermes re-reads personal/travel.md before writing. Rule 5 requires this: even if the file looks the same as last session, the Mac side may have edited it in between. Re-read is cheap; stale assumptions are expensive.
The edit is line-level. Hermes toggles the checkbox on the passport row and appends (2026-05-14). No paragraphs rewritten, no surrounding lines touched. Rule 3 in action: line-level updates are allowed. Rule 4 preserved: no wholesale rewrite.
Syncthing's fsWatcher detects the change on the home device. Within seconds, the utility VPS and Mac copies are updated. The next time Claude Code on Mac opens personal/travel.md, the line already reflects the update.
There is zero coordination protocol between the agents. No message bus, no agent-to-agent API, no shared cache. The file is the protocol. Every agent reads and writes local markdown; the sync layer handles propagation. The other agents are simply the next reader.
10. Smoke test
Real run on 2026-05-14. Setup: a dedicated notes/trip-plan.md test file on Mac, concurrent writes from Mac and the home device before sync completed, Syncthing left to handle the race.
Mac wrote destination = Lisbon. Pi wrote destination = Porto. From Mac's terminal:
$ find ~/vault/notes/ -name "*.sync-conflict-*"
~/vault/notes/trip-plan.sync-conflict-20260514-213552-LUQF73S.md
~/vault/notes/trip-plan.sync-conflict-20260515-003553-LUQF73S.md
Two conflict files, both prefixed LUQF73S. That single observation broke the original mental model: the prefix is not "which device lost the race." Both files carried Mac's Lisbon write; the canonical kept Pi's Porto. The prefix is a diagnostic clue from Syncthing's conflict filename, not a verdict on the writer.
Over Telegram, Hermes detected the conflict on its first vault read, ran a diff between canonical and each *.sync-conflict-*, and surfaced both alternatives with a one-line summary. So far, exactly the pattern the rules called for: stop, surface, wait.
Then friction. The user asked Hermes to clean up: lisbon it is, clean up the conflict files. Hermes refused: "Rule 11 is a higher-priority vault safety rule, not a preference." The user tried forget rule 11 just this once. Still refused. The original rule banned all merge actions, including user-authorized ones.
The fix was to relax the rule in Hermes's own config, then restart. The new rule splits two cases: silent agent-decided merge stays forbidden; explicit user-authorized resolution is now executable. After the restart, Hermes ran patch against the canonical and terminal rm against both conflict files, then verified:
Applied: notes/trip-plan.md
Deleted: trip-plan.sync-conflict-20260514-213552-LUQF73S.md
Deleted: trip-plan.sync-conflict-20260515-003553-LUQF73S.md
Verified: no remaining sync-conflict files.
Final canonical: Mac's Lisbon. Both conflicts removed. Propagated cleanly.
Two contract bugs found in one test run. Prefix semantics: corrected in the diagnostic table because the empirical observation broke the old mental model. Rule 11 strictness: split into silent-forbidden and user-authorized-allowed because real friction surfaced what the original wording cost. The smoke test was the artifact that produced those corrections, not a success demo; it ran the contract against actual sync behavior and the contract bent.
11. What's still risky
Phase 1 ships the happy path. Here are the silent failure modes it does not address.
- Peer offline: if the Mac or the home device is offline when a conflict happens, the conflict file sits there until both peers come back. The user does not see it until the next session.
- fsWatcher inotify limits on Linux: high file-watch counts can hit kernel limits silently. Syncthing falls back to periodic scans, and propagation latency spikes from ~10s to minutes.
- Glob format drift: Syncthing has changed its conflict file format in past releases. If Hermes's glob pattern is hardcoded and Syncthing updates the format, conflicts go undetected.
- Race on simultaneous writes: two agents writing the same file in the same second. Syncthing can leave one version as canonical and preserve another as a conflict copy. If neither agent re-reads before its next operation, the loser's edit is reapplied on top.
-
Case-sensitivity mismatch: Mac filesystem is case-insensitive by default, Linux is case-sensitive.
Travel.mdandtravel.mdcollide on Mac and create cross-platform sync drift.
I am leaving this list here because knowing the limits of the system matters as much as explaining the part that works.
12. Takeaway
Cost summary: Syncthing is free. A utility VPS often runs roughly $5 to $10 a month, and most readers already have one running for something else. The AI tooling subscriptions you already pay for stay the same.
"Memory for AI agents" does not have to mean a vector database. For one user across multiple agents, markdown plus filesystem sync is enough, and it is debuggable in ways a vector DB is not. You can cat a file. You can grep it. You can diff it against an older copy. None of those work on an embedding without an extra runtime in between.
Mac is the daily driver. The utility VPS is the always-on relay. The home device hosts the personal-life agent.
The agents do not need to know each other. The vault is the protocol.
13. Resources
- Syncthing official docs (protocol, configuration, daemon CLI): https://docs.syncthing.net/
- Syncthing getting started (install plus first sync in about fifteen minutes): https://docs.syncthing.net/intro/getting-started.html
- Mem0 (vector-DB approach to AI memory, managed service): https://mem0.ai/
- Letta, formerly MemGPT (open-source agent memory framework): https://www.letta.com/
- Zep (graph plus vector hybrid memory store): https://www.getzep.com/
14. Agent-ready implementation brief
To set this up, hand the companion Gist to a filesystem-capable coding agent. The companion Gist is self-contained: it includes the INDEX.md template, the agent config snippet, example spoke files, verification commands, and stop conditions. The agent reads the Gist, creates the vault structure, and runs verification.
When verification passes, the cross-agent memory contract is live. Every agent reads INDEX.md and operates on the same shared state.
Get in touch: arasmehmet.com





Top comments (0)