DeepSeek Harness 匿名用户标识设计解析:`$DSH_HOME/.anonymous-user-id` 与 OTel Resource `user.id`
DeepSeek Harness 匿名用户标识设计解析$DSH_HOME/.anonymous-user-id与 OTel Resourceuser.id【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness本文以 DeepSeek Harness 仓库中的架构决策记录Agent Note为主体结合其落盘实现与三大消费方源码完整讲解会话遥测Session Telemetry中匿名用户标识的设计权衡、存储契约与并发语义。读完你可以掌握匿名 UUID 如何被生成与持久化、为何随机而非派生是匿名性的根基、user.id为何挂在 OTel Resource 而非每条记录上以及删除文件、只读 home、多进程首启等边界情况下的行为。该标识的最终归属包是deepseek-ai/dsh-anonymous-user-id被会话遥测 OTel 后端、/feedback命令与 DeepSeek 直连请求三方共享。背景遥测已就位但缺少用户这一维度会话遥测默认被挂载进 dsh web 组合见 default-mount Note然而当时的 OTel Resource 只携带service.name/service.version完全没有用户级身份。这意味着收集端collector既无法按用户聚合记录也无法统计活跃用户数。此前唯一相关的裁决是用 hostname/本地 IP 哈希派生用户 ID的未实现提案。OTel 数据流迫切需要一种语义干净的匿名用户身份。决策总览一个 UUID、一个文件、三方共享核心决策一句话概括getOrCreateAnonymousUserId()返回$DSH_HOME/.anonymous-user-id文件中的裸 UUID 行home 由resolveDshHome解析优先级$DSH_HOME~/.dsh首次调用时铸造并持久化一个随机 UUID v4后端构造函数将其作为 Resource 的user.idOTel semconv 标准用户属性携带每个导出批次batch携带一次。该实现最初内嵌在session-telemetry-otel中因为当时不存在第二个真实消费方。/feedback后来成为第二个消费方于是 共享 ID 决策 将所有权迁移至独立包deepseek-ai/dsh-anonymous-user-id存储、匿名性、并发与丢失语义均保持不变DeepSeek 直连请求身份 则是同一 ID 的第三个消费方。设计裁决表裁决项取值理由ID 来源随机 UUID v4绝不从 hostname、网络地址或 git remote 派生派生 ID 可逆会让匿名沦为虚构存储形式.anonymous-user-id裸 UUID 行 换行无 JSON 包装身份是独立事实不应归档在某个遥测数据流的文件名/格式之下IO 形式同步 IO 以解析后文件路径为键的进程生命周期 memoOpenTelemetrySessionBackend构造函数是同步的异步会重塑插件加载流程每个进程只碰一次磁盘运行中删除文件不影响当前进程并发首启用排他创建wx写入决出胜负失败者重读胜利者的 ID覆盖常见并发场景若重读恰好落在胜者创建→写入的微秒窗口内本次运行仍可能每进程各持一个 ID下次启动收敛到持久化值——遥测级后果可接受丢失语义文件被删 → 下次启动铸造全新 ID丢失被接受匿名身份没有恢复价值可恢复性需要派生材料这与匿名性冲突写入失败尽力而为返回内存中的 IDSessionTelemetryBackend绝不被只读 home 阻塞上报位置Resource 属性而非每条记录属性每个批次一次足以支撑 Resource 维度聚合逐条注入会触碰 seam 契约并增大线上体积semconv 依赖不引入opentelemetry/semantic-conventions一个字符串常量不值得引入一个依赖归属包deepseek-ai/dsh-anonymous-user-id由 OTel 后端、/feedback、DeepSeek 直连请求共享消费者共享同一存储契约而不依赖某个导出器后端独立开关无任何消费方都可创建该身份DSH_TELEMETRY_DISABLED只停止遥测上报不关闭 feedback 确认或 DeepSeek 请求头源码级拆解getOrCreateAnonymousUserId()的实现机制完整实现位于 packages/identity/anonymous-user-id/src/index.ts包内只暴露一个主函数与一个文件名字符串常量export type AnonymousUserId BrandedAnonymousUserId /** File inside the harness home storing the id: a bare UUID line, no wrapper format. */ export const ANONYMOUS_USER_ID_FILE_NAME .anonymous-user-id const UUID_PATTERN /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i export function getOrCreateAnonymousUserId(options: AnonymousUserIdOptions {}): AnonymousUserId { const file join(resolveDshHome(undefined, options.env ?? process.env), ANONYMOUS_USER_ID_FILE_NAME) const cached memo.get(file) if (cached ! undefined) return cached let id readPersistedId(file) if (id undefined) { const generate options.randomUUID ?? randomUUID const created generate() as AnonymousUserId try { mkdirSync(dirname(file), { recursive: true }) writeFileSync(file, ${created}\n, { encoding: utf8, flag: wx }) id created } catch { // wx 拒绝EEXIST同时覆盖并发胜者与既有损坏文件两种情况…… id readPersistedId(file) if (id undefined) { try { writeFileSync(file, ${created}\n, utf8) } catch { // 尽力而为持久化home 不可写时也在本次运行中返回一致 ID } id created } } } memo.set(file, id) return id }完整代码见 src/index.ts。四个关键机制随机铸造ID 来自node:crypto的randomUUID()src/index.ts即标准 UUID v4。源码注释明确它绝不从 hostname、网络地址、git remote 或任何其他可识别来源派生——匿名是铸造行为本身的属性而非事后修饰。逐路径 memomemo是一个以解析后文件路径为键的Mapsrc/index.ts所以不同的测试 home / 不同的$DSH_HOME永远不会共享 ID且同一进程只触碰一次磁盘。读取校验readPersistedId读取文件后trim()并与 UUID 正则匹配src/index.ts不合法或不可读返回undefined由调用方走铸造并持久化路径。wx排他创建 失败回退首写用writeFileSync(file, ..., { flag: wx })。若抛错EEXIST等先重读文件以采纳并发胜者写入的合法 ID若重读仍无效损坏文件则回退到普通覆盖写覆盖写再失败只读 home则直接返回内存中新 ID——telemetry 与 feedback 永远不会被不可写的 home 阻塞。可测试性设计AnonymousUserIdOptions提供两个默认值钩子src/index.tsenv默认为process.env用于注入DSH_HOME与randomUUID默认为crypto.randomUUID用于模拟并发。测试套件 tests/anonymous-user-id.spec.ts 用这两个钩子覆盖了全部边界首次使用创建、持久化并返回裸 UUID 行含末尾换行home 目录缺失时自动递归创建后续调用返回持久化 ID容忍首尾空白损坏文件被新 ID 覆盖并发胜者采纳在初始读与wx写之间植入胜者文件返回的是胜者的 IDhome 无法容纳文件时返回可用 ID 且不持久化最佳努力进程生命周期 memo删除文件后仍返回同一 ID不同 home 持不同 ID默认读取process.env。存储契约一个裸 UUID 行.anonymous-user-id的存储契约被刻意保持极简裸 UUID 行 换行无 JSON 包装、无版本标记。裁决理由写得很清楚——身份是独立事实不是归档在某条遥测数据流的文件名/格式之下。读取时对整行trim()后做 UUID 校验首个写入者使用排他创建wx并发失败者重读并采纳胜者值损坏或不可读文件落入铸造并覆盖路径。memo 以解析后路径为键保证不同 home 不共享 ID。一个值得注意的开放问题记录在包 README 的 Dev Note由于契约是无版本标记的裸 UUID 行未来若要在 ID 旁增加第二个值或将行包装进容器对存量文件没有迁移方案——带版本的行格式是让这类变更变安全的一种方式。三大消费方同一 ID 贯穿遥测、反馈与请求1. OTel 会话遥测后端Resourceuser.id在 packages/session/session-telemetry-otel/src/index.ts 中后端构造LoggerProvider时通过resourceFromAttributes组装 Resourcethis.provider new LoggerProvider({ resource: resourceFromAttributes({ service.name: APP_IDENTITY.product, service.version: APP_IDENTITY.version, // OTel semconv 标准用户属性每导出批次在 Resource 上携带一次而非逐条记录 user.id: getOrCreateAnonymousUserId(), }), ... })正如裁决表上报位置一栏所述collector 按 Resource 聚合ID 在进程内本来就是稳定的每批次一次足矣逐条注入不仅多余还会触碰 session-telemetry seam 契约并增大线上传输体积。同时该包没有引入opentelemetry/semantic-conventions依赖——一个user.id字符串常量不值得增加一个依赖。调用发生在后端构造函数内这与同步 IO memo的裁决直接呼应构造函数同步插件加载流程不被重塑。2./feedback命令确认消息中的匿名用户行在 packages/feedback/command-feedback/src/index.ts 中feedback 成功确认消息形如Feedback recorded for session {sessionId} Anonymous user: {userId}. {sharingDisclosure(telemetry)}这样运维方可以把确认消息与导出的遥测记录关联起来。设计上feedback 包只依赖身份能力本身而非遥测 seam 或 OTel SDK——避免了直接命令依赖可选导出器后端以及遥测导出 feedback 时形成反向依赖环的问题详见 共享 ID 决策 的备选方案分析。3. DeepSeek 直连请求x-deepseek-harness-user-id请求头在 packages/llm/llm-deepseek/src/adapter.ts 中请求头集合包含const headers { authorization: Bearer ${apiKey}, content-type: application/json, accept: text/event-stream, ...attributionHeaders(), x-deepseek-harness-user-id: String(userId), ...options.sessionId ! undefined ? { x-deepseek-harness-session-id: String(options.sessionId) } : {}, ...options.purpose compaction ? { x-deepseek-harness-compact: 1 } : {}, }这样用量可以按安装实例harness home归属。注意共享 ID 决策中的两个时序细节无效 feedback 在解析 ID 之前就被拒绝DeepSeek 适配器在凭据验证成功后才解析 ID——因此一次空命令或一次凭据失败都不会创建.anonymous-user-id文件详见 共享 ID 决策。消费方聚合图┌──────────────────────────────────────┐ │ $DSH_HOME/.anonymous-user-id (UUID) │ └──────────────────┬───────────────────┘ getOrCreateAnonymousUserId() ┌───────────────────┬───────┴──────────┬──────────────────┐ ▼ ▼ ▼ ▼ OTel Resource /feedback 确认消息 x-deepseek-harness- 未来新消费方 user.id Anonymous user: … user-id 请求头 直接导入复用备选方案回顾为何这些路被否决被否决方案一句话理由Hostname/IP 哈希派生 ID此前的旧裁决可逆就意味着不匿名随机 UUID 语义干净——用户裁决予以取代在每条记录属性上带user.idClaude Code 的形状触碰 session-telemetry seam 契约或逐条注入、增大线上体积每批次放 Resource 上已能完成聚合在/feedback需要 ID 之前就抽出共享包初版做法当时唯一真实消费方是 OTel 后端只有直连 feedback 需要同一关联 ID 时才证明抽包是合理的AppCLIEntry 读取 ID 并通过配置 patch 注入每个表层入口都要接线把运行时事实塞进部署配置混淆了两类关注点将能力放进deepseek-ai/dsh-home-pathspaths 是纯路径计算、零 IO把持久化身份能力塞进去会污染包边界其中先在 OTel 后端内实现、待出现第二个消费方再抽包的演进路径尤其值得注意它体现了共享库只有在确有两个以上真实消费者时才值得抽离的工程原则。/feedback成为第二个消费方后共享 ID 决策将所有权迁移到独立包并完整保留了既有的随机 UUID、home 解析、进程 memo、排他创建并发、损坏替换与最佳努力写语义——不改变本 Note 记录的任何存储、匿名性、并发或丢失语义。后果与运维视角一个$DSH_HOME即 OTel 数据流中的一个稳定用户不同 home 天然是不同用户且不存在跨 home 关联机制。OTel 数据流、/feedback与 DeepSeek 直连请求共享同一个.anonymous-user-id。删除.anonymous-user-id即重置身份下次启动生效home 不可写时每个进程在 home 变可写之前持有自己的内存 ID。就匿名用户 ID 部分而言default-mount Note 中身份跟进事项已由此决策闭环hostname/表面维度、脱敏规则与用量指标跟踪仍保持开放。实际操作速查观察 ID查看$DSH_HOME/.anonymous-user-id默认~/.dsh/.anonymous-user-id文件内容即裸 UUID 行。重置 ID删除该文件下次启动铸造新 ID运行中的进程因 memo 机制保持当前 ID 直到退出。多个 home为不同部署设置不同DSH_HOME即可获得相互独立、不可关联的匿名身份。在自己的包中使用来自包 README 的官方示例README.mdimport { getOrCreateAnonymousUserId } from deepseek-ai/dsh-anonymous-user-id const userId getOrCreateAnonymousUserId() // 进程生命周期内稳定遥测总开关DSH_TELEMETRY_DISABLED非空即禁用遥测上报行含0/false但它不会禁用 feedback 确认或 DeepSeek 请求头中的匿名 ID。已知限制删除后不可恢复文件丢失即铸造新匿名身份这是有意设计——恢复需要稳定的派生材料而派生材料会削弱匿名性。并发为最佳努力落在并发进程排他创建与写入完成之间狭窄窗口的读取者本次运行可能使用不同的内存 UUID后续启动会收敛到持久化值。无跨 home 身份不同$DSH_HOME之间无法关联。配置的 DeepSeek 网关会收到该 IDdsh-llm-deepseek向其解析出的baseURL含部署覆盖发送稳定请求头与遥测共享模式无关。删除文件不会重置当前进程memo 让本次运行的 ID 保持到下次启动。延伸阅读决策原始记录2026-07-31-telemetry-anonymous-user-id.md及中文版遥测默认挂载决策2026-07-31-web-telemetry-default-mount.md共享 ID 抽包决策2026-08-07-shared-feedback-telemetry-user-id.md包级说明identity/anonymous-user-id/README.md身份组地图identity/README.mdhome 路径解析util/home-paths/README.md拥有$DSH_HOME与~/.dsh的解析语义消费方源码session-telemetry-otel/src/index.ts、command-feedback/src/index.ts、llm-deepseek/src/adapter.ts【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

MXNet Gluon Model Zoo Vision 模型库完全指南:从 `get_model` 到 ResNet/VGG/MobileNet 的预训练模型使用

MXNet Gluon Model Zoo Vision 模型库完全指南:从 `get_model` 到 ResNet/VGG/MobileNet 的预训练模型使用

深度学习机器学习人工智能 【免费下载链接】mxnet Lightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more 项目地址: https://gitcode.c…

2026/9/22 0:07:32 阅读更多 →
Handsontable 单元格渲染器(Cell Renderer)实战指南:从内置别名到自定义函数、注册与框架组件渲染器

Handsontable 单元格渲染器(Cell Renderer)实战指南:从内置别名到自定义函数、注册与框架组件渲染器

Handsontable 单元格渲染器(Cell Renderer)实战指南:从内置别名到自定义函数、注册与框架组件渲染器 【免费下载链接】handsontable JavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and…

2026/9/20 20:09:48 阅读更多 →
Codex AI编程代理快速入门:从安装到第一条指令的完整指南

Codex AI编程代理快速入门:从安装到第一条指令的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:09:48 阅读更多 →

最新新闻

压印底层图解原理:3步读懂Java对象内存与GC机制

压印底层图解原理:3步读懂Java对象内存与GC机制

压印底层图解原理:3步读懂Java对象内存与GC机制 盯着满屏红色的 StackTrace 报错,你是不是只想砸键盘?别慌,这通常是 JVM 内存模型里的“压印”机制在作怪。很多初学者看到 OutOfMemoryError 或…

2026/9/22 0:47:13 阅读更多 →
苏宁业绩图解原理:3个代码实战破解源码阅读难题

苏宁业绩图解原理:3个代码实战破解源码阅读难题

苏宁业绩图解原理:3个代码实战破解源码阅读难题 看了一堆教程还是不会写项目?这痛点我太懂了。别急,今天咱们不整虚的,直接上苏宁业绩图解原理。很多应届生朋友问我,为什么看源码像看天书?因为没人给你拆解底层逻辑。…

2026/9/22 0:47:13 阅读更多 →
3天搞定穆斯林的葬礼读后感保姆级教程避坑指南

3天搞定穆斯林的葬礼读后感保姆级教程避坑指南

3天搞定穆斯林的葬礼读后感保姆级教程避坑指南 官方文档太长抓不住重点?别慌,这本《穆斯林的葬礼》的读后感其实有套固定的底层逻辑。很多转岗或者跨行写书评的朋友,一上来就陷入“剧情复述”的泥潭,越写越偏,最后像流水账。…

2026/9/22 0:47:13 阅读更多 →
搞定ADCM4高频面试题,源码拆解助你通关

搞定ADCM4高频面试题,源码拆解助你通关

搞定ADCM4高频面试题,源码拆解助你通关 看了一堆教程还是不会写项目?别慌,很多兄弟都卡在这。其实你缺的不是知识,而是把散落知识点串成逻辑的能力。最近ADCM4成了高频面试题,但大多数回答都停留在背概念,面试官根本听不进去。…

2026/9/22 0:47:13 阅读更多 →
2026最新十一月性能优化:3个技巧搞定StackTrace报错

2026最新十一月性能优化:3个技巧搞定StackTrace报错

2026最新十一月性能优化:3个技巧搞定StackTrace报错 凌晨两点,线上告警炸了。你盯着控制台,满屏红色的 Stack Trace 像天书一样堆叠。 NullPointerException…

2026/9/22 0:47:13 阅读更多 →
5个新手避坑技巧,看说实战让公路项目代码跑通

5个新手避坑技巧,看说实战让公路项目代码跑通

5个新手避坑技巧,看说实战让公路项目代码跑通 学会语法却不知怎么搭项目,这是很多刚入行公路工程信息化开发的兄弟最头疼的事。你背下了 Python 的 if-else ,记住了 Java…

2026/9/22 0:46:12 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →