结论与适用边界

Nacos Skill Sync 最值得学习的不是“用 Nacos 存放 SKILL.md”,而是它在不修改 Codex、Claude Code、Cursor 等 Agent 的前提下,增加了一层本地适配基础设施:远端 Registry 负责治理和分发,本机 daemon 负责同步,本地中心仓库负责保存真实文件,各 Agent 继续按原有方式读取自己的 Skill 目录。

这套设计适合研究以下问题:

  • 多个异构 Agent 如何共享同一份 Skill。
  • 团队 Skill 如何经过版本、审核和标签后分发到成员设备。
  • 本地修改、远端更新和冲突如何进入一个可观察的状态机。
  • Registry、Plugin、安装缓存与 Agent 会话分别承担什么职责。

本文中的 Host 指负责发现、注册和加载 Skill 的 Agent 客户端,例如 Codex、Claude Code 或 Cursor。

五层模型比“同步目录”更准确

这套基础设施至少包含五层。把它简化成“Nacos 目录软链接到 Agent”会掩盖真正的状态边界。

层级保存或处理的对象主要职责
RegistrySkill ZIP、版本、标签、可见性、审核状态团队事实源与治理控制面
同步客户端已订阅 Skill、MD5、本地哈希、同步状态拉取、冲突判断与可选的自动上传
本地中心仓库解压后的真实 Skill 目录本机多个 Agent 的单一文件副本
Agent Skill 目录每个受管 Skill 的同名软链接适配不同 Agent 原有的目录发现协议
Agent 会话已发现或已加载的 Skill 信息决定本次会话何时看到和使用新内容

新版源码把 profile 对应的中心仓库放在:

~/.nacos-cli/skill-sync/profiles/<profile>/skill-repo/<skill-name>/

早期文档和旧版实现可能显示为 ~/.nacos-cli/skill-repo/。无论具体路径如何变化,中心仓库都是本机目录,不是 Nacos Server 上可以直接挂载的远端文件系统。

软链接按 Skill 建立,而不是接管整个根目录

假设中心仓库存在真实目录:

~/.nacos-cli/skill-sync/profiles/team/skill-repo/pdf/

CLI 会在各 Agent 的 Skill 根目录里创建名为 pdf 的软链接条目:

~/.codex/skills/pdf
~/.claude/skills/pdf
~/.cursor/skills/pdf

每个链接的目标都是本机中心仓库中的 pdf。源码对应的核心操作是:

target := filepath.Join(repoPath, skillName)
link := filepath.Join(agentPath, skillName)
os.Symlink(target, link)

因此,Agent 的整个 Skill 根目录不会被替换。未被 Nacos 管理的本地 Skill、项目专用 Skill 和其他来源安装的 Skill 仍可共存。

按 Skill 建立间接层有三项收益:

  1. 渐进接入:可以只托管 pdf,不必一次迁移整个目录。
  2. 故障隔离:一个 Skill 的冲突不会阻塞所有 Skill。
  3. 来源共存:Nacos、Plugin、项目目录和个人 Skill 可以并存,但同名冲突仍需显式处理。

当前 Nacos CLI 源码没有“创建软链接失败后自动退化为复制模式”的托管逻辑。复制用于解除同步时保留本地副本;冲突处理还会单独备份已有目录。依赖软链接意味着 Windows 权限、容器挂载和受限文件系统需要额外验证。

远端更新采用条件轮询,不是服务端推送

Nacos 的 Skill 监听契约明确不提供推送通道。所谓 subscribeSkill 或“实时同步”,描述的是客户端最终获得变更回调或文件更新的使用语义,不代表底层存在服务端 Push、Watch Stream 或 Skill 专用长连接。

CLI 的更新步骤

在 Nacos mode 下执行 nacos-cli skill-sync start 后:

  1. CLI 先执行一次初始同步。
  2. 后台 daemon 默认每 30 秒查询一次已加入同步状态的 Skill,最小周期为 5 秒。
  3. 查询参数包含 Skill 名称、跟踪标签和本地记录的 MD5。
  4. 内容未变化时,服务端返回 304 Not Modified,不传输 ZIP。
  5. 内容变化时,服务端返回 200、新 MD5、解析后的版本和 Skill ZIP。
  6. CLI 暂存内容并计算目录哈希,随后更新本机中心仓库。
  7. Agent 目录中的软链接继续解析到同一路径,因此重新读取文件时会看到新内容。

这里的“轮询 Nacos”也不是扫描整个 Registry。daemon 只查询本地同步状态里已经订阅的 Skill。

Java SDK 的订阅语义

Java SDK 的 subscribeSkill 在调用时同步查询并预热一次缓存,随后默认每 10 秒执行条件查询。检测到 MD5 变化后,SDK 在客户端进程内部发布 SkillChangedEvent,再调用注册的 listener。

因此需要区分两个方向的“消息”:

  • 网络方向:客户端主动发送周期性 HTTP GET,Nacos 被动响应。
  • 进程内部:轮询发现变化后,SDK 主动调用本地 listener。

底层 HTTP 可能复用 keep-alive 连接,但连接复用不等于服务端推送。

MD5 把“是否变化”从下载动作中分离出来

每次都下载完整 ZIP 能实现同步,但客户端数量、Skill 数量和包体积增长后,流量会快速放大。Nacos 使用内容 MD5 进行条件查询:

客户端持有 MD5 A
服务端仍是 MD5 A → 304,无 ZIP
服务端变成 MD5 B → 200,返回新 ZIP

这是一个可迁移的设计原则:先用轻量指纹判断是否变化,再决定是否搬运实体内容。

远端 MD5 和本地目录哈希承担不同职责:

指纹比较对象用途
远端 MD5发布版本对应的 ZIP 字节条件下载、判断 Registry 内容是否变化
本地目录哈希解压后的 Skill 文件树判断成员是否在任一 Agent 路径中修改了内容

版本号适合表达兼容性和发布身份,内容指纹适合证明字节是否真的变化。两者不应互相替代。

本地共建依赖同步状态机,而不只是双向复制

软链接使多个 Agent 看到的是同一份本机文件。因此,在 ~/.codex/skills/pdf/SKILL.md 中修改内容,本质上也修改了中心仓库中的文件;Claude Code 随后读取的也是这份修改。

跨设备共建不能仅靠这个软链接完成。daemon 还需要区分以下状态:

状态含义自动行为
Synced本地与远端基线一致继续条件轮询
Local changes本地目录哈希偏离已同步基线保护本地内容,准备上传或等待人工处理
Uploaded本地内容已经上传为 Nacos draft等待审核和发布,不把它误判成线上版本
Conflict本地和远端都从共同基线发生变化停止自动覆盖,要求明确选择来源
Upload blocked存在 reviewing 版本或他人修改的 draft阻止覆盖远端协作者的工作

当前自动上传逻辑要求连续两轮观察到相同的本地哈希,避免文件仍在连续写入时上传半成品。上传前还会检查远端是否存在 reviewing 版本,以及已有 editing draft 是否能通过上次记录的 MD5 证明属于当前客户端。自动上传只形成 draft,不等于自动审核和发布。

这里值得学习的是:双向同步的难点不是复制,而是判断谁改了什么、基于哪个共同版本修改,以及什么时候必须停止自动化。

分发完成不等于 Agent 已经激活新版本

中心仓库更新后,文件系统层面的传播已经完成,但 Agent 是否立即使用新内容是另一个状态:

  • 新会话通常会重新扫描 Skill 目录,因此更容易看到新版本。
  • 已经运行的会话可能缓存 Skill 名称、描述或已经加载的正文。
  • 某些 Agent 在实际触发 Skill 时重新打开文件,某些 Agent 需要新会话或显式 reload。
  • daemon 不感知 Codex Task、Claude Code Session 或 Cursor Chat 的生命周期。

这说明能力分发系统至少要分别观察四个问题:

  1. 远端事实源是什么版本。
  2. 本机缓存是什么版本。
  3. Host 注册了什么版本。
  4. 当前会话实际加载了什么版本。

只检查“文件已经下载”无法证明用户正在运行新版本。

Registry 与 Plugin 解决的是不同层级的问题

Nacos Skill Registry 和 Plugin 都能“分发 Skill”,但交付边界不同。

维度Skill RegistryPlugin / Marketplace
核心对象独立 Skill 包及其版本、标签和治理状态Skill、Hook、MCP/App、Agent、脚本等组合包
主要价值私有存储、审核、可见性、标签路由、跨 Agent 分发Host 原生安装、依赖、权限声明、组件注册和卸载
本地适配CLI daemon + 本地中心仓库 + 软链接Host 的安装目录、缓存和 manifest
更新粒度单个 Skill 或标签整个 Plugin 版本
适合场景团队需要治理大量可独立演进的 Skill一项能力需要多个组件协同安装

Registry 不应替代 Plugin:需要同时安装 Hook、MCP Server、授权配置和 UI 元数据时,Plugin 的交付边界更完整。Plugin 也不天然替代 Registry:一个 Plugin Marketplace 通常解决安装包的发现和升级,不一定提供 Skill 级审核、标签切换、细粒度可见性和跨 Host 的统一内容治理。

二者可以组合:Registry 保存和治理可复用 Skill,Plugin 负责安装特定 Host 所需的适配器、工具和权限配置;适配器再把 Registry 中选定的 Skill 暴露给 Host。

如果只是个人安装一两个稳定 Skill,Registry mode 很可能属于过度建设。Local mode、Git 仓库或 Host 原生 Plugin 已经足够。Registry 的收益主要在多成员、多设备、多 Agent、审核与追溯同时出现时成立。

可以迁移到其他系统的设计原则

用本地适配层兼容异构消费者

不要要求每个 Agent 都实现 Nacos SDK。daemon 把远端 Registry 转换成各 Agent 已经理解的本地目录协议,降低了接入成本。这种模式也适用于配置、模型、模板和策略包分发。

对单个资源建立间接层

按 Skill 建立软链接,而不是映射整个根目录,使系统能够渐进迁移、混合来源并隔离故障。类似设计还包括按包建立缓存路径、按服务建立路由别名和按模型建立版本指针。

把使用语义与传输协议分开命名

subscribeSkill 可以向调用方提供订阅回调,但底层仍是轮询。评审接口时应分别询问:调用方获得什么语义、客户端如何检测变化、网络上采用什么传输机制。

将可变标签与不可变版本分开

版本保存可追溯内容,lateststable 等标签负责把客户端路由到某个版本。回滚可以移动标签,但客户端仍要在下一轮查询后才能生效。这是控制面变更,不是即时推送。

冲突时默认保护内容

本地和远端同时变化时,自动选择任一侧都可能丢数据。进入 Conflict、保留双方证据并要求显式决策,比“最后写入者获胜”更适合人机共建内容。

把分发、安装、注册和会话加载分别验证

Registry 已发布、文件已下载、Host 已发现和当前会话已加载是四个不同事实。状态页和排错命令应分别展示这些事实,而不是只显示一个模糊的“已同步”。

不应直接照搬的部分

设计风险引入前需要回答的问题
固定周期轮询一致性延迟;请求数约随客户端数、Skill 数和轮询频率增长是否需要抖动、退避、批量查询或推送通道?
依赖软链接Windows 权限、容器挂载、沙箱和安全扫描兼容性不同是否需要受支持的复制、junction 或 Host 原生注册方式?
所有 Agent 共用可写目标任一 Agent 的修改会立即影响其他 Agent哪些目录只读,谁可以产生本地修改?
自动上传本地变化误把临时编辑上传成团队 draft默认是否关闭,如何显示待上传 diff?
标签跟踪客户端最终一致,无法保证同一时刻全部切换是否需要批次、设备清单和发布完成度?
文件更新后依赖 Host 重载当前会话可能继续使用旧内容如何观测 Host 注册版本和会话版本?

如果客户端规模扩大,条件轮询仍应补充随机抖动、指数退避、服务端限流和可观测性。否则所有客户端按相同周期启动,可能形成周期性请求峰值。

设计同类系统时的检查清单

  • 远端事实源、本地缓存和消费者入口是否清晰分层?
  • 更新单位是整个产品、一个 Plugin,还是一个 Skill?
  • 版本身份和内容指纹是否分别定义?
  • 未变化时能否避免传输完整内容?
  • 本地修改是否允许,如何识别共同基线?
  • 本地与远端同时变化时是否默认保护双方内容?
  • 自动上传、审核和发布是否是三个独立动作?
  • 回滚改变的是版本、标签还是本地缓存?生效延迟是多少?
  • 文件落盘后,如何证明 Host 已注册、当前会话已加载?
  • 软链接、复制和 Host 原生安装各自支持哪些平台?
  • Registry 和 Plugin 是否各自解决了真实问题,还是重复建设两套分发入口?

资料来源


相关笔记