跳转至

使用记录、Memory 与产品分析

状态:P0 存储已落地(XDG usage.sqlite schema v2 + track()),漏斗与画像仍按本文推进。关联 #239。实现契约见 ../backend/usage.md。 相关:../backend/catalog.md../backend/agent.md../backend/telemetry.md../backend/translate.md../backend/skill-import.md../frontend/pdf.md../frontend/pdf-layout-analysis.mdplaza.md

1. 目标与非目标

Issue 三条诉求 + 产品分析,落在同一条 Activity 总线上:

# 诉求 本文对应
1 记录浏览 / 下载 / 阅读,以及翻译、版面、Skill、批注等操作 §3 事件模型 + §4 存储
2 让 Agent 拿到用户操作习惯 §5 Memory + §6 Agent 接入
3 基于额外 context 做总结、推荐 §7 上层功能
4 部分行为投影到 PostHog §3.4 Registry + §8 隐私

目标

  • 本地记录用户意图动作的时间线(何时、对哪篇、用什么方式),形成可查询画像。
  • BYOA Agent 以「被动注入 + 主动查询」获得习惯 context。
  • 同一条事件按白名单投影到 PostHog,做功能采用率,不含论文身份与正文。
  • 在画像之上提供继续阅读 / 周回顾 / 库内推荐。

非目标(首版)

  • 不做 embedding / 向量库。
  • 不把检索词、论文标题、划词原文、译文、批注正文、Skill 仓库 URL 发给 PostHog。
  • 不跨设备同步使用记录。
  • 不自动抽取 ChatGPT 式「记忆短句」(P3 需用户确认)。
  • 不记录纯 UI 噪音(缩放、大纲展开、滚动、命令面板打开)。

2. 现状盘点

能力 现状 复用方式
PostHog core/telemetry:仅 app started / app exited;无私有 capture() Telemetry::capture,业务禁止直连
Catalog SQLite SCHEMA_VERSION + 迁移;is_read / added_at;无 opened_at 复用迁移范式;不共用库
阅读产物 marks/reading-heatmapreading-position 空间分布仍派生;动作时间线另记事件
翻译 runTranslate + 划词 mark + layout-translate.json 在消费方记 1 次会话,不在每块 MT 调用上打点
版面 enqueuePaperLayoutAnalysis / run-analysis;sidecar 可重建 只记真实分析,跳过 cache hit 静默载入
Skill 导入 lookup_import_batchconfirmSkillImportskill_install 确认安装后记一条
批注 高亮走 annotations.json;ask/translate/visual 走 marks/<id>.json 在用户提交处打点,不在防抖导出处
已有 recents @ MRU、最近 Vault、reading-position 首版并存
Agent 上下文 agentPersonalPrompt + AGENTS.md + 会话 chip 无习惯 Memory;见 §5

3. 事件模型

3.1 原则:记动作,不记内容

旧草案「能从产物派生的一律不记」对空间热力仍然成立,但对 Memory / 产品分析 不够:

产物能回答 产物答不好
这篇有没有高亮、译过哪一段 何时开始密集翻译、用哪个 provider
layout.json 是否存在 是入库自动跑的还是用户点了「重新分析」
.agents/skills/pptx 在不在 什么时候从 GitHub 装的、装了几个
is_read 是人点的还是 paper-reader 写的

因此:

  1. 事件 = 用户意图动作的时间线(kind + 类别字段 + 可选 path)。
  2. 不把动作内容写入事件(划词原文、译文、批注正文、检索词对 PostHog 禁用;本地仅 search.query 保留 q)。
  3. 空间分布继续从产物派生reading-heatmap)。删掉一条高亮不会改写历史事件——「用过这个功能」仍在。
  4. 自动后台任务要降噪:版面 cache hit、全文翻译的逐 region runTranslate、EmbedPDF 标注防抖回写,都不单独成事件。

3.2 统一总线

UI / Host 动作
      │
      ▼
 track(kind, payload)          ← 唯一入口
      │
      ▼
 Event Registry                ← 本地 schema + PostHog 投影白名单
      │
      ├──────────────┬─────────────────┐
      ▼              ▼                 ▼
 LocalSink      TelemetrySink     UsageProfile
 usage.sqlite   PostHog 投影      Agent / 继续阅读

前端缓冲:5s / window blur / beforeunload / 满 50 条 → activity_record_events。同一 (kind, path, mode) 1s 去重。Host 一事务写本地(schema v2)。app.started / app.exitedTelemetry::start / shutdown 直写本地,不走 track()。其它 kind 的 PostHog 投影(Telemetry::capture_activity已接线:写本地前按白名单脱敏转发。

业务代码禁止直接 posthog_rs::Event 或手写 INSERT usage_events

3.3 事件表

A. 阅读与库(#239 原范围)

kind 本地载荷 PostHog 漏斗
paper.open / note.open path, mode paper_opened / note_openedmode openTab
paper.focus / paper.blur path, dur_ms 不上报 handleActivePanelChange / closeTab
paper.session path, dur_ms paper_sessiondur_bucket(仅 ≥10s) blur 结算
asset.download path, asset asset_downloadedasset downloadPaperAssetsAction
paper.import path, source paper_importedsource 入库 action
search.query q, hits search_performedhits_bucket(无 q vault/search.ts
agent.run workflow, path? agent_runworkflow runOnce

停留用 focus/blur 配对。单段上限 30min;window blur 立刻结算。

B. 翻译

runTranslate 是执行引擎,不是埋点漏斗:版面全文翻译会对每个 region 调一次,若在此处打点会爆炸。

kind 本地载荷 PostHog 漏斗
translate.selection path, provider, target_lang, chars, auto translate_ransurface=selection, provider_family, target_lang, chars_bucket use-pdf-selection-translate 一次划词结束
translate.layout path, scope(doc|page), provider, target_lang, region_count, ok_count translate_ransurface=layout, scope, provider_family, target_lang, region_bucket use-pdf-layout-translate 整次 doc/page 任务结束(不是每块)
  • provider_familyfree / commercial / agent。本地可存具体 provider id(deepl / tencent…);PostHog 只传 family,避免把商用配置当画像。
  • 不记原文、译文。
  • auto=true 仅当「划词自动翻译」触发;手动点菜单为 false
  • 停止 / 清除覆盖层不记成功事件;可记 translate.layout + ok_count 反映部分完成。

C. 版面分析

kind 本地载荷 PostHog 漏斗
layout.analyze path, trigger(import|open|manual), backend(onnx|paddle|mineru), cache(miss|force), region_count, dur_ms layout_analyzedtrigger, backend, cache, dur_bucket run-analysis / headless executor 实际跑模型成功后

跳过:sidecar cache hit 的静默 JSON→侧栏(打开论文时的常态)。那不是用户动作,会淹没画像。

用户点 Figures「分析 / 重新分析」:

  • 有 sidecar 且非 force → 只是再归并,不记(与打开时相同)。
  • 无缓存或内部 force → 记 cache=miss|force

D. Skill 导入

kind 本地载荷 PostHog 漏斗
skill.install skill_id[], source_kind(github|npx|skills_sh), installed, skipped skill_installedsource_kind, count_bucket confirmSkillImport 任务成功
  • 一次确认装多个 → 一条事件,带数量。
  • 本地可存 skill_id(给 Agent:「你装过 pptx」)。
  • PostHog 不传 owner/repo、URL、skill 名。
  • 仅发现、用户取消 → 不记。远程 Vault 禁用安装,无事件。

E. 批注 / 划词产物

kind 本地载荷 PostHog 漏斗
mark.create path, type(highlight|comment|ask|translate|visual), source(selection|region|formula), page? mark_createdtype, source 见下
mark.update path, type 不上报(编辑备注噪音) 改 comment / 续写 ask
mark.delete path, type mark_deletedtype 删除 pin / 高亮

漏斗必须在用户提交,禁止挂在防抖导出上:

type 写入 打点处
highlight / comment marks/annotations.jsonsaveAnnotationItems 会整表回写) 划词菜单「高亮 / 批注」确认、批注编辑器首次保存;对 annotations 做 id diff,只对新增 id 打 create
ask marks/<id>.json 划词「提问」提交,或选区 chip 发送后落盘对话卡(与现有 ask 卡同一路径)
translate(划词卡) marks/<id>.json translate.selection 成对:一次划词既有翻译会话,也有 mark 落盘。允许两条,kind 不同
visual marks/<id>.json + assets/ 视觉批注编辑器「保存」;仅草稿不记

ask / 带 Agent 的 visual 另外会走 agent.run。两条都记:一条是「做了批注」,一条是「叫了 Agent」。

不记:quote、comment 正文、裁剪图、公式符号表内容。

F. 其它高信号动作(「等」的首版清单)

只收有明确漏斗、对习惯或采用率有用的。滚动 / 缩放 / 大纲 / 命令面板开合排除。

kind 本地载荷 PostHog 漏斗
paper.tag path, op(set|add|rm), tags? paper_taggedop, tag_count_bucket paper_set_tags 前端 action
paper.read path, is_read, via(user|paper_reader) paper_read_setis_read, via paper_set_is_read
refs.parse path, trigger(auto|manual) refs_parsedtrigger paper_refs_parse 成功
refs.import path(目标论文) refs_imported References 卡「入库」
zotero.save count zotero_savedcount_bucket Connector saveItems 提交成功
vault.open (无 path 出站) vault_opened 打开 / 切换 Vault
onboarding.complete onboarding_completed 向导最后一步

标签名可进本地(喂 tagAffinity);PostHog 只传操作类型与数量桶。

3.4 Registry 形状

'translate.selection': {
  local: { path: 'string', provider: 'string', target_lang: 'string', chars: 'number', auto: 'boolean' },
  posthog: {
    name: 'translate_ran',
    props: ['surface', 'provider_family', 'target_lang', 'chars_bucket'],
    map: { surface: () => 'selection', provider_family: fromProvider, chars_bucket: bucketChars },
  },
},
'layout.analyze': {
  local: { path: 'string', trigger: 'import'|'open'|'manual', backend: 'onnx'|'paddle'|'mineru', cache: 'miss'|'force', region_count: 'number', dur_ms: 'number' },
  posthog: { name: 'layout_analyzed', props: ['trigger', 'backend', 'cache', 'dur_bucket'] },
},

没登记的 kind 不能发出。新增事件先改 Registry,再在漏斗调用 track()

3.5 候选事件(未纳入 P0)

首版故意不收下面这些。按「值 / 噪音 / 隐私」分成三档,需要时再进 Registry。

建议下一波(P1,对 Memory 或采用率都有用)

kind 为什么值得 漏斗 PostHog 投影
library.export / library.import 文献工作流是否闭环 exportLibraryToFile / importLibraryFromFile format(bibtex/ris/…)、count_bucket
library.rescan Doctor/盘漂是否被用 rescanLibraryPapers added_bucket
paper.parse liteparse 是否在补正文 enqueuePaperPdfParse 成功写出 PAPER.md(跳过 cache) trigger(import|manual)
paper.reader 精读触发面 runPaperReaderWorkflow 开始;与 agent.run+paper.read 互补 via(zap|auto)
skill.use 装了不等于用了 Composer 提交时 skillIds count_bucket(无 skill 名出站)
agent.session 续聊 vs 新开 新建草稿 / loadSession op(new|load|cancel)
agent.fail 产品质量 agent:failed workflow(无错误正文)
note.export 笔记离开应用 Markdown 导出 PDF/PNG format
wiki.follow 双链是否在被走 navigateWiki 不上报 path
refs.graph.click 引用图是否驱动打开 Graph 节点点击 node(paper|stub)
zotero.sync 双向同步采用 zotero_sync 成功 direction/count_bucket
zotero.migrate 欢迎页迁移 migrateZoteroFromWelcome count_bucket
vault.create 激活漏斗 vault_create 仅计数
command.run 哪些命令真有人用 命令面板 run() command_id(稳定 id,如 settings.open
doctor.run / doctor.fix 诊断是否被用 Doctor pane section(alias|wiki|visual)
update.install 版本采纳 关于页「安装并重启」 from_version 已在 person

skill.use 对 Memory 比 skill.install 更重要:画像应说「常用 pptx」,而不是「曾经装过」。

可后置(有信号,但漏斗散或和已有事件重叠)

kind 备注
import.fail 魔棒失败原因类别(识别失败 / 超时 / 限流),无 URL
asset.download.fail 与成功对称,看补下健康度
search.surface search.query 拆成 palette / library / vault;首版一个 kind 即可
library.scope 进入文件夹作用域;弱信号
file.create / file.rename / file.move / file.trash / file.restore 组织习惯;和 paper.import/paper.tag 部分重叠
workspace.split / note.split 分屏是否为核心读法
window.new 多窗采用
pdf.find ⌘F;采用率有用,对 Memory 弱
pdf.outline / pdf.immersive / pdf.page_theme 阅读 chrome
citation.goto 文中引用跳页 / 外链
layout.figure.focus 侧栏点开图/表
agent.permission ask 模式回应;option_kind
agent.ask_user 结构化问卷提交/取消
agent.model / agent.mode 换模型、Plan/Default;出站只传是否第三方,不传 model id
agent.context @ / 选区固定 / 图片附件;易噪,应用「每轮发送时汇总」而不是每次点 chip
agent.install 设置页安装/升级/卸载 CLI
settings.change 只传 allowlist 键名telemetryEnableduiThemetranslate.provider…),永不传值
onboarding.step 比只记 complete 更能看流失;P0 有 complete 即可
remote.open / bridge.pair 远程 / iOS 采用;移动端本身无 PostHog
cli.invoke agentero 子命令名;看 Agent 是否真走 CLI

明确不记

动作 原因
滚动、缩放、翻页、大纲展开 高频、无意图
命令面板 / 设置窗 打开 打开 ≠ 使用
Markdown 自动保存、每次按键 爆炸
EmbedPDF 标注防抖导出、layout cache hit、逐 region runTranslate 机器回声
citation hover、公式 dwell 未点开 试探不是动作
剪贴板复制、Finder / 终端打开 OS chrome
错误 toast 原文、Agent 回复正文、检索词出站 隐私
主题预览 hover、列宽拖拽 无分析价值

判定口诀:有明确提交/成功边界、一天不会上百次、对「习惯」或「这功能有没有人用」有增量 才进 Registry。

4. 存储

4.1 $XDG_DATA_HOME/agentero/usage.sqlite

P0 已落地。 不放进 Vault、也不放进 catalog.sqlite:远程会镜像 catalog;使用记录是设备本地事实。一台机器上的多个 Vault 用 vault 列(绝对路径)区分。

路径、列定义与 kindfacet 对照见 ../backend/usage.md(schema v1)。

usage_vaults    -- 本机 Vault 身份(path UNIQUE)
usage_events    -- append-only:ts, vault, kind, path, paper_path, mode, facet, status, dur_ms, qty, extra
usage_daily     -- PRIMARY KEY (day, vault, kind, paper_path, facet)
usage_memories  -- 声明式短句(P3 再写)

paper_path / facet / qty 由 Host 写入时从 path + extra 抽出,画像只读 usage_daily。WAL + busy_timeout + foreign_keys

4.2 路径与忽略

  • paper_move 成功后 usage_rename_path
  • 必须忽略:wiki / search / watcher(catalog 故意不忽略;usage 必须忽略)/ remote catalog 镜像。
  • 保留期:事件 180 天;日聚合 2 年。usage_clear() + 设置页清除。

5. Memory

没有第四套「ChatGPT Memory」库。三层:

是什么 持久化 注入
情节 usage_events usage.sqlite CLI 按需
语义 UsageProfile 由 daily 算出 build_prompt
声明 agentPersonalPrompt(已有)+ 可选 usage_memories settings / sqlite 现有 preference 块
工作 当前论文 / @ / 选区 会话 已有,不动

5.1 UsageProfile(≤800 tokens)

字段 算法
topPapers Σ dur_ms × 0.5^(age/14) Top 8 + title/tags
continueReading 未读 + 有 reading-position,按最后 focus
stalled 已下载未 open,或 focus < 60s 且 ≥7 天未碰
tagAffinity catalog tags + 本地 paper.tag 事件
rhythm 3h 桶、日均、本周 vs 上周
agentUsage workflow 次数
toolAffinity 新增:翻译 / 版面 / 批注 / Skill 的频次与近况

toolAffinity 示例:

{
  "translate": { "selection": 24, "layout": 3, "providerFamily": "free", "targetLang": "zh-CN" },
  "layout": { "analyzed": 11, "manual": 2, "backend": "onnx" },
  "marks": { "highlight": 40, "comment": 8, "ask": 12, "visual": 6, "translate": 20 },
  "skills": { "installed": ["pptx", "frontend-design"], "lastAt": "2026-08-10" }
}

半衰期 14 天。超出 800 tokens 先截 topPapers,再截 skills.installed

5.2 注入语气

画像是观察,不是指令agentPersonalPrompt 仍是「必须遵守」。

<user_usage_profile>
 30 日均 42min活跃 21:00-24:00
主要方向diffusion 38% · 3D 22%
在读[[Flow Matching]]p12/242 天前
工具划词翻译较多免费 MT  zh-CN);视觉批注 6提问 12
已装 Skillpptxfrontend-design
搁置[[NeRF Survey]]下载 12 天未打开
</user_usage_profile>
workflow 注入
summary / qa / free / related_work
paper_reader 否(忠于原文)
ACP slash(isAcpCommand 否(本就跳过 envelope)

细节通过桌面端设置 / Host API 查询,不塞 prompt。已落地:

# CLI 不再暴露 usage 命令;查询通过桌面端或 Host API 进行

usage top / usage tools 仍是规划,画像未做前先用 timeline + summary。

5.3 声明式记忆(P3)

周回顾 skill 提议短句 → 设置页确认 → usage_memories。不自动写入。可删。关本地开关则不注入。

6. Agent 接入

与 §5.2 相同:prompts.rs::build_promptpersonal_preference_directive 旁加可选块;Host 在组装时读 Profile(不要让前端把画像塞进 personalPrompt)。

内置 skill templates/vault/.agents/skills/usage-reviewer/SKILL.mdversion: 1):何时用 CLI、周回顾写 notes/Reviews/YYYY-WW.md、推荐必须给「因为你在读 X」、不改用户手写笔记。

7. 上层功能

  • 继续阅读:Library 顶栏 continueReading 前 3 + stalled 前 2。
  • 周回顾:手动触发,不自动跑 Agent。
  • 库内推荐 v0:双链邻居 ∪ 同标签未读 ∪ 已入库参考文献;plaza 管库外。

8. 隐私与开关

开关 默认 含义
telemetryEnabled true(已有) 投影行为事件到 PostHog(下次启动生效)

本地记录始终开启、无开关:行为事件恒写 usage.sqlite(生成画像、注入 Agent、供 CLI 查询)。是否上报 PostHog 由 telemetryEnabled 单独控制;关闭后本地照常记录,仅停止投影。可一键清除本地记录。Telemetry::capture_activity 投影已接线

PostHog 硬约束:无路径、标题、paper id、DOI、检索词、划词/译文/批注正文、Skill URL/名称、Vault 路径。已有 app started / app exited 保留;新事件用 object_verb

设置 → 隐私:telemetryEnabled 开关 +「清除使用记录」;文案写明本地检索词与 Skill id 不出站。

iOS / TestFlight 仍无遥测。debug / 无 key 构建不上报。

9. 分期

阶段 内容 可验证
P0 Registry + track() + usage.sqlite v2 + 双开关 已落地(漏斗见 usage.md「前端漏斗」;翻译 / 版面 / 批注尚未接线)
P0 余 补翻译 / 版面 / 批注漏斗;Telemetry::capture_activity 投影 已接线行为事件投影(telemetry.md 映射表);关 telemetryEnabled 停投影但本地照记;cache hit 与逐 region 翻译不刷屏;翻译 / 版面 / 批注漏斗仍待接线
P1 Profile(含 toolAffinity)+ 继续阅读 顶栏能跳对页
P2 build_prompt 注入 问答能提到工具习惯;关本地后注入消失
P3 usage-reviewer、周回顾、推荐、可选 memories Reviews/YYYY-WW.md;CLI 不再提供 usage 查询入口

10. 风险

风险 缓解
漏斗挂错层(runTranslate / saveAnnotationItems)导致事件爆炸 消费方打点;高亮用 id diff;layout 跳过 cache hit
翻译双记(translate.selection + mark.create 允许,语义不同;Profile 分别统计
用户觉得被监视 双开关 + 清除 + 文档写明本地 / 出站边界
prompt 被工具习惯撑爆 800 token 硬顶;Skill 列表截断
usage.sqlite 触发 watcher §4.2 忽略列表逐项确认
与 heatmap 重复 heatmap 继续只读产物;事件不存坐标

11. 文档落点

  • 本文(草案):docs/development/usage-analytics.md
  • 实现后:docs/backend/usage.md + 同步 telemetry.md / data-model.md / agent.md / cli.md / remote.md / library.md / roadmap.md + todo.md