# Agent Note: 反馈门控的会话遥测

Status: implemented

[English](2026-08-05-feedback-gated-session-telemetry.md) | 中文

## 问题

会话遥测原本只有一种已挂载行为：每条已接受记录都立即进入上报后端。部署方需要两种更严格的策略，且不替换插件：只有用户记录反馈时才释放该会话的遥测，或禁用上报并仍向用户说明反馈的去向。该策略必须保留遥测 seam 在记录抵达后端之前脱敏的边界。

## 决策

`@deepseek-ai/dsh-session-telemetry-otel` 向 TypeScript 调用方公开以字符串为值的 `SessionTelemetryMode` 枚举，并在序列化配置中接受相同的三个大写 `mode` 值：

- `FULL` 显式选择向已配置 OTel 流水线即时投递。
- `FEEDBACK_ONLY` 在追加 `feedback/record` 时读取权威会话日志，并交接截至该事件的未释放前缀。该边界后追加的记录会留在本地，直到另一个反馈事件。
- `DISABLED` 是[默认值](2026-08-10-telemetry-default-off.md)，不构造导出器、处理器或日志提供方，并在观察到 `feedback/record` 时输出警告，说明什么都不会共享，且反馈仍留在本地。

通用遥测协调器拥有 `live` 与 `on-demand` 捕获。实时捕获在会话 firehose 上投影、深拷贝、脱敏每个事件，并将其交给后端。按需捕获不注册持续捕获监听器；`captureSession(session, throughSeq)` 从 handoff 游标起读取权威日志，直至含边界的指定序列号，然后投影、深拷贝、脱敏并交接该前缀。游标只为已交接记录推进。[无缓冲回放决策](../simplification/2026-08-06-buffer-free-feedback-telemetry.md)说明了按需路径为何使用权威日志而非记录副本。

模式解析采用封闭式检查，并在设置前失败：通过直接构造传入未知值时，会在读取传输配置前失败。只有 `FULL` 向 SDK 流水线开放公共服务的 `emit()` 路径。`FEEDBACK_ONLY` 向其按需协调器提供私有后端能力；其监听器向 `captureSession()` 传递事件的唯一条件，是该事件与那个 `feedback/record` 对象身份完全相同，且该对象已存储于 `session.events[event.seq]`。`Session.append` 在发布 `session/event` 前已提交该对象，因此回放包含该反馈，但不会越过其边界。`DISABLED` 既不创建该能力，也不创建 SDK 流水线，并且不检查导出器配置。

## 考虑过的替代方案

**会话在首次反馈后永久开放。** 已否决，因为后续工作会在用户未再次提交反馈的情况下被共享，而且插件需要额外的会话开放状态。每次反馈只释放一个待处理前缀，状态机更小，共享边界也更窄。

**反馈前保留捕获时已脱敏记录。** 已否决，因为权威日志已拥有这些事件，该方案仍会复制无上限的会话前缀。它能保留捕获时的脱敏策略与运维记录，但对于一个定义为「反馈后上传会话日志」的模式，这些性质不足以证明该内存成本合理。

**在反馈回放期间临时允许公开 `emit()` 调用。** 已否决，因为在标志开启期间，脱敏监听器或另一个可重入调用方可能将无关记录入队。私有后端能力使授权成为结构性保证，并确保公共服务在整个回放过程中保持关闭。

**以不挂载插件表示禁用状态。** 这仍然是静默退出方式，但无法在记录反馈时输出警告。显式禁用模式让部署方可以保持同一种配置形态，并说明本地反馈未离开进程。

## 后果

`FULL` 作为显式启用模式保留原有的源码与协议行为。`FEEDBACK_ONLY` 在反馈前不增加遥测自有的逐事件缓冲；直接服务调用与非权威反馈事件均不上传任何内容，且反馈前发生崩溃时，该前缀也不上传任何内容。回放使用记录反馈时挂载的脱敏策略，并排除权威日志中不存在的运维记录。因此，仅反馈的流既不携带 `agent-error` 记录，也不携带 `shutdown` 记录，而缺少 shutdown 不是崩溃信号。每个后续反馈都会捕获从上一个边界起累积的后缀。`DISABLED` 可省略 `exporter.url`，不执行任何上报工作，并仅在权威会话日志中保留反馈。
