claude-mem PostHog 可观测性改造实战:在 Stop Hook 中采集被观测会话的模型、来源与计费类型 claude-mem PostHog 可观测性改造实战在 Stop Hook 中采集被观测会话的模型、来源与计费类型【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem导读claude-mem 的遥测管线此前只向 PostHog 上报observer观察者模型——即 claude-mem 自身用于压缩生成记忆所用的模型而用户 IDE 会话里真实在跑的那个模型却从未进入遥测。本篇基于仓库中的设计文档 plans/2026-09-01-posthog-observed-model.md 与其在src/、tests/中的落地实现完整剖析一条“从 Stop Hook 读 transcript → Hook 进程内探测计费 → Worker 落库 → 遥测聚合上报 PostHog”的新增遥测链路。读完你将掌握如何跨平台从 transcript JSONL 中提取最后一条 assistant 回合的模型 id、如何在 Hook 进程内安全推断 Claude 订阅/API/云厂商计费档位、以及如何让新字段穿透 Worker 存储与遥测脱敏白名单抵达observer_turn_rollup。为什么需要区分“observer 模型”与“observed 模型”claude-mem 的架构存在两个独立的“模型”observer 模型observer modelclaude-mem 自身用来把会话压缩成记忆摘要的模型遥测中记录为model/top_model/providerobserved 模型observed model用户 IDE 会话Claude Code、Cursor、Codex 等里真实运行、产生该会话上下文的模型。改造前的遥测只携带 observer 侧信息导致一个核心盲区“用户的 IDE 会话跑的是什么模型”这个变量与记忆质量、成本、使用频率的相关性完全不可见。设计文档明确要求新增observed_model、observed_billing两个独立属性并修复ide在逐轮记录中被丢弃、无法进入 rollup 聚合的问题同时不得改动model/top_model/provider任何既有语义——三条信息分别回答“记忆是谁写的”“会话是谁在跑”“这份会话按什么计费”。Phase 0 情报决策三条数据的来源与取舍设计文档以表格形式完成了文档勘察Documentation Discovery结论值得原文保留因为它们决定了整个实现的形状。从 transcript 而非 SessionStart hook 读 observed 模型候选来源事实结论Claude Code hook stdinmodel仅在SessionStart出现UserPromptSubmit/PostToolUse/Stop 均缺失若要传输需新建 transport且不刷新Transcript JSONL每条 assistant 条目都带message.model真实 transcript 验证为claude-fable-5-1行角色标记line.typeClaude Code或line.roleCursor采用平台通用、无需新 hook 接线、每回合刷新天然覆盖会话中途/model切换Hook 输入类型NormalizedHookInput已声明model?等字段但仅 codex adapter 填充不依赖因此实现定在Stop hook 的summarizehandler读取 transcript——PostToolUse 每工具调用触发一次、不适合做全量扫描Stop 每回合一次成本可控。从 Hook 进程环境与.claude.json探测计费档位计费探测只在claude-code平台执行因为.claude.json是 Claude Code 专有文件。可用的信号源订阅账号~/.claude.json的oauthAccount.organizationType如claude_max、billingType、organizationRateLimitTierAPI Key 计费Hook 进程环境变量ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENHook 继承 Claude Code 环境worker 守护进程没有用户 shell 环境这是计费探测必须放在 Hook 进程的根本原因云厂商路由CLAUDE_CODE_USE_BEDROCK/CLAUDE_CODE_USE_VERTEX/CLAUDE_CODE_USE_FOUNDRY长期 OAuth tokenCLAUDE_CODE_OAUTH_TOKEN配置文件路径设置了CLAUDE_CONFIG_DIR时为$CLAUDE_CONFIG_DIR/.claude.json否则为~/.claude.json。探测优先级closed enum、低基数命中即返回1. CLAUDE_CODE_USE_BEDROCK 非空且非 0/false → bedrock CLAUDE_CODE_USE_VERTEX → vertex CLAUDE_CODE_USE_FOUNDRY → foundry 2. key ANTHROPIC_API_KEY || ANTHROPIC_AUTH_TOKEN非空 且 (无 oauthAccount || customApiKeyResponses.approved 含 key 尾 20 字符) → api_key 3. oauthAccount.organizationType 为字符串 → 去掉 claude_ 前缀 (如 max/pro/team/enterprise)非法值一律归入 subscription 4. 存在 oauthAccount 或 CLAUDE_CODE_OAUTH_TOKEN → subscription 5. 否则 → unknown最终落地实现于 src/shared/observed-billing.tsObservedBilling类型被收敛为 10 个值的闭集max | pro | team | enterprise | subscription | api_key | bedrock | vertex | foundry | unknown。值得注意两个工程细节organizationType去除claude_前缀后若不在KNOWN_SUBSCRIPTION_TIERS中会坍缩为subscription保证意外取值永远不会撑大遥测基数见 observed-billing.ts.claude.json解析后立刻通过projectBillingFields投影到只含oauthAccount.organizationType与customApiKeyResponses.approved的窄类型上observed-billing.tstoken、email、account id 在 JSON.parse 之后的一行内即被丢弃下游代码从物理上无法读取或记录它们。遥测管线的“铁律”白名单在 scrubber设计文档强调一个容易被忽略的事实属性必须进入ALLOWED_PROPERTY_KEYS白名单否则 scrubber 会静默丢弃。这条铁律决定了observed_model/observed_billing从落点emit 处就必须同时登记白名单见 scrub.ts才能活着到达 PostHog。Phase 1 Hook 侧提取模型并上报计费1.1 transcript 反向扫描提取模型 id实现新增了两个导出函数见 src/shared/transcript-parser.tsextractLastAssistantModel(transcriptPath)入口校验路径存在、读取文件、判空后交给内部函数extractLastAssistantModelFromJsonl(content)借助既有parseJsonlLinesBackward生成器从最后一行向前扫描跳过空行与解析失败行命中角色为 assistantline.type ?? line.role且message.model为非空字符串的行即返回。反向扫描天然保证返回的是最近一次 assistant 回合的模型——正是会话中途/model切换后应保留的值。同时仓库在落地时将文件读取合并优化extractLastAssistantTurn一次读盘同时返回「最后一条 assistant 文本 该回合模型」transcript-parser.ts避免长 transcript 为两个目的读两次磁盘。1.2 Hook 进程内的计费探测detectObservedBilling(env?, claudeJsonFile?)的两个参数均可注入——这是单测能用假env对象 临时文件跑全矩阵的关键设计见 observed-billing.ts。真实调用时它读取的是 Hook 进程环境与claudeJsonPath(env)解析出的配置路径。isTruthyEnv要求值非空且不是0/false大小写不敏感。关于健壮性的取舍设计文档 §0.4 允许的 API 极其克制readFileSync/existsSync/join/homedir且明文要求只在既有逐行JSON.parse容忍处添加 try/catch让错误浮出水面。唯一例外是.claude.json的读取——落地代码确实为它包了 try/catchobserved-billing.ts文件损坏会退化为“无账号”与文件缺失同路径继续探测避免用户端一个损坏配置文件导致整个 summarization 流程中止且刻意不记录异常文本——JSON.parse的 SyntaxError 会引用触发错误的原文而该文件含 OAuth token因此只记录error.name。1.3 summarize handler 汇合两条新字段在 src/cli/handlers/summarize.ts 中两个取值逻辑分别落在 Stop 流程的关键节点observedModel优先复用 hook 输入里已带的lastAssistantMessage此时从 transcript 提取 model否则走extractLastAssistantTurn一次读取拿到文本与模型summarize.tsobservedBilling仅在input.platform claude-code时调用detectObservedBilling()summarize.ts其它平台保持undefinedscrubber 会丢弃。随后二者与contentSessionId、last_assistant_message、platformSource一起 POST 到/api/sessions/summarizesummarize.ts。文档特别交代server-runtime 路径summarizeViaServer保持不动只有 worker 路径走新字段。设计文档还列了 Phase 1 的验证清单可作为可复现的自测步骤npm run typecheck bun test tests/transcripts tests/shared tests/cli npm run lint:hook-io grep -n observedModel\|observedBilling src/cli/handlers/summarize.ts # 二者都出现在请求体 grep -rn accessToken\|refreshToken src/shared/observed-billing.ts # 必须为空Phase 2 Worker 侧持久化到会话并回填内存态2.1 SQLite 迁移版本戳 50设计文档在勘察时确认了两条关键事实SQLite 迁移模式是SessionStore上的幂等私有方法PRAGMA table_info检查 →ALTER TABLE … ADD COLUMN→INSERT OR IGNORE INTO schema_versions并追加进构造函数链原文给出可照抄模板addObservationModelColumns既有最高版本戳为 49normalizeConceptTags且构造函数链并非按戳有序34 已用作ensureUserPromptsSessionDbId因此新迁移使用版本 50。落地后的实现正是ensureSDKSessionsObservedColumns()为sdk_sessions表增加observed_model TEXT与observed_billing TEXT两列追加在构造函数链末尾SessionStore.ts、SessionStore.tsSdkSessionDetailRow行类型补上两个可空字段SessionStore.tsgetSessionById的SELECT同时带上两列SessionStore.ts。2.2 写入方法COALESCE 保住首写值setSessionObservedMetadata(sessionDbId, observedModel?, observedBilling?): void { this.db.prepare( UPDATE sdk_sessions SET observed_model COALESCE(?, observed_model), observed_billing COALESCE(?, observed_billing) WHERE id ? ).run(observedModel ?? null, observedBilling ?? null, sessionDbId); }实际实现见 SessionStore.ts。COALESCE(?, observed_model)的语义是后到的undefined不会覆盖已存值——首个已知值成为该会话的基准这与「后续回合变更时以最新值为准」的 last-seen 语义互补存储侧保守、上报侧最新。2.3 从库到内存态的回填ActiveSession接口在lastModelId?旁新增observedModel?/observedBilling?doc-comment 明确标注“observed IDE 会话在跑的模型来自其 transcript——遥测observed_model不是observer 模型”src/services/worker-types.tsSessionManager.initializeSession新建会话对象时用dbSession.observed_model ?? undefined填充SessionManager.ts命中缓存会话分支时若库值存在且与内存不同则覆盖SessionManager.ts保证重启/并发下内存态与库一致。2.4 summarize 路由收口summarizeByClaudeIdSchema使用既有.passthrough()风格新增observedModel: z.string().min(1).max(200).optional()与observedBilling: z.string().min(1).max(40).optional()SessionRoutes.ts长度上限与闭集设计呼应从入口拒绝超长或恶意载荷。handleSummarizeByClaudeId在createSDKSession之后、queueSummarize之前落库并回填内存态SessionRoutes.ts且位于 subagent early-returnStop hook 对子代理跳过之后。Phase 3 遥测侧逐轮携带、rollup 聚合、白名单与文档3.1 逐轮记录补齐三处落点每条session_compressed记录都携带observed_model: session.observedModel与observed_billing: session.observedBilling。文档列出三条产生路径落地已覆盖成功路径ResponseProcessor的compressionPropsResponseProcessor.ts错误路径SessionRoutes.ts错误分支SessionRoutes.ts中止路径SessionRoutes.ts中止分支SessionRoutes.ts。ClaudeProvider的...pending重发因 spread 既有 props 而无须改动源码亦印证ClaudeProvider.ts 直接读session.observedModel。未知值省略不上报——scrubber 丢弃undefined未知语义由 rollup 统一填unknown。3.2 rollup 的 last-seen 语义逐轮记录永不直接发送而是由computeSessionCompressedRollup聚合成observer_turn_rollupsrc/services/telemetry/buffer.ts。设计文档指出此处同时修复一个文档承诺长期失效的 bugdocs/public/telemetry.mdx曾声称 rollup 携带provider/ide/hook实际只有数字聚合与top_model存活。落地后的聚合逻辑为每个会话维护四个 last-seen 字符串buffer.tsif (lastIde) rollup.ide lastIde; if (lastProvider) rollup.provider lastProvider; rollup.observed_model lastObservedModel ?? unknown; rollup.observed_billing lastObservedBilling ?? unknown;实现中的语义注释解释了每个字段为何用 last-seenide在会话内恒定provider可能在 provider 回退或配额冷却时中途变化last-seen 上报的是结束会话的那个 providerobserved_model/observed_billing只在/model切换时变化取最新即所求hook逐轮变化init/ingest/summarize单个值会误导故刻意不携带见 buffer.ts 的注释。一个值得注意的不对称buffer.tside/provider沿用top_model的“有值才出现”规则而observed_*是恒出现属性缺失一律填unknown——这样 PostHog 侧可以用显式的unknown过滤而不是依赖属性缺失这种不可靠信号。3.3 scrubber 白名单登记observed_model与observed_billing需加入 src/services/telemetry/scrub.ts 的ALLOWED_PROPERTY_KEYS。文档强调它们与ide/provider/model/top_model同属“已批准”类别落库前任何不在白名单的自由形式字段都会被静默删除——这是遥测管线防注入的最后一道闸门。3.4 隐私边界的明文承诺只收集 observed 会话的模型 id与闭集计费档位永不收集账号 id、邮箱、tokendocs/public/telemetry.mdx事件表中对两个新属性有明确定义observed_model为“observed IDE 会话本身在跑的模型 id读自其 transcript——区别于作为压缩模型的model无法确定时记unknown”observed_billing为闭集枚举telemetry.mdx修复后的 rollup 行描述将字段更正为 “(provider,ide)”并把observed_model、observed_billing加入携带清单telemetry.mdxusage_limit_hit事件因 observer 与 observed 共享同一账号也新增携带ide、provider、observed_model、observed_billingtelemetry.mdx。设计纪律与反模式红线设计文档 §0.5 与代码注释共同固化了一组值得独立成条的工程约束既是实现准则也是代码评审清单遥测 props 绝不装自由文本不传路径、不传 promptobserved_model是模型 idobserved_billing是闭集枚举不读取/记录任何类 token 字段.claude.json除oauthAccount.organizationType、oauthAccount存在性、customApiKeyResponses.approved外一律不碰不复用 observer 侧字段observations.generated_by_model、session.lastModelId、model、top_model语义保持 observer-side不在 PostToolUse handler 读 transcript每工具调用都会触发只在每回合一次的 Stop 路径扫描不新建 settings 键遥测同意只由 env /telemetry.json控制src/services/telemetry/consent.ts不动plugin/scripts/*.cjs构建产物在版本升级时重建不属于功能 PR文档引git log -- plugin/scripts/worker-service.cjs为证mock 同步契约凡mock.module(../../../src/shared/transcript-parser.js, …)的测试如 summarize-tag-stripping.test.ts必须同步导出新函数否则 handler 导入在调用期为undefined——新增导出会隐性破坏所有 mock 该模块的测试。测试覆盖与最终验收设计文档为每个阶段规划了对应测试实现已按此落地transcript 提取extractLastAssistantModelFromJsonl覆盖 Claude Codetype形式返回模型、Cursorrole形式携带message.model、跳过 user 行与畸形行、无 assistant 行时返回undefined见 observed-model-extraction.test.ts计费探测全矩阵用注入env 临时.claude.json覆盖 bedrock/vertex/foundry、无账号下的 API key、未获批 API key 回退账号档位、organizationType: claude_max→max、无 organizationType 账号 →subscription、仅CLAUDE_CODE_OAUTH_TOKEN→subscription、全空 →unknown、文件缺失 →unknown见 tests/shared/observed-billing.test.ts存储与迁移内存库建表后PRAGMA table_info(sdk_sessions)含两列setSessionObservedMetadata的 COALESCE 语义——第二次以undefined调用不清空首值旧表种子后构造SessionStore完成迁移且schema_versions写入 50见 session-store-observed-metadata.test.tstelemetryrollup 携带 last-seen 的ide/provider/observed_*无记录时observed_*默认unknown后到记录覆盖先到记录scrubber 放行两个新属性。端到端验收Phase 4可用此命令做真实会话冒烟构建并同步后等任意 Claude Code 会话触发一次 Stop hook然后查询 SQLitenpm run typecheck npm run build bun test tests npm run lint:hook-io npm run lint:spawn-env sqlite3 ~/.claude-mem/claude-mem.db \ select id, platform_source, observed_model, observed_billing from sdk_sessions order by id desc limit 3预期看到真实模型 id 与订阅档位如max。另有三组反模式 grep 兜底observed-billing.ts中不得出现accessToken|refreshToken|emailAddress|accountUuidobservation.ts中不得出现extractLastAssistantModel防止混入 per-tool 路径git status --porcelain plugin/scripts必须为空无构建产物混入功能 PR。小结一条可复制的“分层遥测补齐”范式纵览整条链路这次改造的价值不止于两个新字段更在于沉淀出可在其它遥测扩展中复用的范式信息源选择以“每回合可刷新 平台通用”为准绳从 transcript 反向扫描取模型比 hook 入参传输更省事且天然覆盖/model切换只在拥有正确环境的进程内做探测Hook 进程持有 Claude Code 环境并把不确定值收敛为闭集枚举保证 PostHog 侧基数稳定隐私不靠自觉而靠结构.claude.json解析即投影、遥测白名单兜底、错误日志不记录 token 明文存储保守、上报最新的双轨语义库侧COALESCE保首值rollup 侧 last-seen 取最新两条规则互补地定义了“一个会话的 observed 身份”。若需深入可直接阅读设计文档 plans/2026-09-01-posthog-observed-model.md、Hook 侧实现 src/cli/handlers/summarize.ts 与 src/shared/observed-billing.ts以及 rollup 聚合核心 src/services/telemetry/buffer.ts。【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

BLE功耗优化:广播间隔与连接参数怎么配?纽扣电池续航一年

BLE功耗优化:广播间隔与连接参数怎么配?纽扣电池续航一年

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

2026/9/7 11:23:23
ADAU1787双DSP音频Codec:超低延迟主动降噪耳机完整开发指南

ADAU1787双DSP音频Codec:超低延迟主动降噪耳机完整开发指南

做主动降噪耳机或者便携音频设备的工程师,应该都绕不开 ADAU1787BCBZRL 这串型号。它是ADI的一颗超低延迟双DSP音频Codec,很多中高端TWS耳机、头戴式ANC耳机和助听器方案里都能看到它的影子。 先说结论:这颗料最大的价值,是把 …

2026/9/7 11:23:23
通达信趋势波段指标“王中王”设计逻辑与源码拆解

通达信趋势波段指标“王中王”设计逻辑与源码拆解

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

2026/9/7 11:23:23
根因误判排查指南:如何通过假设验证与日志分析快速定位线上故障

根因误判排查指南:如何通过假设验证与日志分析快速定位线上故障

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

2026/9/7 11:23:23
计算机单片机毕设实战-基于 STM32 的重量检测型智能药箱控制系统设计 基于 STM32 的语音播报服药提醒硬件系统开发

计算机单片机毕设实战-基于 STM32 的重量检测型智能药箱控制系统设计 基于 STM32 的语音播报服药提醒硬件系统开发

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/7 11:23:23
FurMark 1.6.5烤机全解读:从显卡压力测试原理到稳定性判断

FurMark 1.6.5烤机全解读:从显卡压力测试原理到稳定性判断

简介:FurMark 1.6.5 是一款基于 OpenGL 的显卡压力测试与稳定性检测工具,面向硬件评测用户、游戏玩家及超频爱好者,用于在高负载渲染场景下检验显卡性能极限与稳定性。软件支持分辨率、反锯齿、窗口/全屏等参数自定义,可通过批处理…

2026/9/7 11:18:22