Telemetry 不能反过来定义会话
你以为Telemetry采集了用户反馈、评分、使用统计,它就是一个"完整的会话记录"。错。Telemetry只是观测侧车,它永远不创建或恢复Agent/Session。
用户反馈系统里常见的字段很完整:sessionId、messageId、rating、note、timestamp。它们足够做质量分析,也可以服务评估和回归,但不能拿来替代主会话日志。
在 Harness 里,Telemetry/Feedback 是观测侧车。它坐在主驾驶旁边记录路况,但它不握方向盘,不踩刹车,更不能在主驾驶员失忆后告诉他“你刚才开去哪里了”。Feedback 数据永远不能用来创建或恢复 Agent/Session。
侧车,不是主引擎
MessageFeedback存储per-message rating + note,但它的API里没有createSession、resumeSession、restoreMessage这些方法。它的写入前提是:目标message已经存在于durable storage中。
写入前的ensureTargetDurable()是硬门槛。你不能给一个不存在的message打分。对于live session,它先flush确保事件已落盘;对于cold session,它从持久化介质重读确认message存在。如果目标不存在,写入直接失败——feedback表不会凭空创建出一个message来匹配你的评分。
这是一个单向依赖:Session Log是事实源,Telemetry是附属观测。箭头永远是Session → Telemetry,反过来不行。
repo="${DSH_SOURCE_DIR:?set DSH_SOURCE_DIR to the official fixed checkout}"
grep -n "ensureTargetDurable\|createSession\|resumeSession" "$repo/packages/feedback/message-feedback/src/index.ts" | head -20
你会看到ensureTargetDurable在每次写入路径上,而createSession根本不存在。
sameIdentity():session id不可靠
你可能以为识别”同一个会话生命周期”用sessionId就行。错。Session id可以被复用——用户resume一个旧会话,id不变,但中间可能隔了几个月;或者fork出子会话,id是新的但有血缘关系。
sameIdentity()用createdAt + cwd两个维度识别。createdAt是会话创建时间戳,cwd是工作目录路径。这两个组合起来,才能确定”这是同一个持久化的连续生命周期”,而不是id碰撞或复用。
版本化乐观并发用ifVersion UUID防写入冲突。每次读取feedback时拿到一个version UUID,写入时必须带上这个version。如果期间别人改了,version不匹配,写入失败——你得重读再试。这防止了两个客户端同时给同一条message打分导致的last-write-wins。
flowchart TD
A[用户打分/写note] --> B[ensureTargetDurable]
B --> C{message在durable storage?}
C -->|否| D[拒绝写入]
C -->|是| E[读取当前ifVersion]
E --> F[尝试写入: ifVersion匹配?]
F -->|不匹配| G[版本冲突, 重读重试]
F -->|匹配| H[写入成功, 更新version]
I[Session Log] -->|是事实源| J[Durable Storage]
J -->|可观测| K[Telemetry/Feedback]
K -.->|永远不能反向创建| I
style D fill:#8b0000,stroke:#fff,color:#fff
style G fill:#cc5500,stroke:#fff,color:#fff
style K fill:#555,stroke:#999,color:#fff
Telemetry能证明什么,不能证明什么
划清边界很重要。你要清楚Telemetry数据能回答什么问题,不能回答什么问题。
Telemetry能证明:
- 用户给某条message打了1-5分的主观评分(rating是显式用户动作)
- 用户写了什么反馈note(note是明文输入)
- 使用统计数据(DAU、功能使用频率、平均会话长度等)
Telemetry不能证明:
- 消息曾被用户阅读。评分是显式动作——用户打了分说明他看到了,但没打分不代表他没看到(可能只是懒得点)
- 模型输出的正确性。评分是用户主观判断,不是客观真理。用户觉得”好”的回答可能事实错误,用户觉得”差”的回答可能技术正确但不符合他的审美
- 完整trajectory。Telemetry只索引finalized的assistant/user message的messageId,不存stream chunks,不存中间tool call过程,不存compaction替换链。你没法从feedback表重建出当时的完整对话流
还有一个容易被忽略的:note是明文存储,没有端到端加密。不要在note里写密码、API key、敏感个人信息——系统不会替你加密它。
repo="${DSH_SOURCE_DIR:?set DSH_SOURCE_DIR to the official fixed checkout}"
grep -n "encrypt\|cipher\|E2E\|plaintext" "$repo/packages/feedback/message-feedback/src/index.ts" | head -10
你不会找到任何加密逻辑——这是设计如此,不是遗漏。Telemetry明确告诉你:我不保证note的机密性,你想写敏感内容请换地方。
容易踩的坑
坑一:用feedback数据恢复会话。 这是最危险的幻觉。Feedback只存messageId引用和rating/note,不存message内容本身。messageId是外键,主表丢了,外键就是无意义的字符串。Session Log才是唯一的恢复源。
坑二:相信session id识别身份。 Session id是可复用的标识符,不是唯一生命周期标识。sameIdentity()要用createdAt + cwd,不能只看id。
坑三:把没打分当作”没看到”。 不打分可能只是用户懒得动鼠标,不代表他没阅读那条消息。点击率/评分率的分母要想清楚。
坑四:在note里写敏感信息。 Note是明文存储在本地的,没有E2E加密。这是给用户写”这里应该用另一种方法”之类的技术反馈,不是写密码管理器。
坑五:以为乐观并发是”可选的优化”。 ifVersion不是性能优化,是正确性保证。没有它,两个并发的feedback写入会静默互相覆盖。
Telemetry 的边界划得很清楚:只观测,不定义。要追溯历史、重放验证、搜索过往会话,应该走 Trajectory、Query 和 Replay 那条链路。