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

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

Bruce · 2026-11-05 · 原始记录: 2026-10-28

https://flydogai.com/bruce/blog/one-store-one-agent?lang=zh

<a id="two-different-answers"></a>

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

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

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

<a id="store-identity"></a>

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

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

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

<a id="lock-and-record"></a>

## 锁负责所有权，记录负责发现

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

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

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

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

<a id="context"></a>

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

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

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

<a id="regression-and-lifetime"></a>

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

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

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

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