☰
OpenChronicle 捕捉层深度解析:AX Tree + 截图如何变成结构化上下文信号
2026/10/1 21:40:30 网站建设 项目流程

OpenChronicle 捕捉层深度解析:AX Tree + 截图如何变成结构化上下文信号

【免费下载链接】OpenChronicle项目地址: https://gitcode.com/gh_mirrors/op/OpenChronicle

OpenChronicle 是一款开源、本地优先的 AI Agent 记忆系统,它的捕捉层负责把屏幕上的 AX Tree(辅助功能树)和截图转化为结构化上下文信号,让 AI 助手真正"看见"你正在做什么。本文用尽量少的代码,带你完整拆解这条捕捉管线:信号从哪来、如何节流降噪、又怎样落成可检索的 JSON 文件。

什么是捕捉层:唯一接触外部世界的组件

在 OpenChronicle 的架构里,捕捉层(Capture Layer)是整个系统中唯一直接接触 macOS 的层——上层的时间线聚合、会话切分、记忆写入,永远不直接调用系统 API。它每"观察"一次,就往~/.openchronicle/capture-buffer/写一个 JSON 文件。

这种"单一入口"设计带来的好处是:下游所有 LLM 阶段看到的都是同一套规范化数据结构,而不是五花八门的原始系统调用结果。

核心思路:先捕获,再压缩,后分类——捕捉层只负责忠实记录"此刻屏幕上发生了什么"。

完整管线可参考 docs/architecture.md,捕捉层由 S0 事件调度器与 S1 解析器两个子阶段组成。

两个信号源:事件驱动 + 心跳定时器

捕捉由两条路径触发,二者最终都汇入同一个capture_once流程(scheduler.py):

信号源工作方式适用场景
mac-ax-watcher(主)一个随包发行的 Swift 二进制,订阅所有 App 的 AX 通知(窗口聚焦、输入值变化、标题变化、App 激活),每发生一个事件就在 stdout 输出一行 JSON你正在打字、切窗口的"活跃期"
心跳定时器(兜底)默认每 10 分钟(heartbeat_minutes)强制拍一次快照长时间静默期,保证"有迹可循"

事件流的读取与容错由 watcher.py 负责:它逐行解析 JSONL,watcher 崩溃后按指数退避自动重连;若系统返回"辅助功能权限未授予"(退出码 2),则停止重连并记录错误,避免空转。

AX Tree 作为主信号:为什么它比截图 OCR 更香

OpenChronicle 采取AX-first策略——把 macOS 辅助功能(Accessibility)树当作第一信号源,而非截图 + OCR。理由很务实:

  • 💰成本更低:结构化文本的处理成本远低于视觉 OCR 管线;
  • 🎯意图更准:当前聚焦元素、正在编辑的文本、URL、交互状态,AX Tree 天然就带;
  • 🧹记忆更干净:文本易于去重、归一化、索引,适合长期留存;
  • 🖼️截图兜底:AX 覆盖不到的视觉信息,留给截图作为补充。

AX 树的获取是一次性调用随包的mac-ax-helperSwift 二进制(ax_capture.py),按配置的ax_depth深度裁剪后返回 JSON,敏感字段(如密码输入框)在 helper 层就被替换为[REDACTED],Python 侧根本看不到明文。

⚠️一个关键的坑——AX 深度:原生 Cocoa 应用的树通常只有 5~15 层,而 Electron 应用(VS Code、Slack、Notion 等)的用户内容藏在 20~60 层深的 DOM 之下。因此默认ax_depth = 100(config.py)。硬件有限的机器可降到 30,但不要低于 20,否则你会"静默丢失"聊天正文这类深层内容。

S1 解析器:把 AX Tree 蒸馏成三个结构化字段

原始 AX 树体积大、噪声多。S1 解析器(s1_parser.py)在每次捕获时内联运行,把树"蒸馏"成下游 LLM 真正消费的三个字段:

字段含义限制
focused_element光标所在元素:角色、标题、正在输入的值、是否可编辑值截断 2000 字符
visible_text屏幕可见内容的 Markdown 渲染("你此刻读到的东西")上限约 10,000 字符
url仅浏览器(Chrome/Safari/Firefox/Edge 等)场景下从地址栏正则提取否则为null

聚焦元素的识别逻辑见 _extract_focused_element:它在聚焦窗口里找可编辑控件(AXTextField/AXTextArea/AXComboBox)或静态文本,这就是"用户光标上下文"——他正在往哪里打字、选中了侧边栏哪一行。

截图是次级信号:被拍下来,但不喂给 LLM

截图由 mss + Pillow 完成(screenshot.py):抓主显示器 → 宽度超过 1920 时按比例缩放 → 编码为 quality 80 的 JPEG base64,内嵌进捕获 JSON。

它当前的角色很克制:

  • 不进时间线聚合器、会话 reducer、分类器的任何 prompt——因为结构化文本已经够用且便宜得多;
  • 留给未来的视觉模型管线和调试排查;
  • 可通过include_screenshot = false完全关闭(config.py)。

四大事件节流机制:别让每个按键都触发一次捕获

macOS 的 AX 事件流是"消防水带"级别的。事件调度器(event_dispatcher.py)先按事件类型分类,再用四个时间旋钮层层过滤:

旋钮默认值作用
debounce_seconds3.0sAXValueChanged事件在窗口内合并,只有最后一次触发捕获——避免"每敲一个键拍一次快照"
dedup_interval_seconds1.0s相同(事件类型, App)组合在窗口内直接丢弃
min_capture_gap_seconds2.0s两次捕获之间的硬性间隔下限
same_window_dedup_seconds5.0s同一(bundle_id, 窗口标题)的非聚焦变化事件合并;聚焦切换永远放行

事件分类本身也很讲究:窗口聚焦切换、App 激活、鼠标点击、键盘输入立即捕获;值变化防抖捕获;标题变化则直接跳过(太吵,已被窗口/应用事件覆盖)。

内容指纹去重:屏幕没变,就一个字都不写

时间旋钮拦不住一种情况:锁屏整晚、暂停的视频、闲置的 IDE——画面纹丝不动,AX 事件却源源不断。

为此调度器引入了内容指纹(scheduler.py):对bundle_id + 窗口标题 + 聚焦元素值 + visible_text + url做 SHA-256 哈希,与上一次写入的捕获比对,相同则不落盘、也不触发会话管理器的空闲计时(时间戳、触发源、截图都刻意排除在指纹之外)。

这一步同时保住了两件大事:缓冲区不被"幽灵捕获"灌爆,且当前会话不会因为无意义的重复事件而永远无法进入空闲切分。

一份捕获文件里到底有什么

每次捕获最终落成如2026-04-21T17-07-32p08-00.json这样的文件(ISO-8601 时间戳做安全文件名化),骨架大致如下:

{ "timestamp": "2026-04-21T17:07:32+08:00", "trigger": { "event_type": "window_focus_changed", "app": "Claude" }, "window_meta": { "app_name": "Claude", "bundle_id": "com.anthropic.claudefordesktop" }, "focused_element": { "role": "AXTextArea", "value": "I have an interview at 18:00" }, "visible_text": "### New conversation — Claude\n...", "url": null, "ax_tree": { "...": "裁剪后的完整树" }, "screenshot": { "image_base64": "iVBORw0KG...", "width": 1920, "height": 1200 } }

写入的同时还会做一次FTS5 索引写入:app_name、window_title、focused_value、visible_text、url这些可搜索文本同步进captures_fts虚拟表(fts.py),让 MCP 客户端能直接全文检索原始屏幕内容,而不必扫描磁盘上的 JSON。

缓冲区卫生:分层保留策略

截图 base64 约占单个捕获文件体积的77%,但下游暂不消费它。于是 cleanup_buffer 采用三段式清理,且只动"已被时间线吸收"的文件,绝不碰尾部未处理数据:

  1. 24 小时后:剥掉截图字段,文件缩到原来约 20% 的体积,文本信号全保留;
  2. 7 天后(buffer_retention_hours = 168):整个 JSON 删除;
  3. 总量超 2GB 时:按最老优先逐份驱逐。

稳态下缓冲区只占几百 MB——这就是"截图先留着、过期先瘦身、结构信号长保留"的分层思想。

小结

OpenChronicle 捕捉层的设计哲学可以浓缩成三句话:

  1. AX Tree 为主、截图为辅——用最低成本拿到最准的用户意图信号;
  2. 多层节流——事件分类 → 时间旋钮 → 内容指纹,三层漏斗把"消防水带"收敛成每天几百份高质量捕获;
  3. 忠实落盘、异步瘦身——结构化信号长期可检索,大体积截图按生命周期优雅降级。

想深入了解每个环节的默认值与调参方式,推荐精读 docs/capture.md;上手验证只需openchronicle capture-once一条命令,立刻就能看到一份完整的结构化上下文信号。

【免费下载链接】OpenChronicle项目地址: https://gitcode.com/gh_mirrors/op/OpenChronicle

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询