---
title: "快照与 ref"
description: "ref 属于产生它的那次快照。这一页讲它的构成、重新识别方式和失效后的动作。"
---

> Documentation Index
> Fetch the complete documentation index at: https://agent-desktop.ndjp.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 快照与 ref

`snapshot` 把一棵可访问性树读成 JSON，并给其中每个智能体能操作的元素分配一个 ref。
智能体之后所有的动作都指向这个 ref。

## ref 的构成

```text
@s8f3k2p9:e8
└─┬─────┘ └┬─┘
  │        └ 元素在这次快照里的序号
  └ snapshot_id
```

id 两段缺一不可。只写 `@e8` 会被拒绝并返回 `INVALID_ARGS`——脱离快照的序号没有含义，
下一次快照里的 `e8` 很可能完全是另一个元素。

## ref 指向什么

一个 ref 背后是这次快照里那个节点的读数：角色、名字、可用动作，以及它是从哪棵子树里来的。

```json
{
  "role": "treeitem",
  "name": "Documents",
  "ref_id": "@s8f3k2p9:e8",
  "available_actions": ["Click"]
}
```

`available_actions` 是选择动作的依据。对不在列表里的动作发起请求，会被平台拒绝。

## ref 如何被重新识别

动作执行前，agent-desktop 会从存储的条目重新识别这个 ref。从 2.6 起，Windows、Linux，
以及 macOS 上无法取得元素地址的 ref，采用同一套证据规则：

- 快照必须完整读取过它的窗口，并证明捕获时恰好有一个元素匹配它的完整谓词：
  角色加上记录的稳定字段（native id、稳定 name、稳定 description）。
- 解析时在当前窗口重新计数这个谓词，只接受恰好一个匹配。
- 骨架、深度截断和 `--root` 下钻都只是同一次完整窗口观察的浅层呈现，它们的 ref 仍然按整个窗口计数。
- 值（value）和位置（bounds）永远不参与身份判断。

如果快照需要完整读取窗口才能证明 ref，但没能读完，`data.identity_unproven` 为 `true`。
这不影响 `complete` 对当前呈现树的描述，但这批 ref 都会以 `identity_not_unique` 失败；
用更大的 `--timeout-ms` 重拍。

macOS 上带元素地址的 ref 走另一条路：地址失效，或进程、角色、标识符不再匹配时，
直接返回 `retained_object_stale`，不执行搜索，避免落到相似元素上。

## 什么时候失效

界面是活的。产生 ref 的那次快照对应的界面状态一旦被替换，ref 就不再安全。

| 情形 | 结果 | 该怎么做 |
|---|---|---|
| 只写了序号，没带快照 id | `INVALID_ARGS` | 用完整的 `@snapshot:序号` |
| 元素消失，或 macOS 的元素地址失效 | `STALE_REF`（`details.kind` 为 `saved_ref_absent` / `retained_object_stale`） | 重新快照，改用新快照里的 ref |
| 捕获之后出现了完全相同的元素 | `AMBIGUOUS_TARGET`（`saved_ref_ambiguous`） | 换一个在窗口内唯一的控件；重新快照会同时拒绝两个双胞胎 |
| 捕获时没有证明 ref 唯一，或窗口没能在超时内读完 | `AMBIGUOUS_TARGET`（`identity_not_unique`） | 换唯一控件；只是超时则用更大的 `--timeout-ms` 重拍 |
| 用 `--app` 选目标时应用有多个窗口 | `AMBIGUOUS_TARGET`，`error.details` 列出候选窗口 id | 用 `--window-id` 指定其中一个 |

最后一行的多窗口歧义是**目标选择**层面的问题，和 ref 身份歧义不是一回事。遇到
`AMBIGUOUS_TARGET` 先读 `details.kind`，不要一律用 `--window-id`。

上述 ref 身份失败都带 `details.retryable: false`，并且是 `not_delivered`。
它们的 `disposition.retry` 仍然是 `safe`，因为什么都没发送；但重复同一个 ref 会得到同样的拒绝。
唯一的例外是 `resolution_identity_unknown`：实时字段读不到导致身份计数未完成，它会以
`TIMEOUT` 报告，可以重试。

> **不要缓存 ref**
>
> 把 ref 存进变量、写进文件、或者在两轮循环里复用它，都会在下一次界面变动时炸掉。
> ref 是一次性的坐标，不是稳定的元素标识符。

## 正确的用法

一次动作之后必然跟一次读取。这不是保守，是因为动作成功只说明请求被接受了，
不说明界面按你的预期变了。

```bash
agent-desktop snapshot --app Finder -i              # 读，拿到 snapshot_id
agent-desktop click @s8f3k2p9:e8                    # 用这次读到的 ref 动作
agent-desktop snapshot --app Finder -i              # 再读，核对结果
```

需要「等到某个值变成什么」时，用 `wait` 而不是反复快照：

```bash
agent-desktop wait --element @s8f3k2p9:e6 --predicate value --value 7
```

## 下一步

- [骨架与下钻](/concepts/skeleton-and-drilldown/)：怎么在密集界面里拿到合适的快照粒度。
- [错误与恢复](/output/errors/)：每个错误码的处置方式。

Source: https://agent-desktop.ndjp.net/concepts/snapshots-and-refs/index.mdx
