Community写作与编辑github.com

ssheleg/agent-sync

多个编码智能体,一个仓库,无冲突——基于TTL的租约、无竞态ID预留、运行日志和可插拔知识云上的生成看板。随 sshlg-skills 工具包一起发布。

agent-sync 是什么?

agent-sync 是一个智能体协调技能,专为 Claude Code 等 AI 编码代理设计,使多个代理能在同一 Git 仓库上并行工作而不产生冲突。它通过一个双平面架构实现:Git 作为持久记录平面,而知识云(如 Outline)或本地文件系统作为协调平面,用于追踪“当前谁在做什么”这类易逝信息。租约(Lease)基于 TTL,并通过 compare-and-swap 机制(本地为原子文件创建,Git 为非快进推送拒绝)确保独占访问。该技能提供了完整的命令集,包括 init(初始化)、acquire/release(获取/释放租约)、reserve(预留 ID)、guard(在写入前检查冲突)、status(查看其他代理状态及新依赖)、以及 merge(安全合并分支)。它通过生成的自述文件(README)、看板(Board)和决策日志(Decision Log)来维护项目状态的一致性。此外,agent-sync 自动集成到 Claude Code 的钩子中,在其他平台上需要手动运行 guard 命令。它通过 .env.agent-sync 文件管理凭证,并确保敏感信息永远不会写入 Git。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add ssheleg/agent-sync

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

agent-sync — 一个项目,多个智能体,无冲突

两个平面,以及它们之间的一条规则:

Git 是记录平面。云是协调平面。 必须持久保存的事实先写入 Git,再从云中引用。 关于当前谁在做什么的事实存在于云中,并会过期。

没有任何云对象是持久事实的唯一归宿。以下所有内容的目的是在多个智能体同时写入时,确保这一点始终成立。

四个陷阱——请先阅读这些,再做其他事

1. 知识库永远不能决定租约。 它做不到:十二次并发追加到一个 Outline 文档,返回了十二次成功,但只留下了三行。独占性必须来自真正支持 compare-and-swap 的机制。记录平面只承载记录,别无其他。完整测量数据见 references/lease-protocol.md。

2. 要知道你拥有哪种租约,并明确说明。 leaseBackend: "local" 是原子文件创建——在一个文件系统内的多个进程之间是独占的,跨机器时仅为建议性。leaseBackend: "git" 会推送一个引用,远程仓库的“非快进”拒绝就是一种 compare-and-swap——跨机器独占。acquire 命令会打印出具体是哪种。一个虚假的租约比没有租约更糟糕:另一个智能体会停止检查。

acquire 同时会将声明写入路线图,而 release 会精确恢复之前的内容——一行,一个单元格,在歧义时拒绝,往返后 git diff 结果为空白。在配置 claimTags 或关闭任务之前,请阅读 references/roadmap.md;关闭是对工作的陈述,并且仍然属于你。

在分支上工作;集成分支是别人稳定的基础。 acquire 仅在集成分支上写入声明;在任何其他分支上,持有者仍停留在协调平面,status 会向每个智能体显示它,而无需任何人拉取你的分支。提交到分支上的声明在合并之前是不可见的,并且会使共享路线图变成两个分支都在编辑的文件。使用 merge 来落地工作:git merge-tree 在接触任何东西之前计算冲突,标明并拒绝任何冲突,合并记录写入 docs/MERGES.md(近期几天完整,旧的写入时压缩),--key 租约被释放。merges 会告诉你离开期间哪些工作已落地。在合并之前,请阅读 references/branching.md。

3. 钩子仅在 Claude Code 中存在。 在其他地方,没有什么能阻止受守卫的编辑:请自行运行 guard 并将运行记录为 ungated。如果项目实际上未受保护,请不要将其描述为受保护的。

4. 要宽松地解析,永远不要把不可读的日志称为丢失的竞态。 存储系统会重写你写的内容——Outline 把 - 项目符号变成 * 。请输出 - ,接受 -/*/+,对任何格式错误但看似条目的内容进行计数,并在无法解析的比例超过 2% 时大声失败。当真相是不可读时,报告 lost 会凭空造出一个不存在的持有者。注意静默的前置过滤器:正则表达式之前的 continue 会将坏行从用于暴露它们的计数器中隐藏起来。

将此工具引入任何项目——完整链条

scaffold  → 创建文档架构,仅创建缺失的部分
adopt     → 读取仓库,提出配置建议,不写入任何内容
init      → 写入批准的配置 + 被 gitignore 的环境变量文件
            (操作员粘贴令牌——绝不是你)
reconcile --set-baseline    → 将历史记录视为已计数的积压工作,只需一次
setup     → 生成描述此项目连接关系的快照
check     → 验证整个设置;如果不健康则返回非零值

check 使本技能能够自给自足。 当出现以下情况时,它会拒绝将设置称为“健康”:存在不保护任何东西的规则(不存在的寄存器、守卫通配符、声明模式、门控或镜像源点)、凭据缺失、环境变量文件被 Git 跟踪(这里唯一不可恢复的错误)、快照过期或未链接,以及没有基线的寄存器。它会逐一指出每个问题;此工具在自身采用过程中曾真实地经历过每一个问题。

在采纳后、更改配置后以及在 CI 中运行 check。

scaffold 从不覆盖。 它会在决策寄存器中播种一个分配行和一个指向快照的 AGENTS.md,并且不触碰任何现有文件——一个在采用时重写项目自身约定的工具比什么都不做的更糟糕。

现有项目:从 adopt 开始

在 init 之前运行 adopt。它会读取仓库并打印出它找到的内容——ID 寄存器、注册表文件、门控——以及它拒绝为你做决定的内容——然后提出配置建议。

python3 "$SKILL_DIR/scripts/agent_sync.py" adopt

首先与操作员确认寄存器和受守卫的文件:指向错误文件的寄存器会使以后的每次检查都自信地错误,而遗漏共享文件的守卫列表会使唯一可能发生碰撞的地方得不到保护。在子模块中,它不声明任何寄存器:决策属于父仓库。然后从上面的链条的 init 步开始。

项目中的第一个命令:init

永远不要对未初始化的项目运行其他任何命令。 init 是提出并回答存储问题的地方,只此一次,然后记录下来。

在聊天中询问操作员这两个问题——不要猜测,不要选择默认值:

  1. 协调状态应该存在哪里?

    • 知识云(outline)——跨机器的共享记录、感知和看板。它不能决定租约;其中的任何东西都不能(陷阱 1);
    • 或本地文件(fs)——无需凭据,并且对另一台机器上的智能体不可见:无共享感知,无跨仓库信号,无共享看板。

    租约由 leaseBackend 单独决定(陷阱 2),并且**gated 遵循该选择,而绝不遵循记录平面**。报告你实际拥有的保证,而不是记录平面建议的更强的那个。

  2. 如果选择云:实例的 URL。 URL 是配置,而不是秘密,所以你可以写入它。令牌不是——你绝不能在聊天中询问它,不能把它读回来,也不能自己放置它。

然后用操作员的答案运行它:

python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend outline --url https://<their-instance>
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend fs

init 写入 .claude/agent-sync.json(形状,已提交),写入 .env.agent-sync 及其键和一个空白的令牌行(身份,权限 600),将 .env.agent-sync 和 .agent-sync/ 添加到 .gitignore,然后精确打印操作员必须自己执行的步骤——在他们自己的实例中创建令牌并将其粘贴到那一行。在没有 --force 的情况下,它从不覆盖现有配置或环境变量文件。

将那些结束指令逐字转达给操作员。将令牌放入文件是他们的步骤,并且设计上要求它始终由他们保管。

然后,在每次会话之前

python3 "$SKILL_DIR/scripts/agent_sync.py" status

幂等。检查、修复、报告,指出恰好一个下一步操作——并且携带 check 的裁决,因此每个会话都运行的命令和验证设置的命令不能对一个项目给出两个不同的答案。

阅读它打印的两个感知部分——它们是重点,不是装饰。

  • 当前正在处理此项目的其他运行。 谁持有什么,就在此刻。不要接手那些,也“不要只查看”它们所涵盖的文件。你看不到的租约使你被阻塞;你能看到的租约使你能够协调。
  • 自你上次查看以来的新变化。 你离开期间落地的跨仓库依赖关系变动。发生变动的依赖关系可能解除你计划的阻塞——或者使其无效。此列表按运行打上水印,因此直到真正发生改变时它才会保持安静;当它发言时,它很重要。

跳过此块的智能体会重新推导别人已经在做的工作,并基于一个小时前就已经改变的依赖状态行事。

status 还会决定什么:没有凭据 → 降级模式,已报告,继续运行(一种更小的模式,不是错误);task-pipeline 不存在 → 它打印安装命令并停止。不要临时编造一个替代流程——没有这些阶段,就没有东西可以绑定。

npx sshlg-skills install

命令

命令功能
init首先运行。 询问状态存在哪里,写入配置 + 被 gitignore 的环境变量文件,打印操作员的步骤
status检查、修复、报告,指出一个下一步操作
bootstrap创建云端容器并打印要粘贴到环境变量文件中的 ID
acquire <KEY>获取任务 ID 的租约。打印 won 或 lost <holder>
renew <KEY>延长租约。PostToolUse 钩子会为你执行此操作
release <KEY>归还租约。始终执行此操作,包括在失败时
reserve <REG>在寄存器(DEC、OQ、DEP 等)中预留下一个 ID。打印 ID
release-id <REG> <ID>归还一个你最终没有写入 Git 的 ID
journal <text>向本次运行的日志追加一行
record <text>追加你实际构建的内容——--decision DEC-…,--files a,b
reconcile意图(Git)与实际构建(云)。每个项目 --set-baseline 一次
signal <DEP-ID> <state>移动一个跨仓库依赖的状态:filed/accepted/delivered/closed/refused
guard <path>回答本次运行是否可以写入该路径。退出码 0 = 可以,2 = 不可以
board重新生成共享看板和此仓库的页面。--mirror 还会将配置的 Git 文档渲染到平面中
whoami打印本次运行的 ID 及其持有的租约
setup写入生成的快照,描述此项目是如何连接的,供智能体读取
adopt检查现有项目并提出配置建议——不写入任何内容
merge落地此分支:本地目标快进,在接触任何内容之前检查冲突,写入合并日志,释放 --key 租约。--summary,--dry-run,--push
merges在你处理分支期间落地了什么。--all 包括压缩的尾部
check端到端验证整个设置。不健康时返回非零值
scaffold [--full]只创建缺失的内容,绝不覆盖任何已有内容。--full 还会播种问题寄存器、索引、依赖看板、带实体寄存器的数据模型以及文档门控
finish [--gates]工作是否完成——每个仓库都已清理、推送并指向正确,没有保留租约。check 回答项目接线是否正确;此命令回答你是否已完成

$SKILL_DIR 是本技能自己的目录:在 Claude Code 插件下为 ${CLAUDE_PLUGIN_ROOT}/skills/agent-sync,其他地方为 ~/.agents/skills/agent-sync。每个会话解析一次并重复使用——不要猜测路径。每个命令都从项目根目录读取 .claude/agent-sync.json,除了列出的参数外不需要其他参数。

每个会话一个身份,以及如何决定

只有两个智能体获得两个身份时,租约才是租约。顺序在这里很重要,并且两端都曾出过问题:仅从 CLAUDE_SESSION_ID 派生 ID 曾导致一个会话有两个身份——它以一个身份获取租约,却被它自己的守卫作为另一个身份拒绝——而每个检出保持一个 ID 曾导致两个会话共享一个身份,这更糟糕。第二个问题是静默的:两个会话都获取了租约,都受到了守卫,而 release 拿走了调用者从未有过的租约。

顺序是:AGENT_SYNC_RUN_ID · CLAUDE_SESSION_ID · 启动此 shell 的会话 · 共享。

第三个很重要,因为普通的 shell 命令没有会话 ID,而钩子有。因此 SessionStart 将 .agent-sync/sessions/<CLI pid> 标记为它知道的会话,稍后的命令通过遍历自己的进程祖先找到一个被标记的 pid 来找到自己。为什么这样做而不是通过命令行解析:请参见 references/earned-rules.md。

当四个中的任何一个都无法建立时,运行会说——“此身份与此检出中的任何其他会话共享”——而不是将一个共享条目呈现为分离。

声明——重要的形式

acquire → 执行工作 → release

永远不要跳过 release,包括在失败时:被放弃的租约会阻塞任务直到其 TTL 过期,并且下一个智能体无法区分“正在进行中”和“一小时前崩溃”。

租约不是声明。 租约说明谁现在持有它并会过期;持久的声明是 Git 中的标签,由 acquire 写入并由 release 清除。一个事实,一个归宿——不要发明第三个记录所有权的场所。

在更改获取、过期、窃取或 ID 分配之前,请阅读 references/lease-protocol.md。

受守卫的文件

配置列出了多个智能体可能写入的注册表文件。在编辑其中一个之前:

python3 "$SKILL_DIR/scripts/agent_sync.py" guard docs/DECISIONS.md

退出码 2 是关于本次运行的:它没有持有任何租约——而不是别人持有那个文件。一个租约涵盖所有受守卫的文件;要么持有一个,要么什么都不写。拒绝会指明另一个运行及其键,因为“r-x 持有租约”在路径旁边会被重复为“r-x 持有此文件”。无论如何都不要编辑,也“不要只修一行”——一个被覆盖的决策看起来和决策一模一样。

Claude Code 的 PreToolUse 钩子会自动为你运行此命令。在其他地方,没有东西会这样做。

预留 ID

读取“下一个可用 ID”行不是预留它——两个智能体读取同一个数字并都会使用它。

python3 "$SKILL_DIR/scripts/agent_sync.py" reserve DEC   # → DEC-0216

分配是基于合并后的日志的位置——每一个分片,从不仅仅是本次运行的——因此每个智能体都会计算出相同的答案。已预留但未写入 Git?使用 release-id 释放它,否则该数字会成为看板报告为泄漏的空洞。

日志中的任何内容都绝不编辑或删除

日志是按顺序重放的,因此编辑会静默地重写其他智能体已经基于其行动的结论。通过追加来纠正:释放一个租约,release-id 一个未使用的 ID,用稍后的条目取代错误的实际构建条目。生成的页面是唯一的例外,并且一个丢失了 agent-sync:generated 标记的页面...

...(译文因原文截断而截断)

相关技能