Canonical configs (skills, MCP, memory, etc.), fanned out to all your AI coding agents.
One Store at ~/.agents/ holds the single real
copy of every config you share — skills, instructions, MCP servers, slash
commands, subagents, hooks. agentstow sync fans it out to all ten agents
you have installed: 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 Store — one real copy of everything you share ~/.agents/ ├── skills/ research/ tdd/ code-review/ ├── commands/ ship.md triage.md ├── subagents/ 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 ~/.codex/skills/ research -> ../../.agents/skills/research tdd -> ../../.agents/skills/tdd code-review -> ../../.agents/skills/code-review
A skill added to the Store appears in Claude Code, Codex, Cursor and pi as a relative symlink — computed, not hand-typed, so the tree survives being moved or restored somewhere else.
opencode and oh-my-pi already read ~/.agents/ themselves, so
agentstow deliberately writes nothing for them. 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 subagents 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 Store 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. agentstow keeps no record at all, because ownership is legible from the disk itself.
- Link identity
- A symlink that resolves into the Store is ours. It gets canonicalised, and pruned when it dangles.
- Name identity
- An MCP server, or a hook whose command appears in the Store, 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.
$ agentstow status
claude (.claude/skills) 12 linked
variant plannotator — left alone
variant-identical old-notes — identical to the
Store, could be re-linked
foreign vendor-thing — not ours
mcp (mcp.json)
managed serena → claude
foreign xapi → claude — not in the
Store — left alone
2 items need attention — run `agentstow sync`.
A real directory shadowing a Store entry is a Variant: deliberate, preserved forever, and flagged only when its contents are identical to the Store copy, so you can dedupe on purpose rather than by accident.
Anything agentstow did not write is Foreign — reported, never modified. Exit
codes are the contract: 0 clean, 1 error,
2 something to do.
Coverage
Ten agents, six families.
| Agent | Skills | Instructions | MCP | Commands | Subagents | Hooks |
|---|---|---|---|---|---|---|
| Claude Code | fan-out | import-line | key-merge | fan-out | fan-out | key-merge |
| Codex | fan-out | 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 |
| Gemini CLI | none | symlink | key-merge | render | none | key-merge |
| Cursor | fan-out | 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 |
Safety
Safe to run before you trust it.
$ agentstow 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.
- 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 Store and out of your terminal
${env:VAR}is resolved at sync and redacted from every line of output, so the Store stays committable.- It reports to CI
status --jsonandmcp list --json, with exit code2reserved for “there is something to do”.
Install
macOS and Linux.
# prebuilt binary — no toolchain, no postinstall script npm install -g agentstow # or from source, needs Rust 1.97+ cargo install agentstow # then, on a machine you have already been using agentstow init
init creates the Store 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 Store 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 agentstow will not pretend otherwise.
No Windows, no GUI, no daemon, no file watcher. It does not
install skills — it fans out whatever is in the Store, whoever put it there. There
is no mcp add, because adding a server is editing a documented
standard file.