agentstow

配置只写一份(技能、MCP、记忆等),扇出到你所有的 AI 编程智能体。

位于 ~/.agents/ 的 Store(配置仓库)保存着你共享的每一份配置的唯一真实副本——技能、指令、MCP 服务器、斜杠命令、子智能体、钩子。agentstow sync 把它扇出到你安装的全部十个智能体:字节可以完全相同的地方用符号链接,不能相同的地方用渲染后的键级合并。没有状态文件,将来也不会有。

# Store — 你共享的一切,只有这一份真实副本
~/.agents/
├── skills/          research/  tdd/  code-review/
├── commands/        ship.md  triage.md
├── subagents/       reviewer.md
├── AGENTS.md        你的指令,只写一次
├── mcp.json         标准 mcpServers 结构
└── hooks/           SessionStart.toml  PreToolUse.toml

扇出

只改一个文件,每个智能体都能看到。

$ 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

加进 Store 的技能,会以相对符号链接的形式出现在 Claude Code、Codex、Cursor 和 pi 里——链接是计算出来的,不是手写的,所以整棵目录树被移动或在别处恢复之后依然有效。

opencode 和 oh-my-pi 本来就会自己读取 ~/.agents/,所以 agentstow 刻意不为它们写任何东西。我们自己的链接一旦悬空就会被清理;指向别处的链接属于外来项(Foreign),一律不动。

两种机制

能用符号链接就用符号链接,不能用就渲染。

技能、指令、斜杠命令和子智能体在每个智能体那里都可以字节完全相同,所以它们是同一个文件被十处引用——漂移在构造上就不可能发生。MCP 服务器和钩子存在于智能体自己也拥有的文件里,而且格式各不相同,所以采用渲染加键级合并:你的其他键原样保留,只有 Store 中点名的条目会被改写。

# ~/.agents/mcp.json — 只需写这一次
{
  "mcpServers": {
    "serena": {
      "type": "stdio",
      "command": "uvx",
      "args": ["--from", "serena-agent", "serena"]
    }
  }
}
# ~/.codex/config.toml — 合并,而不是替换
model = "gpt-5.3-codex"        # 原样保留

[mcp_servers.serena]
command = "uvx"
args = ["--from", "serena-agent", "serena"]

[projects."/Users/you/work"]   # 原样保留
trust_level = "trusted"

无状态

文件系统就是状态。

同类工具靠复制、渲染,并在你的配置旁边留一份记录来记住自己拥有什么。一旦记录与磁盘不一致,输掉的是你的手工修改。agentstow 完全不留记录,因为归属可以直接从磁盘上读出来。

链接身份
解析进 Store 的符号链接就是我们的:它会被规范化,悬空时会被清理。
名字身份
名字出现在 Store 里的 MCP 服务器,或命令出现在 Store 里的钩子,是我们的。同一文件里的其他名字都是外来项(Foreign),原样保留。
标记身份
完全由生成得到的文件带有一行注释声明这一点。有标记的是我们的;没有标记的出自别人之手。

克制

变体是特性,不是冲突。

$ 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`.

遮蔽 Store 条目的真实目录是一个变体(Variant):有意为之,永远被保留,只有当内容与 Store 副本完全相同时才会被标出——去重因此是你主动的决定,而不是一次意外。

凡不是 agentstow 写下的都是外来项(Foreign)——只报告,绝不修改。退出码就是契约:0 干净,1 出错,2 有事可做。

覆盖范围

十个智能体,六大配置族。

智能体技能 指令MCP 命令子智能体 钩子
Claude Codefan-outimport-linekey-mergefan-outfan-outkey-merge
Codexfan-outsymlinkkey-merge (TOML)fan-outnonekey-merge
opencodenativesymlinkkey-mergefan-outfan-outnone
pifan-outsymlinknonenonenonenone
oh-my-pinativesymlinknativenativenonenone
Gemini CLInonesymlinkkey-mergerendernonekey-merge
Cursorfan-outnonekey-mergefan-outnonenone
Windsurfnonesymlinkkey-mergefan-outnonenone
Roononerules-dir linknonefan-outnonenone
Clinenonenonekey-mergenonenonenone
每个智能体支持什么、通过哪种机制。none 表示该智能体没有这类配置面——不是 agentstow 跳过了它。native 表示该智能体自己读取 Store,再写任何东西都只会造成重复。

安全

还没建立信任,也可以放心运行。

$ 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.
第二次 sync 是空操作
连续运行两次,第二次会报告 Everything is up to date,文件字节完全相同。
每次写入都是原子的
私有临时文件、fsync、再原子重命名——目标路径先经符号链接解析,所以 dotfiles 的接线不会被破坏。
密钥不进 Store,也不进终端
${env:VAR} 在 sync 时解析,并从每一行输出中脱敏,Store 因此始终可以提交进版本库。
它向 CI 汇报
status --jsonmcp list --json,退出码 2 专门表示「有事要做」。

安装

macOS 与 Linux。

# 预编译二进制 — 无需工具链,没有 postinstall 脚本
npm install -g agentstow

# 或从源码构建,需要 Rust 1.97+
cargo install agentstow

# 然后,在一台你已经用了一段时间的机器上
agentstow init

init 先创建 Store,然后告诉你现有配置里有哪些可以接管——按配置族列出,并附上对应的命令。

边界

它不做什么。

不做跨机器同步。Store 是一个普通目录——用 git 或 chezmoi 做版本管理。自建同步就意味着自建冲突解决,而这场仗 git 早已赢了。

不做记忆同步。智能体记忆不是一种有明确定义的产物,agentstow 不会假装它是。

不支持 Windows,没有 GUI,没有守护进程,没有文件监听。它不负责安装技能——Store 里有什么就扇出什么,不管是谁放进去的。也没有 mcp add,因为添加服务器不过是编辑一个有文档的标准文件。