BRUCE BLOG / ENGINEERING

为什么一个数据目录只运行一个 Agent

桌面里有会话,手机却看不到。一次双服务并行的问题,让我们重新明确服务的所有权、发现方式和客户端上下文。

Bruce · 发布于 · 原始记录

两个入口,各自看起来都正常

一次排查中,驻留 Agent 与开发窗口启动的 Agent 同时服务于同一个数据目录。窗口只去一个预想端口寻找服务,没找到就启动了第二个;手机连接的入口则被先到的服务占用。桌面里刚出现的会话,手机上没有,两边也没有明显报错。

这不是先增加一次刷新就能说明的问题。两个入口实际上找到了不同的服务实例。2026 年 10 月的修复,从一条更基础的规则开始:同一份状态,需要明确谁拥有它。

边界是数据目录,不是整个设备

Bruce 现在以数据目录为服务所有权的边界。同一个目录只能由一个 Agent 持有;第二个启动请求应该找到已有服务,说明谁在服务,然后退出。客户端也因此不需要各自猜测固定端口。

独立数据目录可以有独立 Agent,这是隔离环境的正常用法。测试需要自己的存储,不能和人的任务列表混在一起。“一台机器只能有一个进程”会把这个有用的边界也抹掉。

锁负责所有权,记录负责发现

实现分成两件事。操作系统文件锁声明谁可以服务这份状态,Agent 在接触存储前先取得它。持有者退出后,锁会释放;即使文件还在,也不能把文件存在误读为锁仍被占用。

获得所有权后,Agent 写下自己的进程、监听位置和启动信息。记录先完整写到临时文件,再改名,避免读到半份内容。其它入口读取记录,寻找正在服务的实例。

记录可能比进程活得更久,所以读到记录还不等于服务在线。调用方仍要确认记录中的地址能够应答。所有权、发现和可用性各自说明,故障才更容易定位。

同一个数据目录
  存储锁   → 谁可以服务这份状态
  发现记录 → 去哪里寻找它
  实际应答 → 它现在是否可用

共享服务,仍然要明确访问哪个项目

找对服务只是第一步。窗口、手机和其它客户端还需要说明项目、会话与请求身份。服务不能根据最近活跃的窗口,猜测手机此刻想访问哪个目录。

这样的划分把界面与工作状态分开:客户端负责表达当前上下文,服务负责它持有的任务、终端和记录。换一个入口不应无意中换成另一套工作;是否允许访问,则由对应的权限与连接配置决定。

把边界写进测试,也把生命周期讲清楚

回归测试覆盖了同目录的第二次持有会被拒绝、释放后可以再次取得所有权,以及不同目录互相独立。这些条件比一次“看起来连上了”更具体。

关闭一个客户端和重启 Agent 是不同操作。存储锁与发现记录不会让终端进程跨 Agent 重启存活。持续工作仍依赖主机在线、服务设置和实际进程生命周期;无损交接是需要另外设计与验证的能力。

  • 先确认两端是不是同一份数据、同一个服务,再讨论同步。
  • 用明确的所有权避免启动竞争,用发现记录避免端口猜测。
  • 把项目、会话和身份作为上下文说明,不靠最近一次界面活动推断。
  • 隔离测试存储,保留人的任务与会话现场。

下载 Markdown