agent-sync

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.

AgentSkills InstructionsMCP CommandsAgents Hooks
Claude Codefan-outimport-linekey-mergefan-outfan-outkey-merge
Codexnativesymlinkkey-merge (TOML)fan-outnonekey-merge
opencodenativesymlinkkey-mergefan-outfan-outnone
pifan-outsymlinknonenonenonenone
oh-my-pinativesymlinknativenativenonenone
OpenClawfan-outnonenonenonenonenone
Hermesfan-outnonenonenonenonenone
Gemini CLInativesymlinkkey-mergerendernonekey-merge
Cursornativenonekey-mergefan-outnonenone
Windsurfnonesymlinkkey-mergefan-outnonenone
Roononerules-dir linknonefan-outnonenone
Clinenonenonekey-mergenonenonenone
What each agent supports, and by which mechanism. none means that agent has no such config surface — not that agent-sync skipped it. native means the agent reads the Commons itself, so no links are written — and where Codex or Cursor still reads its old fan-out directory beside the Commons, 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 --json and mcp list --json, with exit code 2 reserved 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.