---
description: >-
  OpenHuman 的本地优先存储库。从工具中摄入数据，规范化为 Markdown，
  分块，评分，并折叠为层级化的摘要树。
icon: tree
---

# 记忆树

<figure><img src="../../.gitbook/assets/image.png" alt=""><figcaption><p>记忆树。所有文档的高度压缩视图。</p></figcaption></figure>

记忆树是 OpenHuman 的存储库。它不是一个披着"记忆"外衣的向量数据库，而是一个确定性的、bucket-sealed（桶密封）处理流水线，将你一天中杂乱的数据流——聊天、邮件、文档、集成同步结果——转化为你机器上结构化的、可查询的、带摘要支撑的 Markdown。

## 它做什么

每个连接的源都走同样的流水线：

```text
源适配器（聊天 / 邮件 / 文档）
        |
        v
规范化    规范化的 Markdown + 来源元数据
        |
        v
分块器    确定性的 ID，≤3k token 的有界片段
        |
        v
内容存储   原子 .md 文件（正文 + 标签）
        |
        v
存储     持久化（块、评分、摘要、任务、热度）
        |
        v
评分     信号 + 向量 + 实体提取
        |
        v
源 / 主题 / 全局树    按作用域的摘要树
        |
        v
检索     搜索 / 深入 / 主题 / 全局 / 获取
```

热路径（规范化 → 分块 → 快速评分 → 持久化 → 入队后续工作）很快。重型工作——向量生成、实体提取、密封摘要 bucket、每日摘要——在后台 workers 中运行，UI 永远不会阻塞。

如果你开启了[本地 AI](../model-routing/local-ai.md)，嵌入向量和摘要树的构建可以在**设备上通过 Ollama** 运行；否则它们像其他模型调用一样通过 OpenHuman 后端处理。

## 三棵树，三个作用域

* **源树**，每个源一个滚动缓冲区（L0），填满后密封为 L1 → L2 → …。每个 Gmail 标签、每个 Slack 频道、每个上传的文档各一棵。
* **主题树**，按实体懒加载的摘要，由**热度**驱动。某个实体（人、项目、股票代码、仓库）出现得越频繁，其主题树就越积极地被构建和刷新。
* **全局树**，一个跨当天摄入的所有内容的每日全局摘要。

检索可以针对任何作用域：搜索单个源，深入某个主题，或拉取全局摘要。

## 它在磁盘上的位置

位于你的工作区内（默认 `~/.openhuman`，或 `OPENHUMAN_WORKSPACE` 指向的路径）：

| 路径                      | 内容                                           |
| ------------------------- | ---------------------------------------------- |
| `memory_tree/chunks.db`   | 块、评分、摘要、实体索引、任务、热度           |
| `wiki/`                   | Markdown 存储库 —— 见 [Obsidian Wiki](./README.zh-CN.md) |

一切都是本地的。除非你明确发送包含原始数据的聊天消息，否则你的原始数据不会离开你的机器。

## 为什么是树，而不是向量存储

向量存储回答"与这个查询相似的是什么？"记忆需要回答更多：

* **今天发生了什么？**（全局摘要）
* **这个人的最新情况是什么？**（主题树，热度驱动）
* **上周二下午 3 点 Stripe webhook 说了什么？**（源树 + 来源追溯）

树给你压缩**和**导航。嵌入向量仍然存在于内部，所以语义搜索继续工作，但上面的结构才是让记忆感觉像大脑而不是一堆碎片的原因。

## 流水线如何工作？

用户看到的功能很简单：连接一个源，智能体就获得了对其的持久记忆。实现这一功能的流水线横跨一条 HTTP 触发的摄入路径、一个持久化的任务队列、一组后台 workers、三个独立的摘要树，以及一个每日 UTC 调度器。

### 1. 摄入

新的聊天 / 邮件 / 文档到达。热路径将其规范化为 Markdown，用确定性 ID 分块，运行廉价的快速评分，在单个事务中持久化所有内容，将每个块标记为 `pending_extraction`，并为 workers 入队后续工作。

这里有三个重要属性：

* **确定性的。** 块 ID 是内容寻址的，所以对相同输入重新运行摄入永远不会产生重复。
* **快速的。** 这条路径中没有 LLM 调用——只有廉价的启发式方法。
* **写入有界。** 所有操作在一个事务中完成，所以部分摄入不会留下悬空的行。

### 2. 队列

后续工作进入持久化的任务队列（与块在同一个磁盘存储中）。每个任务携带一种类型、一个 payload、一个去重 key、重试记录和一个调度窗口。类型如下：

| 类型            | 功能                                                                                   |
| --------------- | -------------------------------------------------------------------------------------- |
| `extract_chunk` | 深度评分 + 实体提取。决定 `admitted` 还是 `dropped`。                                  |
| `append_buffer` | 将一个 admitted 的叶子添加到源的（或主题的）树的 L0 缓冲区。可能触发密封。            |
| `seal`          | 将 L0 缓冲区压缩为 L1 摘要；如果父缓冲区已满，则向上级联。                             |
| `topic_route`   | 将叶子路由到每个实体的主题树，由热度检查控制。                                          |
| `digest_daily`  | 构建全局每日摘要节点。                                                                  |
| `flush_stale`   | 强制密封停留太久的缓冲区。                                                              |

### 3. Workers

一个小型的后台 workers 池（默认 3 个）从队列中取出任务并运行。池被摄入路径立即唤醒，有一个短轮询后备方案，所以错过的唤醒不会搁置工作。共享信号量限制并发 LLM 调用，这样新源的突发不会意外地扇出到数十个并发嵌入向量。

启动时，任何 worker 租约已过期的任务（因为崩溃或 kill）会被返还到队列。崩溃不会丢失已 admitted 但尚未密封的工作。

### 4. 树状态

三棵独立的树从同一个叶子流构建。

* **源树** —— 每个源一个。新叶子进入 L0 缓冲区；当缓冲区填满（或 stale-flush 触发），一个 `seal` 写入 L1 摘要，级联继续向上。
* **主题树** —— 每个高热度实体一个。路由器检查实体是否足够热以值得拥有自己的树，如果是，则追加到其缓冲区。
* **全局树** —— 一棵树，每天增长一个节点，随着天数累积向上行走。

### 5. 调度器

调度器循环独立于摄入路径运行。每天 00:00 UTC 它为昨天入队一个全局每日摘要，并为今天入队一个 stale-flush。调度器**不自己运行**摘要器——一切通过队列，所以重试、去重和 stale-lock 恢复保持集中。

### 6. 叶子生命周期

每个块经历一个小型状态机：

```text
pending_extraction --> admitted --> buffered --> sealed
        \
         --> dropped
```

* 提取根据深度评分决定 `admitted` 还是 `dropped`。
* Admitted 的叶子移入缓冲区（`buffered`）。
* 当缓冲区密封时，里面的每个叶子被标记为 `sealed`。
* `dropped` 的叶子停在这里。它们的块行保留用于来源追溯，但没有缓冲区或摘要引用它们。

这就是为什么检索可以显示来源追溯而无需重新运行流水线：块行及其终端生命周期状态就够了。

## 触发摄入

* **自动的** —— 每个活跃的集成每 20 分钟自动拉取一次；见 [自动拉取](auto-fetch.zh-CN.md)。
* **手动的** —— 桌面 app 的"记忆"标签页暴露了每个源的"运行摄入"触发器。
* **RPC** —— `openhuman.memory_tree_ingest`，用于高级工作流。

## 在桌面 app 中 —— 智能标签页

从底部导航栏打开。

**系统状态。** 页面顶部显示当前状态（空闲、摄入中、摘要中）和一个**运行摄入**按钮，用于手动触发对任何连接源的同步。

**记忆指标：**

| 指标                   | 显示内容                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| **存储**               | `<workspace>/memory_tree/chunks.db` 和 Obsidian 存储库总大小。                           |
| **源**                 | 已摄入的不同源数量（每个 Gmail 标签、Slack 频道、文档等各算一个）。                      |
| **块**                 | 存储中 ≤3k token 的块总数。                                                                |
| **主题**               | 目前已实例化的主题树数量（从"热"实体构建的每个实体摘要）。                                |
| **最早 / 最新记忆**    | 最旧和最新块的时间戳。                                                                     |

**记忆图谱。** 一个实体及其关系的力导向可视化，从实体索引绘制。图谱随着自动拉取获取更多数据而增长——早期稀疏，几天内变得密集。

**Obsidian 存储库。** 一个**"在 Obsidian 中查看存储库"**按钮通过 `obsidian://open?path=...` 深度链接直接打开 `<workspace>/wiki/`。你也可以在任何文件浏览器中打开该文件夹。

**摄入活动。** 一个显示摄入事件随时间分布的热力图，类似于 GitHub 的贡献图。可用于发现自动拉取空闲的时期（例如连接中断导致同步停止）。

**搜索与检索。** 记忆树上的搜索栏。支持源作用域、主题作用域或全局查询，任何结果都可以链接回底层块文件（在你的 Obsidian 存储库中）以获取完整来源追溯。

**路由。** 智能标签页还显示智能体每个任务使用的模型——见[自动模型路由](../model-routing/README.zh-CN.md)。

## 交换后端

记忆树流水线（分块 → 评分 → 密封 → 摘要）是默认的。在多个智能体间自托管 [agentmemory](https://github.com/rohitg00/agentmemory) 且希望 OpenHuman 共享相同持久化存储的操作员可以通过 `MemoryConfig.backend = "agentmemory"` 选择外部后端——参见 [agentmemory 后端](agentmemory-backend.zh-CN.md) 了解配置 keys、字段映射、端点表、安全措施和故障模式。
