One canonical .agents/ folder, fanned out to all your AI coding agents.
The Commons — the canonical ~/.agents/ directory
— holds the single real copy of every config you share — skills,
instructions, MCP servers, slash commands, agents, hooks. agent-sync sync
fans it out to every agent you have installed — twelve are supported: symlinks where
the bytes can be identical, a rendered key-merge where they can't. There is no state
file, and there never will be.
# the Commons — one real copy of everything you share ~/.agents/ ├── skills/ research/ tdd/ code-review/ ├── commands/ ship.md triage.md ├── agents/ reviewer.md ├── AGENTS.md your instructions, once ├── mcp.json standard mcpServers shape └── hooks/ SessionStart.toml PreToolUse.toml
Fan-out
Edit one file. Every agent sees it.
$ ls -l ~/.claude/skills/ research -> ../../.agents/skills/research tdd -> ../../.agents/skills/tdd code-review -> ../../.agents/skills/code-review $ ls -l ~/.pi/agent/skills/ research -> ../../../.agents/skills/research tdd -> ../../../.agents/skills/tdd code-review -> ../../../.agents/skills/code-review
A skill added to the Commons appears in Claude Code, pi, OpenClaw and Hermes as a relative symlink — computed, not hand-typed, so the tree survives being moved or restored somewhere else.
Codex, opencode, oh-my-pi, Gemini CLI and Cursor read
~/.agents/skills themselves, so agent-sync writes no skill links for
them at all — and where Codex or Cursor still reads its old fan-out directory,
sync prunes our now-duplicate links from it. A link of ours that
dangles gets pruned; a link pointing anywhere else is Foreign and is left
alone.
Two mechanisms
Symlinked where it can be, rendered where it can't.
Skills, instructions, commands and agents can be byte-identical everywhere, so they are one file seen from ten places — drift is impossible by construction. MCP servers and hooks live inside files the agent also owns, in formats no two agents share, so those are rendered and key-merged: your other keys are preserved, and only the entries named in the Commons are rewritten.
# ~/.agents/mcp.json — you write this once
{
"mcpServers": {
"serena": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "serena-agent", "serena"]
}
}
}
# ~/.codex/config.toml — merged, not replaced model = "gpt-5.3-codex" # untouched [mcp_servers.serena] command = "uvx" args = ["--from", "serena-agent", "serena"] [projects."/Users/you/work"] # untouched trust_level = "trusted"
No state
The filesystem is the state.
Other tools in this space copy, render, and remember what they own in a record beside your configs. When that record disagrees with the disk, your hand-edits lose. agent-sync keeps no record at all, because ownership is legible from the disk itself.
- Link identity
- A symlink that resolves into the Commons is ours. It gets canonicalised, and pruned when it dangles.
- Name identity
- An MCP server, or a hook whose command appears in the Commons, is ours. Every other name in the same file is Foreign and survives untouched.
- Marker identity
- A wholly generated file carries one comment line saying so. Marked is ours; unmarked was written by somebody else.
Restraint
Variants are a feature, not a conflict.
$ agent-sync status
claude (.claude/skills) 12 linked
variant plannotator — left alone
variant-identical old-notes — identical to the
Commons, could be re-linked
foreign vendor-thing — not ours
mcp (mcp.json)
managed serena → claude
foreign xapi → claude — not in the
Commons — left alone
2 items need attention — run `agent-sync sync`.
A real directory shadowing a Commons entry is a Variant: deliberate, preserved forever, and flagged only when its contents are identical to the Commons copy, so you can dedupe on purpose rather than by accident.
Anything agent-sync did not write is Foreign — reported, never modified. Exit
codes are the contract: 0 clean, 1 error,
2 something to do.
Coverage
Twelve agents, six families.
| Agent | Skills | Instructions | MCP | Commands | Agents | Hooks |
|---|---|---|---|---|---|---|
| Claude Code | fan-out | import-line | key-merge | fan-out | fan-out | key-merge |
| Codex | native | symlink | key-merge (TOML) | fan-out | none | key-merge |
| opencode | native | symlink | key-merge | fan-out | fan-out | none |
| pi | fan-out | symlink | none | none | none | none |
| oh-my-pi | native | symlink | native | native | none | none |
| OpenClaw | fan-out | none | none | none | none | none |
| Hermes | fan-out | none | none | none | none | none |
| Gemini CLI | native | symlink | key-merge | render | none | key-merge |
| Cursor | native | none | key-merge | fan-out | none | none |
| Windsurf | none | symlink | key-merge | fan-out | none | none |
| Roo | none | rules-dir link | none | fan-out | none | none |
| Cline | none | none | key-merge | none | none | none |
sync prunes agent-sync's now-duplicate links from it.Safety
Safe to run before you trust it.
$ agent-sync sync --dry-run
dry run — no changes will be made
claude (.claude/skills)
missing research
missing tdd
mcp (mcp.json)
missing serena → codex — not in this agent's config yet
3 changes would be made.
- It plans before it writes
- Every family is surveyed before the first write; any Commons fault reports the complete list and writes nothing, so a config error can never leave a half-synced machine.
- A second sync is a no-op
- Run it twice and the second run reports Everything is up to date with the files byte-identical.
- Every write is atomic
- A private temp file, fsync, then an atomic rename — resolved through a symlinked destination, so dotfiles wiring survives.
- Secrets stay out of the Commons and out of your terminal
${env:VAR}is resolved at sync and redacted from every line of output, so the Commons stays committable.- It reports to CI
status --jsonandmcp list --json, with exit code2reserved for “there is something to do”.
Install
macOS, Linux and Windows.
# prebuilt binary — no toolchain, no postinstall script npm install -g agent-sync-sh # or the same binary as a wheel pip install agent-sync-sh # or from source, needs Rust 1.97+ cargo install agent-sync-sh # or try it first, without installing anything npx agent-sync-sh doctor uvx agent-sync-sh doctor # or on macOS and Linux, from the tap in the repo itself brew tap agent-sync-sh/tap https://github.com/agent-sync-sh/agent-sync brew trust agent-sync-sh/tap brew install agent-sync # then, on a machine you have already been using agent-sync init
init creates the Commons and then tells you what of your existing
config it could take over — per family, with the command that would do it.
Limits
What it does not do.
No cross-machine sync. The Commons is an ordinary directory — version it with git or chezmoi. Building sync would mean building conflict resolution, and git already won that.
No memory sync. Agent memory is not a defined artifact, and agent-sync will not pretend otherwise.
No GUI, no daemon, no file watcher, no undo — refusals come
before writes, and sync and adopt preview with
--dry-run. It does not install skills — it fans out whatever is in
the Commons, whoever put it there. There is no mcp add, because
adding a server is editing a documented standard file.