> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/gong-neng/obsidian-wiki/sources.md).

# 记忆来源与作用域

一个 **记忆源** 是一个已配置的连接器，它向其提供 [记忆树](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md)。其中树拥有 *“我该如何存储和总结？”*， `memory_sources` 域（`src/openhuman/memory/sources/`）拥有上游问题： **“我的记忆由什么提供？”** 它是一个带类型的连接器注册表，持久化在 `config.toml` 下的 `[[memory_sources]]`中，运行时支持 CRUD、统一的读取器抽象、按源同步状态，以及 `openhuman.memory_sources_*` RPC 接口。

该域只 *定义连接器并从中读取*。摄取引擎和同步调度位于 `记忆` / `memory_sync`；源会把工作分派到正确的后端。

***

## 源类型

每个源都是一个单一的扁平 `MemorySourceEntry` (`src/openhuman/memory/sources/types.rs`），其 `类型` 判别字段（ `SourceKind` enum）决定需要哪些字段。校验在添加/更新时由 `validate()`强制执行，而不是由类型系统强制。各类型：

| 类型             | `SourceKind`   | 摄取内容                                                   |
| -------------- | -------------- | ------------------------------------------------------ |
| **Composio**   | `Composio`     | 一个通过 OAuth 连接的 SaaS 集成（Gmail、Slack、Notion 等）；同步由提供方驱动。 |
| **对话**         | `对话`           | 代理自身的对话转录。                                             |
| **文件夹**        | `文件夹`          | 本地目录，按 glob 匹配（默认 `**/*.md`，每个文件上限 10 MB），并带有路径穿越防护。   |
| **GitHub 仓库**  | `GithubRepo`   | 项目活动（提交、问题、PR）通过 `gh` CLI 或公共 REST 回退接口获取。             |
| **RSS 订阅源**    | `RssFeed`      | RSS/Atom 订阅项。                                          |
| **网页**         | `WebPage`      | 抓取到的网页，可选地通过 CSS `选择器`.                                |
| **Twitter 查询** | `TwitterQuery` | 已保存的 Twitter 查询。读取器已搭建；在凭据到位前，故意未实现同步。                 |

每个条目还携带可选的每次同步预算（`max_tokens_per_sync`, `max_cost_per_sync_usd`, `sync_depth_days`），这样一个话多的源就不会在一次运行中把你的 token 开销炸掉。

***

## 添加和配置源

源通过 `memory_sources` 控制器进行 CRUD（`src/openhuman/memory/sources/schemas.rs` → `rpc.rs`），命名空间 `openhuman.memory_sources_*`:

| RPC           | 目的                        |
| ------------- | ------------------------- |
| `列表`          | 列出已配置的源（会先延迟协调 Composio）。 |
| `获取`          | 通过 `id`.                  |
| `获取一个源`       | 添加一个源；特定类型的字段在请求中是平铺的。    |
| `更新`          | 通过 `MemorySourcePatch`.   |
| `进行部分更新`      | 通过 `id`.                  |
| `删除一个源`       | 通过其读取器列出该源中的可读条目。         |
| `读取单个条目的内容。`  | read\_item                |
| `sync`        | 排队一次手动同步（立即返回；进度通过事件查看）。  |
| `status_list` | 按源的同步状态。                  |

所有变更都会重新加载活动的 `Config`，应用更改，并 `config.save()` 以原子方式（`registry.rs`）。在桌面应用中，这些会与 [自动获取](/openhuman/zh/gong-neng/obsidian-wiki/auto-fetch.md) 节奏一起显示在 Intelligence / Memory 标签页中。

***

## 读取器抽象

每种类型都实现一个异步 trait， `SourceReader` (`src/openhuman/memory/sources/readers/mod.rs`):

```rust
#[async_trait]
pub trait SourceReader: Send + Sync {
    fn kind(&self) -> SourceKind;
    async fn list_items(&self, source, config) -> Result<Vec<SourceItem>, String>;
    async fn read_item(&self, source, item_id, config) -> Result<SourceContent, String>;
}
```

一个 `reader_for(kind)` 分发器会返回正确的实现（`FolderReader`, `GithubReader`, `RssReader`, `WebPageReader`等等）。在手动 `sync`时，基于读取器的类型会遍历 `删除一个源` 并通过 `memory::ingest_pipeline::ingest_document` (`sync.rs`）；Composio 源则整体委托给 `memory_sync::composio::run_connection_sync` ，而不是逐条读取条目，因此 `ComposioReader::read_item` 只是一个说明性占位符。

***

## 同步状态与新鲜度

`status.rs` 通过查询计算每个源的 `SourceStatus` ，并使用 `mem_tree_chunks` （已同步/待同步块、最后一个块的时间戳） `source_id LIKE` 前缀： `mem_src:{id}:%` 用于读取器类型， `{toolkit}:%` 用于 Composio。每个源都会得到一个 `FreshnessLabel`:

* **Active**：最后一个块距今 ≤ 30 秒。
* **Recent**：最后一个块距今 ≤ 5 分钟。
* **Idle**：更早，或尚无块。

同步进度作为 `MemorySyncStageChanged` 事件流转（Requested → Fetching → Stored → Ingesting → Completed/Failed），并标记 `connection_id = Some(source.id)`，这样 UI 就能在不轮询的情况下显示实时进度。 `status_list` 把每个源的查询失败降级为一个 `Idle` 零行条目，而不是让整个调用失败。

**Composio 自动 upsert。** 当创建一个 OAuth 连接时， `memory_sync::composio::bus` 调用 `upsert_composio_source`，因此新连接的集成会立即作为源出现，无需重启。 `list_rpc` 还会在每次列出时执行一次延迟协调（`reconcile::ensure_composio_sources`），以捕获在此钩子存在之前创建的连接。

***

## 代理档案的源作用域

默认情况下，代理会从 **每个** 源中召回。源作用域允许代理档案将召回限制为一组白名单源 id，因此客户支持风格的代理绝不会暴露你的个人 Gmail，而研究风格的代理会始终专注于相关的仓库和订阅源。这是隐私与专注控制，而不只是相关性调整。

该机制位于 `src/openhuman/memory/source_scope.rs`。将允许列表贯穿于每个记忆工具和深层 `select_trees` 检索层会涉及数十个调用点。因此，类似于 `thread_context`，channel 在代理轮次周围设置一个 `tokio::task_local!` ，检索层会以环境方式读取它，无需显式传递：

* **`None`** （在任何作用域之外，或 `with_source_scope(None, …)`）表示 **不受限制**。这是 cron、子代理、CLI，以及任何未设置 `memory_sources` 的档案的默认行为。
* **`Some(set)`** 会将召回限制为集合中的源作用域。 **空** 集合则什么也不会显示（该档案没有选择任何源）。

该闸门是 **按标签区分并失败开放** ，适用于不是记忆源块的所有内容。每个由源摄取的块都带有 `memory_sources` 标签；门禁（`chunk_source_allowed`）只会处理带标签的块：

* 一个块 **在没有** 的 `memory_sources` 标签（工作记忆、对话转录、内部块） **始终通过**，即使在空白名单下也是如此。
* 一个 **带标签的** 记忆源块只有在其源 id 被允许时才会通过。该 id 会与原始 `source_id` （Composio / 类似 `slack:#eng`这样的通道作用域）或从 `mem_src:<id>:<item>` 复合标识（基于读取器的源）中提取的注册表 id 匹配。

因此，收紧档案的作用域会隐藏其连接的源，而不会让它失去自己的对话上下文。

***

## 另请参阅

* [自动获取](/openhuman/zh/gong-neng/obsidian-wiki/auto-fetch.md)：保持活跃源新鲜的 20 分钟节奏。
* [Memory Trees](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md)：每个源都会进入的管道。
* [Obsidian Wiki](/openhuman/zh/gong-neng/obsidian-wiki.md)：Markdown 金库源最终落入的地方。
* [集成](/openhuman/zh/gong-neng/integrations.md)：连接 Composio 源背后的 OAuth 提供方。
