Documentation
agent-sync keeps one copy of every config you share and puts it where
each agent expects to find it. This page is the reference: what lives in the Commons
— the canonical ~/.agents/ directory — how each family reaches each
agent, and what the tool will and will not touch.
Install
# prebuilt binary for macOS, Linux and Windows — no toolchain, no postinstall script, # so it installs offline and inside a sandboxed CI npm install -g agent-sync-sh # or the same binary as a PyPI wheel — no Python code, just the binary pip install agent-sync-sh # or build from source — needs Rust 1.97 or newer 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 Homebrew 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
The npm package is a small launcher; the real binary ships in one platform
package per target, declared as optional dependencies so npm installs exactly the one
matching your machine. On a platform with no prebuilt binary you get a message naming
what it looked for and pointing at cargo install, rather than a
missing-file crash. The same design is why npx works: there is no
postinstall step and nothing to fetch beyond the binary itself, and
doctor only reads — it reports the agents this machine has without
creating a Commons.
The PyPI wheels contain no Python code at all — each one carries the same
compiled binary in the wheel's scripts/ directory, which is how
pip install and uvx put a working agent-sync on
PATH with no interpreter shim in between. There is one wheel per platform, so pip
resolves the right binary the same way npm picks the right optional dependency.
On a platform with no prebuilt wheel you still get a clean install and a command
that names your platform and points at cargo install, rather than
pip's bare no matching distribution found — the same courtesy the npm
launcher extends.
The Homebrew tap lives in the agent-sync repository itself rather than a separate
homebrew-agent-sync one, which is why it is tapped by URL rather than by
the short brew tap agent-sync-sh/agent-sync form. It installs the same
prebuilt binary the release attaches to its GitHub Release, verified against the
published SHA256SUMS.txt; the formula is regenerated by CI on every tag,
so it can only ever point at assets that exist. Homebrew 6 refuses to load a
third-party tap until you trust it, hence the brew trust line.
On Windows, creating symlinks requires Developer Mode
(Settings → System → For developers) or an elevated shell; sync says
exactly that when it cannot link.
First five minutes
$ agent-sync init
Created the Commons at ~/.agents
skills/
commands/
agents/
Found 6 agents: claude, codex, opencode, pi, oh-my-pi, gemini
3 skills not in the Commons:
claude/skills/my-workflow
claude/skills/review-loop
pi/agent/skills/notes
take one with `agent-sync adopt <path>`
4 MCP servers not in the Commons: serena, xapi, node_repl, computer-use
take them all with `agent-sync mcp adopt --all`
init answers the question a used machine actually poses: not what
agent-sync does, but what of your existing config it could take over. Adopt what you
want shared, then sync.
$ agent-sync adopt ~/.claude/skills/my-workflow adopted my-workflow from claude into the Commons (skills) $ agent-sync mcp adopt --all adopted serena from claude scoped to claude — remove the allowlist in agent-sync.toml to share it $ agent-sync sync
adopt is one verb; where the path lives picks the mechanic. A real
config inside an agent's directory is moved into the Commons with a
link left behind; a path inside a git repo becomes a Sourced entry —
the Commons links out to it and the repo keeps the truth; anything else is
copied in. --dry-run names the mechanic before anything
moves. If the Commons already holds that name with identical contents it collapses
the duplicate into a link; if the contents differ it refuses and explains, because
choosing which side to discard is not agent-sync's call.
The Commons
~/.agents/ ├── skills/<name>/ a directory per skill ├── commands/<name>.md Claude-dialect markdown ├── agents/<name>.md Claude-dialect markdown ├── AGENTS.md your shared instructions ├── mcp.json the standard mcpServers shape └── hooks/<Event>.toml [[hook]] entries: command, matcher, timeout
The Commons lives at ~/.agents/. It holds ecosystem-standard content
only — nothing agent-sync-specific lives there, which is what lets other tools read it
directly, as opencode, oh-my-pi and Hermes already do. And it is exactly that — a
commons, not agent-sync's alone: other tools keep their own files there, and
doctor names entries that aren't agent-sync's and leaves them be. Tool
configuration lives separately in $XDG_CONFIG_HOME/agent-sync/ (default
~/.config/agent-sync/).
The six families
- Skills, commands, agents
- Symlink fan-out. One canonical relative link per entry in each agent's
directory; links of ours that dangle are pruned; anything else is left alone.
Agents that read
~/.agents/skillsnatively — Codex, opencode, oh-my-pi, Gemini CLI, Cursor — get no skill links at all, and where Codex or Cursor still reads its old fan-out directory,syncprunes our now-duplicate links from it. - Instructions
- Three mechanisms, because agents differ in kind. A plain symlink where
the whole file can be ours (Codex, opencode, pi, oh-my-pi, Gemini CLI, Windsurf).
An import line
for Claude, whose
CLAUDE.mdholds your own content, so agent-sync only ensures@~/.agents/AGENTS.mdis present — additive and idempotent. A rules-dir link for Roo, which globs a directory. - MCP servers
- Rendered into each agent's dialect and key-merged. See below.
- Hooks
- Command-hooks declared once in
hooks/<Event>.tomland merged into Claude, Codex and Gemini by command string: agent-sync owns exactly the array elements whose command matches a Commons hook, and other tools' hooks in the same event array survive untouched. Codex trust hashes are never written — approving code execution stays your decision.
A file another tool already owns — claude-mem's boilerplate in opencode's
AGENTS.md, say — is a Conflict: reported with the
remedy named, never overwritten.
Ownership, without a state file
Everything agent-sync writes must be recognisable as its own on a later run, with no database to consult. Three identities cover every mechanism.
- Link identity
- A symlink resolving into the Commons is ours — canonicalised, and pruned when it dangles. A link resolving anywhere else is Foreign and is never touched.
- Name identity
- An entry key-merged into a shared file is ours when its name appears in the
Commons; for hooks the command string is the name. Unknown names in the same file
are Foreign. One consequence is deliberate:
syncnever deletes an MCP server, because a name that vanished from the Commons cannot be told apart from one you added by hand. Removal isagent-sync mcp remove. - Marker identity
- A wholly generated file carries one comment line saying so. That is what makes pruning safe: removing a marked file returns the machine to a state agent-sync created. An unmarked file at the same path was written by somebody else.
Reading status
| State | Means |
|---|---|
| linked | A canonical link onto the right Commons entry. Nothing to do. |
| missing | The Commons has it; this agent does not, yet. |
| stale | Our link, but not in canonical form — it will be rewritten. |
| dangling | Our link, pointing at a Commons entry that is gone. It will be pruned. |
| variant | A real file or directory shadowing a Commons entry, on purpose. Preserved. |
| variant-identical | Same, but byte-identical to the Commons copy — a duplicate you can collapse. |
| foreign | Not ours. Reported, never modified. |
| conflict | Another tool owns a file agent-sync would otherwise write. |
0 clean, 1 error, 2
something actionable. A Variant that diverges and a Foreign entry are settled
states, not work, so neither makes the exit code 2.MCP servers
One mcp.json in the standard shape is rendered into each agent's
dialect: Codex takes TOML tables with http_headers; opencode wants
local/remote with the command flattened into one array and
env renamed environment; Gemini distinguishes SSE from
streamable HTTP by which URL key it uses; Windsurf calls it serverUrl.
# ~/.config/agent-sync/agent-sync.toml [mcp.serena] agents = ["claude", "codex"] # default: every capable agent [mcp.node_repl.tweaks.codex] startup_timeout_sec = 120 # reaches Codex alone
Secrets are written as ${env:VAR} in the Commons, resolved from the
environment at sync, and redacted from every line of output — so the Commons stays
committable. If a resolved secret would land in a file others can read, agent-sync says
so.
mcp disable parks a server without retyping it: the definition and
its rules stay in the Commons, renders are removed everywhere, and mcp
enable restores them.
agent-sync mcp adopt claude/serena reverse-translates an existing
server into the Commons, turning agent-specific knobs into Tweaks. It proves the
result first: if re-rendering it would not reproduce what you already have, it
refuses rather than reporting a success that the next sync would undo.
CLI
| Command | Does |
|---|---|
| init | Create the Commons and report what could be adopted. |
| adopt <path> | Take a path under management; where it lives picks the mechanic — absorb, Sourced, or copy. --dry-run names it. |
| sync | Fan the Commons out to every installed agent — planned in full before the first write. --dry-run prints the plan. |
| status | What is in sync, what is not, what is not ours. --json. |
| doctor | Installed agents, Commons hygiene, Sourced entries with missing clones marked, and .agents Protocol surfaces attributed by name. |
| revert <agent> | Remove everything agent-sync put into one target — links, merged MCP and hook keys, rendered files. Refuses until the target is disabled. |
| mcp list | Commons servers and their state in every agent. --json. |
| mcp adopt | <agent>/<server>, or --all. |
| mcp remove | Delete a server from the Commons and from every agent. |
| mcp enable | disable | Park a server or restore it — the definition and its rules stay in the Commons either way. |
agent-sync.toml
Optional, in $XDG_CONFIG_HOME/agent-sync/ (default
~/.config/agent-sync/agent-sync.toml). Absent means defaults. It never
lives in the Commons, so the Commons stays ecosystem-standard content only. The lock
— agent-sync's only machine state — lives in $XDG_STATE_HOME/agent-sync/
(default ~/.local/state/agent-sync/).
[targets] cursor = false # leave this agent alone entirely [custom.myagent] # an agent the registry does not know root = ".myagent" skills = ".myagent/skills" [mcp.heavy-server] agents = ["codex"]
Many machines
agent-sync does not sync across machines, deliberately — that would mean building conflict resolution, and git already won that argument. The Commons is an ordinary directory, so version it:
# as a git repo cd ~/.agents && git init && git add . && git commit -m "my agent config" # or with chezmoi chezmoi add ~/.agents ~/.config/agent-sync
On the second machine, clone it and run agent-sync sync.
Vocabulary
- Commons
- The canonical directory
~/.agents/holding the single real copy of everything agent-sync syncs — a shared commons other tools also live in; agent-sync is a tenant, never the landlord. - Target
- One agent's config surface that receives fan-out.
- Fan-out
- One Commons entry materialised into every Target as a symlink.
- Native
- An agent that reads the Commons directly and therefore needs no fan-out.
- Variant
- A real file or directory in a Target that intentionally shadows the Commons copy for that agent alone. First-class and preserved, never clobbered.
- Adopt
- Taking a path under agent-sync's management with one verb; where the path lives picks the mechanic — absorbed into the Commons with a link left behind, made a Sourced entry when it lives in a git repo, copied in otherwise.
- Sourced
- A Commons entry that is a symlink out to its Source — an outside path, typically in a git repo, holding the content's single real copy. The Commons keeps the pointer; the Source keeps the truth.
- Revert
- Removing everything agent-sync put into one Target, as a deliberate offboarding act. Refused while the target is still enabled, so a later sync cannot redo what revert undid.
- Foreign
- A link, file or server entry in a Target that agent-sync does not own. Never touched, listed by
status. - Conflict
- A Foreign file occupying a destination agent-sync would otherwise write — reported with the remedy named, never overwritten.
- Render
- Translating a Commons entry into one agent's native format and merging it into that agent's config file.
- Managed
- An entry whose name appears in the Commons: agent-sync owns it in every targeted config, and the Commons wins.
- Disabled
- A Managed server whose Commons definition carries
"disabled": true— defined but rendered nowhere, so enabling restores it everywhere without retyping. - Protocol surface
- A
.agentsProtocol path at the Commons root —tasks/,memories/,models.json,system-prompt.md— thatdoctorattributes by name and agent-sync never touches. - Tweak
- A per-agent addition merged into one agent's rendering, declared in
agent-sync.toml, never in the Commons. - Marker
- The one-line comment in every generated file, making it recognisable as agent-sync's without a state file.