Repository navigation
v2026.10.02.1
v2026.10.02.1 — 后台同步主备改为持久租约 · 备副本自动接管
纯客户端发布:
CLIENT_VERSION2026.09.21.3 → 2026.10.02.1(mcp/server.py、mcp/updater.py、mcp/auto-sync.py、server/client_update.py),各端经/api/client/manifest自动更新、Agent 重启后生效。无 schema 变更,无服务端 API / Web UI 改动。触发场景:同一台机器开多个 Agent 窗口(每个窗口各 spawn 一个 MCP 客户端),实测只有一份在跑后台同步,而角色由"启动抢锁"一次性决定——关掉拥有后台循环的那个窗口后,其余窗口的后台同步会静默停摆。
Fixed(后台同步的角色与并发)
- 主/备角色改为持久租约,备副本会自我提升:原先角色只在启动时决一次(O_EXCL 锁每周期释放 ⇒ 后启动的副本总能赢下已释放的锁并自认主,副本越多越像多主;而抢锁输家则终身 standby——omp 这类不重拉 MCP 进程的宿主上等于永久停摆)。改为
hermes-sync-<agent>.primary.json租约(属主 pid + 心跳 + 上次成功同步时间,原子写、每周期刷新):启动时属主仍活着就做备(同时启动仍由 O_EXCL 锁选出唯一主),备每周期复查租约并在属主失联后接管继续跑后台循环。「失联」= 心跳已过一个完整HERMES_SYNC_INTERVAL且 pid 探测说死,或心跳超过HERMES_SYNC_LEASE_STALE_S(默认INTERVAL+120,兼作 pid 复用兜底);心跳优先,避免误判造出第二个主,代价是失联后最多晚一个周期接管(与同步间隔同量级)。顺带消除"每开一个窗口就重复一次启动全量同步"。 - Windows 进程判活不再失真:
os.kill(pid, 0)实测三种问题——对pid 4抛SystemError(不是OSError,原实现接不住,启动任务直接崩、角色卡在starting);对部分活进程报WinError 87(被当成死的 ⇒ 会窃取活写者的锁);对刚退出但父进程仍持有句柄的进程报"活"。改用OpenProcess(SYNCHRONIZE|QUERY_LIMITED_INFORMATION)+ 零超时WaitForSingleObject,不可判定(access denied)按"活"处理;POSIX 分支不变。 - OpenClaw 常驻同步进程真的持锁了:
mcp/auto-sync.py的文档一直写"与 MCP server 共用同一把锁,绝不并发",但循环体从未取锁(会与 MCP server 的后台周期并发写同一本地库)。现在每轮经run_once()取同一把跨进程锁,拿不到就跳过本轮、下一 tick 重试。
Added(省事与可观测)
- 启动同步新鲜度:上次成功同步的时间记在租约里(同机多窗口共享),距上次成功不足半个同步间隔就跳过启动拉取(另一个窗口刚同步过);手动
sync_full/sync_pull/sync_push成功后同样刷新。 sync_status增加instance段:pid、role、background_sync、lock_holder_pid、lease/lease_owner_pid/lease_owner_alive、log_file、client_version—— "谁在同步、有没有重复"不必再猜。- 日志落盘:
hermes-sync-<agent>.log(与锁同目录,单代轮转为.log.1,HERMES_SYNC_LOG_MAX_BYTES默认 512 KiB)。宿主通常丢弃 MCP stderr(omp 全平台不留存),此前客户端进程崩溃没有留下任何可查线索;stderr 同时改为以 UTF-8 字节写出,避免中文 locale 下 GBK 编码让对方(含 MCP SDK 自身的 stdio 客户端)reader 线程解码失败。 - 新环境变量:
HERMES_SYNC_LEASE_FILE、HERMES_SYNC_LEASE_STALE_S、HERMES_SYNC_STARTUP_DELAY_S、HERMES_SYNC_LOG_FILE、HERMES_SYNC_LOG_MAX_BYTES。
测试与验证
- 客户端 208 项 OK(194 → 208:新增租约存活判定、备按周期延后与自我提升、启动选举与让位、启动新鲜度、
sync_status实例段、日志落盘与轮转、auto-sync持锁;删除把旧行为钉死的 standby 测试)。 - 真机冒烟 13/13(两个真实
mcp/server.py进程 + 私有锁/租约/日志目录 + server 指向不可达端口,避免触碰本地库):A 选举为主 → B 判为备且不写库 → 杀掉 A 后一个周期内 B 自我提升 → 新副本立刻让位给活属主,sync_status全程如实回报角色与属主。 - 236 部署:4 个文件对上一版提交
b8a55ae逐文件预检通过、上传后回读 sha256 一致、py_compile通过;重启后 active、journal 无 error、回环/health=200。线上核对 9/9 PASS:/api/client/manifest?agent=omp&v=2026.09.21.3返回 2026.10.02.1 +update_available=true、包内manifest.json版本一致且mcp/server.py的 sha256 与清单相符、包内含租约代码且不再含旧的 "standby 终身" 文案、含新存活探测、mcp/updater.py为新版本号、mcp/auto-sync.py持锁。
生产状态:236 已部署(代码提交 b07eb4d,记录提交 86a5ce5,tag v2026.10.02.1)。已装客户端会在下一次更新检查(每小时一次,或 Agent 重启后 60 秒)自动拉取新版本,重启 Agent 后新逻辑生效(正在运行的 MCP server 进程仍持旧代码)。