# 智能体记录冲突时如何调查 API 审计日志

API 提供商说某个请求修改了生产数据。智能体记录却显示它根本没有执行到那一步。两种说法可能都是真的。把任一日志当成最终结论，团队就会把一个可控的差异变成糟糕的事件响应。

把这次操作当作一串观察结果来调查。确认是谁发起了运行，智能体尝试做了什么，什么内容越过了凭据边界，提供商接受了什么，以及之后发生了哪些变化。时间戳可以帮助排列这条链路。请求标识符可以把各环节连起来。结果和状态能告诉你它是否产生了影响。缺失的事件同样是证据，但要先排除记录正常消失的常见原因。

我见过有人一上来就做电子表格，然后立刻按时间排序。这种做法顺序反了。时间戳往往是现场最弱的关联字段。应先从稳定标识符和不可变导出开始，再用时间检验提出的顺序是否合理。

## 在有人刷新仪表板前保留记录

在筛选、重试、撤销访问权限，或请提供商支持团队调查之前，先收集原始证据。交互式仪表板会变化，保留任务会运行，而一次重试可能产生第二个事件，让第一个事件变得难以判断。

建立一个带事件 ID 的案件文件夹，收集原始导出文件，不要只保存截图。应包括智能体会话记录、单次操作记录、提供商审计导出、受影响系统的应用日志，以及团队能获得的任何出口记录。记录收集时间，统一使用 UTC；记录收集人、使用的账户或角色，以及生成每份导出文件时采用的筛选条件。

收集后为每个文件计算哈希。如果操作系统提供标准的 SHA-256 工具，一条 shell 命令就够了：

```
$ shasum -a 256 provider-events.json agent-activity.json
81b5777b8416320fe26cb8a8dddb6a9e736fab4f5e7aa5812bf6afeffc5f4e82  provider-events.json
a1e98c01992b51104fbc8c5fcbaa78e65db31f1edb3e546f4c14d0e6d3673ba  agent-activity.json
```

把哈希写入普通案件笔记。哈希不能证明提供商导出文件完整，只能证明你收集后的工作副本没有悄悄变化。这是两种不同的说法，而事件报告经常把它们混在一起。

不要把 JSON 先“清洗”成电子表格。规范化可能丢弃重复字段、数组顺序、毫秒级时间、小数秒、空值，以及后来解释不匹配所需的确切请求正文。保留一份未改动的导出文件，再创建单独的解析工作文件。

如果差异可能涉及凭据泄露或未经授权的使用，应以保留事件顺序的方式限制访问。可以的话，撤销正在运行的智能体会话，或锁定操作路径。除非正在发生的滥用要求立即轮换凭据，否则不要在收集提供商最近的审计记录前轮换提供商凭据。轮换有时确实必要，但也可能抹掉最后一条归因路径。

## 请求标识符优先于时间戳

通过能够跨越边界的标识符关联记录：提供商请求 ID、客户端提供的关联 ID、幂等键、写入后返回的对象 ID，以及提供商有文档说明时的追踪 ID。保留每个标识符，因为提供商可能会在请求头、审计事件、支持导出文件和错误正文中显示不同的标识符。

最理想的情况很简单。操作记录显示智能体调用了 `POST /v1/invoices`；响应头包含 `x-request-id: req_72M...`；提供商导出文件中也有 `req_72M...`；创建的发票则是 `inv_4P...`。这样，你就把意图、发送、提供商处理和持久状态关联起来了。

更困难的情况更常见。提供商可能只有在解析请求后才分配请求 ID。此时如果 TLS 失败，请求根本没到达应用，就不会有提供商请求 ID。网关可能生成一个 ID，下游服务又生成另一个。异步 API 可能先返回任务 ID，几分钟后才写入请求的对象。应记录每个 ID 由哪个边界签发，不要把它们压成一个 `request_id` 字段。

使用一张能显出不确定性的核对表：

| 字段 | 本地操作记录 | 提供商记录 | 受影响系统 |
| --- | --- | --- | --- |
| 客户端关联 ID | `run-18-call-42` | `run-18-call-42` | 缺失 |
| 提供商请求 ID | 响应中为 `req_72M...` | `req_72M...` | 缺失 |
| 方法和路径 | `POST /v1/invoices` | `POST /v1/invoices` | 发票已创建 |
| 结果 | `504 timeout` | `202 accepted` | 任务 `job_91...` 已完成 |
| 事件时间 | `10:04:03.219Z` | `10:04:03Z` | `10:04:11.802Z` |

这张表揭示了一种常见故障：调用方超时，但提供商接受了写入，并在调用方放弃后继续处理。如果因为调用方的结果就说操作“失败”，那是错误的。如果把提供商日志称为“智能体有意执行该操作的证据”，同样错误。证据表明，智能体发送了一个提供商接受的请求，随后调用方没有及时收到响应。

如果提供商允许写入操作使用幂等键，就使用它。IETF 的 Idempotency-Key Internet-Draft 很好地说明了实际目标：客户端重试不安全的 HTTP 操作时，不会意外产生两次相同效果。不同提供商的行为各不相同，因此要阅读提供商关于保留期限和匹配规则的文档。不要假设只匹配端点就足够。

对于接受自定义请求头的 API，在调用前生成关联 ID，并在有文档说明的请求头中发送，例如 `X-Client-Request-ID`。将它与本地事件一起保存。绝不要把密钥、提示词、用户数据或原始令牌放进这个 ID。安全的值在案件之外没有含义，例如 `case-2025-041-run7-call18`。

## 时间可以推翻一个说法，却很少能单独证明它

使用时间戳界定事件范围，并发现不可能的顺序。除非所有来源都没有更好的标识符，否则不要把时间戳作为主要身份字段。

RFC 3339 定义了常见的互联网时间戳格式，并建议使用以 `Z` 结尾的大写 UTC 形式，例如 `2025-03-08T10:04:03.219Z`。解析后仍要保留原始字符串。`10:04:03Z` 和 `10:04:03.219Z` 的区别很重要，因为一个来源可能只精确到秒，另一个来源则报告毫秒。

为每个相关事件创建四个时间字段：

- 导出时的原始时间戳
- 规范化后的 UTC 时间戳
- 事件类型，例如已发送、已接受、已完成或已记录
- 时钟所有者，例如本地 Mac、提供商边缘节点、提供商工作进程或数据库

提供商边缘节点的时间戳早于本地“收到响应”的时间戳，并不矛盾。提供商工作进程的完成时间可以晚于智能体进程退出时间。本地时钟发生漂移，也可能让操作看起来早于会话开始。这些都是正常机制，不是篡改证据。

围绕已知锚点建立时间窗口，通常使用请求 ID 或会话开始时间。窗口一开始要足够窄，避免意外关联。只有在能说明原因时才扩大，例如提供商只记录到秒、操作是异步的，或你根据可信参考测量出了时钟偏差。把选择的窗口写入案件笔记。“我们搜索了大致的时间范围”不是方法。

注意日志摄取时间。许多系统同时提供 `event_time` 和 `created_at`。前者表示发出系统认为事件发生的时间，后者可能表示聚合器接收或建立索引的时间。延迟到达不代表延迟执行。如果某个事件看起来是在事件开始后才出现，先检查这两个字段，再构建叙述。

一个有用的顺序测试只问一件事：提出的故事是否可能成立。提供商在 10:04:03 记录了事件，而本地发送时间是 10:04:03.219，如果时钟不同或提供商向下取整，这可能成立。但如果提供商说任务在 10:04 被接受，声称它在 10:02 完成就不可能成立，除非你混淆了两个事件或误解了字段含义。

## 区分已尝试、已发送、已接受和已完成

团队常常用“调用”一词压缩四种不同状态。这个捷径造成了大多数日志争议。

智能体可以通过构造请求来尝试操作。本地组件可以把字节发送到远程端点。提供商可以接受请求。下游工作进程可以完成效果。每个阶段都有不同的记录和失败方式。

HTTP Semantics 规范 RFC 9110 说明，状态码描述的是服务器的响应，不是调用方完整的经历。`202 Accepted` 明确表示处理已被接受，但尚未完成。`204 No Content` 表示服务器成功完成了请求，但它本身不能解释所有下游影响。网络超时可能完全没有 HTTP 响应，但服务器仍可能处理了请求。

为每个有争议的事件标记以下状态之一：

- **仅已尝试**：存在本地操作记录，但没有证据表明数据已发送到网络。
- **已发送，结果未知**：请求离开了本地边界，但调用方没有收到可靠响应，提供商也还没有可搜索的记录。
- **已接受，效果待定**：提供商返回了接受结果或任务引用，但还没有完成状态。
- **已完成**：提供商结果与观察到的状态变化相互吻合。
- **相互矛盾**：考虑字段含义后，各来源的说法无法同时成立。

“结果未知”是合理结论。不要因为智能体收到了异常，就把它重新标为失败。对于写入操作，如果没有幂等机制或读回检查来保证重试安全，这个异常应阻止自动重试。

反过来的错误同样严重：`200` 响应不代表预期的业务结果已经发生。端点可能会为语法有效的请求返回成功，但之后的校验、异步任务或下游依赖可能拒绝预期变更。检查 API 合约中代表完成状态的返回对象、任务状态或目标系统事件。

## 缺失事件需要有边界的解释

缺少记录可能意味着请求从未发生，也可能意味着你查错了服务、使用了错误的账户范围、查错了保留层级，或者期待了提供商根本没有承诺发出的记录。

按照固定顺序处理缺失事件。第一，确认准确的账户、项目、区域、环境和 API 产品。提供商经常会按其中一个或多个字段隔离审计视图。第二，使用每个标识符搜索，再按有文档说明的时间窗口和端点搜索。第三，确认提供商记录的是已接受请求、被拒请求、数据平面调用、控制平面调用，还是仅记录管理操作。第四，检查保留期限和导出延迟。第五，确认代理、SDK 或异步队列是否会生成与你预期不同的提供商事件。

有一个具体故障值得记住。智能体提交 `POST /exports` 后遇到连接超时。团队用本地客户端 ID 搜索提供商审计日志，却什么也没找到。他们随后重试，接着收到两条导出完成通知。

第一次请求发往区域性摄取端点。他们查看的审计界面只显示控制平面事件。提供商用生成的导出 ID 记录任务，而不是使用客户端请求头；任务服务在超时后完成了任务。这一连串情况不需要任何恶意活动。重复结果来自在检查幂等键、任务查询端点或业务级标记前重试写入操作。

这个故障也说明了为什么必须谨慎描述缺失。应说“我们收集的导出文件在这个窗口内没有匹配的数据平面事件”，而不是“提供商没有记录”。前一种说法指出了证据及其边界，后一种说法提出的主张往往无法得到支持。

如果某类日志本应存在却缺失，请保留查询参数，并收集提供商关于预期事件覆盖范围的文档。没有确切请求 ID、账户范围、UTC 窗口、端点和证据哈希的支持请求，会浪费好几天。

## 检查结果不能只看状态码

比较请求声明的意图、响应正文和可观察到的效果。状态码只说明协议交换情况，不能说明请求范围是否正确、提供商是否应用了默认值，或智能体是否发送了过期标识符。

对于每个操作，在 API 提供这些字段时都应记录：HTTP 方法、规范化路径、请求 ID、幂等键、操作者或凭据身份、状态码、响应正文哈希、返回的对象 ID，以及任何异步任务 ID。向更广范围共享前要删除凭据和敏感载荷数据，但如果政策允许，应保留受保护的原始文件。

响应正文哈希有助于区分两个表面上相同的 `200` 记录。在美化 JSON 之前，先根据原始响应字节计算哈希。如果 JSON 在不同层之间字段顺序发生变化，同时保留原始字节和规范化解析副本。不要声称相同的状态码意味着相同的响应。

然后查询应该存在或发生变化的资源。对于创建操作，获取返回的对象 ID，并比较其创建者、创建时间和属性。对于更新操作，如果服务提供了版本、修订号或审计条目，就获取并检查它。对于删除操作，检查对象是否已不存在，以及提供商审计记录是否将删除归因于同一凭据。

这正是宽泛凭据会让调查变得困难的地方。如果多个工具共享一个 API 令牌，提供商通常只能告诉你该令牌执行过操作，却无法告诉你是哪个本地进程或人员发起了操作。把凭据身份视为边界标记，不要把它当成操作者身份。

## 只有记录了边界，网关记录才有用

操作网关在智能体与需要凭据的操作之间提供了一个清晰的观察点。它应记录发起进程或运行、获批的授权状态、请求的操作、返回给智能体的结果，以及足以关联提供商记录的标识符。不能把凭据交给智能体，再把由此产生的本地遥测称为审计轨迹。

Sallyport 将 API 和 SSH 凭据保存在加密保险库中，由自己执行操作，并把结果而不是密钥返回给智能体。它的 Sessions 和 Activity 日志来自一份防写的加密哈希链审计日志，为调查人员同时提供运行级和调用级记录。`sp audit verify` 可以在离线状态下通过密文验证这条链，无需保险库密钥。

这种设计解决了一个具体缺口。提供商日志可以识别凭据和 API 请求，却无法告诉你哪个智能体进程获准使用该凭据，也无法证明智能体从未看到密钥。只有当凭据边界确实位于生成记录的组件内部时，本地审计记录才能回答其中一部分问题。

不要夸大网关记录的能力。它无法报告绕过自身的请求，也无法把含糊的提供商 API 变得精确。它能提供更好的证据比较位置，并让你在调查继续时撤销已知的智能体运行。

## 用带证据和限制的主张写结论

好的结论能让另一位工程师在不继承你的假设的情况下复现推理。分别为调用、权限、请求发送、提供商处理和观察到的效果撰写主张。为每个主张附上支持它的标识符、时间戳、源文件和字段含义。

使用与可信度相匹配的语言。“操作日志记录进程 X 在这个时间请求了 `POST /v1/invoices`。”“提供商导出文件包含一个具有相同提供商请求 ID 的请求。”“发票已经存在，其属性与记录的响应相符。”这些都是可测试的陈述。只有在关联关系和凭据边界都支持时，才可能合理地说“智能体确定导致了这张发票”。

当记录不一致时，应在最终报告中保留这种不一致。不要平均时间戳，也不要丢弃不方便的来源。说明最可能的解释、已经排除的替代解释，以及仍然缺少的证据。如果无法确定写入是否完成，就将其记录为未知，并在允许自动重试前修复 API 路径。

事件之后最实际的改动通常很小，也不引人注目：要求关联 ID，保留正确的提供商事件类别，保存带小数秒的 UTC 时间戳，并为写入操作使用幂等机制。这些控制措施能把下一次争议从取证争论变成一次简短的核对。
