内容
<p align="center">
<img src="assets/logo.png" alt="Enfold logo: folded planes of light converging on a single bright point" width="280">
</p>
<h1 align="center">Enfold</h1>
<p align="center"><em>每个记忆都包含了整体。</em></p>
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
<img src="https://img.shields.io/badge/MCP-stdio%20server-1f6feb" alt="MCP stdio server">
<img src="https://img.shields.io/badge/hermes--agent-memory%20plugin-8A2BE2" alt="Hermes Agent memory plugin">
<img src="https://img.shields.io/badge/local--first-SQLite-044a10" alt="local-first SQLite">
</p>
Enfold 是为 AI 代理提供本地、私有、长期记忆的工具:一个 SQLite 文件,您的代理可以通过有意义的回忆来读写。Claude Code、Codex CLI 和任何支持 MCP 的代理都可以通过捆绑的_stdio MCP 服务器连接。[Hermes Agent](https://github.com/NousResearch/hermes-agent) 作为内存提供程序获得了更深入的本地集成。它们都指向同一个数据库,它们共享一个大脑:一个代理会话中学习的一个事实在其他代理的下一个会话中被回忆起来。
这个名字就是设计。在全息图中,板块的每个碎片都重建了整个场景。Enfold 以同样的方式处理记忆:事实被一次性、原子性地存储,一个部分提示(一个释义、一个实体、一个随机关键词)足以带回完整的上下文。
## 为什么选择 Enfold
- **按含义召回,而不是关键词。**密集嵌入、BM25 关键词搜索、token 重叠和全息(HRR)组合检索被混合成一个信任加权分数。释义有效;确切的端口、SHA 和主机名也有效。
- **一个大脑,许多代理。**MCP 服务器和 Hermes 网关并发操作在同一个 SQLite 文件上,使用 WAL 加上跨进程写锁。每次写入都带有 `source` 标签,因此您始终知道哪个代理学习了什么。
- **写入被控制,因此存储保持干净。** 近似重复的重新陈述在写入时被拒绝。真正的价值更新(相同事实,新数字或状态)取代旧事实而不是与之共存,历史链被保留:失效,但永不删除。
- **本地优先,无需离开您的机器。** SQLite 用于存储,本地嵌入通过 Ollama(GPU)或 FastEmbed(仅 CPU,无基础设施)。如果嵌入器关闭,召回会优雅地降级为关键词和符号搜索,而不是失败。
- **衡量,而不是凭直觉。** 配备了可复制的召回基准;本 README 中的每个数字都来自存储库中的脚本。当一种流行技术丢失时(例如,一个交叉编码器重新排序器),它被删除,负面结果被记录。
- **为无人值守操作而构建。** 事实提取通过一个崩溃安全的 SQLite 队列,带有重试、退避和死信行。睡眠时间反射从相关事实的集群中提炼出可靠的见解,带有对来源的强制引用。
## 快速入门:Claude Code
```bash
git clone https://github.com/victorv2i/enfold.git
pip install mcp numpy fastembed
claude mcp add enfold -- python3 /absolute/path/to/enfold/enfold/mcp_server.py \
--db-path ~/enfold-memory.db \
--embedding-backend fastembed
```
这就是整个设置。您的代理现在拥有 `memory_search`、`memory_add`、`memory_supersede`、`memory_explain` 和 `memory_history`。无需照顾服务器,无需容器,无需云。
两件事需要注意:
- 按文件路径运行服务器**,而非 `python -m`**(详细信息请参见 [MCP 服务器](#mcp-服务器))。
- 没有 Hermes 检出,服务器运行在捆绑的轻量级引擎上(真正的 SQLite,FTS5,信任分数和实体链接;与 300 测试套件运行的相同测试套件)。对于完整全息引擎,请添加 `--hermes-src /path/to/hermes-agent/src`。普通的 `git clone` hermes-agent 就足够了:无需安装,无需运行网关。
## 快速入门:Codex CLI
在 `~/.codex/config.toml` 中:
```toml
[mcp_servers.enfold]
command = "python3"
args = ["/absolute/path/to/enfold/enfold/mcp_server.py",
"--db-path", "/home/you/enfold-memory.db",
"--embedding-backend", "fastembed"]
```
任何其他支持 MCP 的代理都以相同的方式连接:它是一个普通的 stdio MCP 服务器。
## 快速入门:Hermes Agent(本地提供程序)
Hermes 自动发现 `~/.hermes/plugins/` 下的内存提供程序目录,因此安装是一个复制:
```bash
git clone https://github.com/victorv2i/enfold.git
cp -r enfold/enfold ~/.hermes/plugins/
hermes config set memory.provider enfold
```
作为本地提供程序,Enfold 不仅提供工具,还预先获取相关事实到上下文中,每个回合,并从会话结束和上下文压缩前提取持久事实,并保持嵌入式侧车同步写入。
要与您的其他代理共享网关的存储,请指向 MCP 服务器到相同的文件和匹配的几何形状:
```bash
python3 enfold/mcp_server.py \
--db-path ~/.hermes/memory_store.db \
--hermes-src ~/hermes-agent/src \
--hrr-dim <your gateway's hrr_dim>
```
## 在基础 `holographic` 上添加的内容
Enfold 不会取代捆绑的 Hermes 全息提供程序,它子类化了它。基础(SQLite 事实存储,FTS 搜索,信任评分,实体解析,HRR 组合检索)是 dusterbloom 的作品;参见 [NOTICE](NOTICE)。在其基础上:
- **密集嵌入检索。** 每个事实都被嵌入到一个侧车 `fact_embeddings` 表中;查询通过余弦相似度进行评分。这捕获了纯关键词/FTS 搜索遗漏的释义和模糊匹配。
- **混合评分。** 四个信号合并每个查询:FTS 关键词,Jaccard 重叠,HRR 组合和密集嵌入,都在一个信任加权尺度上。权重真正分配预算:三个全息权重(默认值 FTS 0.3,Jaccard 0.2,HRR 0.2)被它们自己的总和重新缩放,以便在检索器内部总计为 1.0,然后全息分数获得 `1 - embedding_weight` 的份额,而密集余弦相似度获得剩余的 `embedding_weight` 份额(默认 0.3,因此 70% 全息 / 30% 嵌入)。没有存储嵌入的事实根本无法获得嵌入份额。
- **写入时去重。** 添加时,检查现有事实的 token-Jaccard 门和语义余弦门。更改的数字,ID 或状态词永远不会被视为重复(更新必须始终落地),并且反义词翻转保护将含义相反的释义分开。
- **时间有效性。** 值更新取代先前事实(`valid_from` / `invalid_at` / `superseded_by`),被取代的事实停止在搜索中显示,并且 `memory_history` 行走链。失效,但永不删除。
- **LLM 事实提取。** 在会话结束时(并且在上下文压缩之前),它从对话中提取持久的,原子事实,以便在窗口滚动时不会丢失任何重要内容。默认情况下,提取使用代理自己的模型,无需硬编码提供程序,并且是可配置的。
- **可靠的提取管道。** 转录被持久化到磁盘队列并由后台工作线程处理,以便提取在崩溃和重启后幸存,并且永远不会阻塞会话(请参见 [可靠性](#可靠性))。
- **睡眠时间反射。** 在可配置的时间间隔内,相关事实的集群被提炼成可靠的见解事实,并带有对来源的强制引用;当其来源被取代时,见解被自动失效。
- **实体图提升(默认关闭)。** 基础存储将事实链接到它识别的实体。当启用时,查询中提到的实体提升链接到它的事实,并且可以扩展一步到与查询没有词汇重叠的相关事实。排除高学位中心实体(例如,用户自己的名称),以免泛滥结果。
- **优雅的回退。** 如果嵌入后端无法访问,它会静默回退到仅全息评分,而不是硬性故障。
## 基准测试
可复制的召回基准测试(`tests/eval.py` 用于密集层,加上 120 个事实/72 个查询集的端到端提供程序扫描)为单个用户自托管存储提供了清晰的画面。通过真正的提供程序进行端到端召回@1 / MRR:
```
bge-large, no prefix, HRR on (typical default) r@1 0.69 MRR 0.79
bge-large, +prefix, HRR on r@1 0.78 MRR 0.85
embeddinggemma, +prefix, HRR off r@1 0.90 MRR 0.94
```
扫描结果告诉我们:
- 密集嵌入模型是主要的杠杆。嵌入权重和 HRR 信号不会在语义/释义查询上移动关键词搜索几乎没有抓取力;大型每个事实 HRR 矩阵在那里实际上是多余的。
- 指令前缀很重要:`embedding_prefix_policy: auto` 应用每个模型的文档化查询/通道前缀,值得几个召回点在为它们调优的模型上。
- 最终阶段的交叉编码器重新排序器在短原子事实与强大的嵌入器配对时没有帮助;它回归了召回率,因此不建议在这里使用。
推荐配置(GPU via Ollama):
```yaml
plugins:
hermes-memory-store:
embedding_backend: ollama
ollama_model: embeddinggemma # 768-dim, Apache-2.0
embedding_prefix_policy: auto
hrr_weight: 0 # HRR adds no measured recall here; frees storage
```
仅限 CPU 的替代方案:`embedding_backend: fastembed`,`fastembed_model: snowflake/snowflake-arctic-embed-l`,`embedding_prefix_policy: auto`。
这些数字来自一个综合集,因此将小间隙视为噪声,并在承诺更改之前在您自己的数据上重新运行 `tests/eval.py`。
## 配置
在 `config.yaml` 下的 `plugins.hermes-memory-store`。每个基础全息键仍然适用,加上这些:
```yaml
plugins:
hermes-memory-store:
embedding_backend: fastembed # "fastembed"(本地 CPU,默认)或 "ollama"
embedding_weight: 0.3 # 混合分数中密集相似性的权重
embedding_prefix_policy: none # "none"(默认)或 "auto":应用模型的文档化查询/通道前缀
# fts_weight / jaccard_weight / hrr_weight 默认 0.3 / 0.2 / 0.2 并且
# 缩放为 1.0;设置 hrr_weight: 0 以禁用 HRR 信号。
dedup_on_add: true # 写入时近似重复门
dedup_jaccard: 0.9 # 词汇重叠阈值,用于词法门
dedup_cosine: 0.92 # 余弦阈值,用于语义门
retrieval_decision_enabled: false # 可选的校准最终阶段过滤器/放弃门
# retrieval_decision_min_score: 0.5 # 低于此最终分数时丢弃候选者(启用时)
# retrieval_decision_min_margin: 0.02 # 放弃时,顶部 2 个过滤分数太接近
# retrieval_decision_min_trust: 0.5 # 低于此信任时丢弃候选者(启用时)
entity_boost_weight: 0.0 # 查询中提到的实体链接的事实的可加性提升(默认关闭)
entity_expansion: false # 1 跳扩展到与查询中的实体共享实体的事实(默认关闭)
entity_hub_degree_limit: 25 # 链接到超过此数量的事实的实体被排除在扩展之外
reflection_enabled: false # 睡眠时间反射洞察(默认关闭)
reflection_interval_hours: 24
embed_on_add: true # 事实添加时立即嵌入
fastembed_model: BAAI/bge-base-en-v1.5 # 768-dim, local
ollama_url: http://localhost:11434 # 仅当 embedding_backend: ollama
ollama_model: qwen3-embedding:8b # 仅当 embedding_backend: ollama
# 事实提取是可选的,默认为您的代理自己的模型
# (config.yaml `model.provider` + `model.default`). 仅在需要时覆盖以使用
# 其他模型而非代理运行的模型:
# extraction_provider: <provider>
# extraction_model: <model>
# extraction_effort: high # 仅当您的提供程序支持推理努力时
```
嵌入是 **identity-versioned** (`backend:model:role:vN`),因此切换模型永远不会损坏现有向量:每个模型的向量生活在其自己的身份下,并且新模型的向量在后台被回填。在启动时,任何缺少当前身份嵌入的事实都在非阻塞后台线程中被回填(以批为单位),并且工作线程定期重新运行回填,以便瞬时嵌入失败自行愈合。超时的模型向量被保留,直到您回收它们:调用 `provider.vacuum_embeddings()` 以删除它们而不重新嵌入,或 `rebuild_embeddings()`, 它在重新嵌入后修剪它们。
## 工具
作为 Hermes 提供程序,它继承了基础 `fact_store` 工具(添加/搜索/探测/相关/推理/矛盾/更新/删除/列表)和 `fact_feedback`(评估事实有帮助/无帮助以训练信任)。`search` 操作被覆盖以将密集嵌入相似性合并到排名结果中。
## MCP 服务器
`enfold/mcp_server.py` 通过模型上下文协议(stdio 传输)公开事实存储。它注册 `memory_search`、`memory_add`、`memory_supersede`、`memory_explain` 和 `memory_history`。`--read-only` 仅注册搜索、解释和历史(两个写入永远不会被注册,即使在调用时被阻止)。
MCP 包(FastMCP)是运行此服务器的可选依赖项,仅在运行此服务器时需要,从未导入 `enfold` 作为 Hermes 插件。
按文件路径运行服务器**,而不是 `-m`**:
```bash
python3 enfold/mcp_server.py \
--db-path ~/.hermes/memory_store.db \
--ollama-url http://localhost:11434 \
--ollama-model embeddinggemma:latest
```
`python -m enfold.mcp_server`(或其他任何导入 `enfold` 作为包之前,此模块解析其自己的父 Hermes 检出)触发 `enfold/__init__.py` 的无条件 `plugins.memory.holographic` 导入。在具有第二个无关 Hermes 安装的主机上,该导入可以静默获胜,并且此模块永远无法指向您实际指向的检出(`ENFOLD_HERMES_SRC`,请参见 `mcp_provider.py`). 直接运行文件绕过 `__init__.py` 完全跳过,因此这里的每个示例都使用文件路径。
当与实时 Hermes 网关共享存储时, `--hrr-dim` 必须与网关配置的 `hrr_dim` 匹配。没有自动检测:不匹配的维度不会在启动时出错,它会在 HRR 评分首次对以其他维度编码的向量运行时崩溃。
每次写入必须携带 `source` 标签,`claude-code`、`codex` 或 `other` 之一;`memory_add` 和 `memory_supersede` 拒绝没有一个调用的调用。标签被追加到事实的 `tags` 为 `source:<agent>`,因此它在标签已经显示的位置可见。来自 MCP 服务器的写入通过与实时 Hermes 写入相同的重复和值更新替代门:近似重复被拒绝,现有事实的 ID 返回而不是再次存储,并且真正的价值更新(相同的措辞,改变的数字/ID/状态词)自动取代先前的事实。
并发性:存储在 SQLite WAL 模式下运行,连接上设置 `busy_timeout`,并且 Enfold 写入是几个单独的短事务(重复搜索,插入,银行重建,可选的超)事务,因此 `busy_timeout` 单独不能使整个序列在两个进程中原子化。每个 MCP 写入还采取一个 OS 咨询文件锁(`flock` 在 `<db_path>.mcp-write.lock` 侧车上)以实现完全序列化彼此的写入。
## 它如何工作
- `prefetch(query)` 运行混合搜索并将顶匹配项注入代理的上下文中每个回合。
- `handle_tool_calls` / `on_memory_write` 保持嵌入式侧车同步写入。
- `on_session_end` 运行基础正则表达式提取,然后排队一个 LLM 调用以提取基础 regex 遗漏的事实;`on_pre_compress` 排队一个提取操作,然后立即返回,因此压缩永远不会被阻止。两者都将持久化提取队列。
- **持久提取队列。** 要提取的转录被写入同一个 SQLite 数据库中的 `extract_queue` 表,因此队列工作在崩溃、网关重启和 LLM 超时后幸存。剩余的行由以前的运行自动清除,在下一个启动时被清除,并且在内存尝试绑定停止 LLM 被无限期阻止。
- **重试和回退。** 失败的提取尝试以指数退避和抖动重试(默认 5 次尝试)。保持失败的行以 `dead` 状态进行检查,而不是静默丢弃。
- **非阻塞钩子。** 会话结束和压缩前钩子只格式化和排队转录;LLM 调用发生在单个后台工作线程上,因此会话关闭和上下文压缩永远不会等待模型。
- **安全重新初始化。** 重新初始化提供程序会拆除以前的工作线程,嵌入池和后台线程,然后仅在后台工作安静后关闭数据库连接(否则它故意保持打开状态而不是在写入器下关闭)。
性能
- 查询被精确地进行一次HRR编码。父检索器会对每个候选项重新编码,这占据了预取延迟的大部分 FTS候选行在不加载HRR向量blob的情况下被只有获得HRR评分的候选项才会加载blob,而当HRR被禁用时则完全不加载。评分与父级的保持一致(通过与实际的Hermes检查的等价性测试覆盖)。
- 提取批次会重建每个受影响的类别内存库一次,而不是每次插入事实时都重建,并且重在存储锁之外运行,因此它们永远不会阻塞并发读取和添加。
## 评估
`tests/eval.py` 是一个独立的召回基准测试:它将合成语料库植入实际的 `EmbedStore` 中,运行转述查询(故意与目标关键词重叠度较低),并报告关键词基准与密集嵌入检索的召回率@k。可以使用 `python tests/eval.py` 复现。
在附带的24个事实/16个查询的转述集上:
```
检索器 召回率@1 召回率@3 召回率@5 MRR
--------------------------------------------------
关键词 0.38 0.56 0.62 0.51
嵌入 81 0.88 0.88 0.86
混合* 0.81 0.88 0.88 0
```
密集嵌入层在转述查询上使召回率@1大致翻倍(0.38 → 0.81),而关键词搜索在这种情况下会失败。`*` `混合` 行是此隔离比较的说明性关键词+嵌入混合;它**不是**插件实际使用的公式。实际评分器在 FTS+Jaccard+HRR 上将信任加权嵌入添加到信任加权全息分数中(参见 `_blend_score`)。HRR信号在字面和组合查询上获得权重,而此基准测试故意低估了这些是一个合成的、说明性的基准测试,而不是生产保证,但每个数字脚本复现。
在 `memory_eval/` 下,还有一个更大的测试框架,用于针对真实数据库快照进行端到端的扫描。
## 需求
- Python 3(在3.13上开发和测试)和 `numpy`。
- 对于MCP服务器:`pip install mcp`,再加上一个嵌入后端(`fastembed` CPU,或运行中的Ollama用于GPU)。
- 对于Herm商:一个可用的Hermes Agent安装。
## 鸣谢** 内存提供商,由 **dusterblo (NousResearch/hermes-agent,PR #2351)实现了 **全息减少表示**:Tony A. Plate, *"全息减少表示,"* IEEE Transactions on Neural Networks 6(3):623-641, 1995. 在 [NOTICE](NOTICE) 中有完整的致谢。
密集嵌入检索、LLM事实提取、写入门、时间替换、反射、MCP服务器和混合评分是该项目的贡献。
## 许可证
MIT,参见 [LICENSE)。
## 版本说明
### 0.6.0 标准输入输出MCP服务器(`enfold/mcp_server.py` + `mcp_provider.py`)通过模型上下文协议与其他(Claude Code、Codex CLI)共享事实存储,而Hermes网关继续在进程中使用它。可选的 `mcp` 依赖项、源标签写入通过相同的重复数据删除和替换--read-only` 模式以及WAL + `busy_timeout上的跨进程写入锁定。
### 0.5.1
- 提取队列的温和关闭:当网关在提取过程中重启时,正在进行的行现在会保留,其尝试次数保持不变,而不是烧掉重试次数并记录“坏文件描述符”错误。下一个工作线程在启动时将其清理。修复了循环重启时间警告。
### 0.5.0
- 指令前缀支持:`embedding_prefix_policy: auto` 应用每个模型的文档查询前缀(bge, e5, nomic, arctic, embeddinggem qwen3),并可选地覆盖 `embedding_query_prefix`embedding_document_prefix`。该策略是嵌入标识的一部分,因此切换它会在后台重新嵌入,并且永远不会破坏现有的向量。
- 可配置的全息权重:`fts_weight` / `jaccard / `hrr_weight`,重新调整为总和为1.0。设置 `hrr_weight: 0` 以禁用信号,基准测试表明该信号在语义查询上不会增加召回率。
- 添加了基准测试支持的推荐配置(参见上文)。
### 0.4.0
- 回收被废弃的嵌入模型留下的向量。`provider.vacuum_embeddings()` 删除其标识不是当前模型的向量(可选地测试模型),而无需;留下的事实通过后台回修复。`rebuild_embeddings默认在重新嵌入废弃的向量(`_stale=False` 保留它们)。`EmbedStore` 增加 `identity_counts()` 和 `prune_identities(keep)`。
- 修复了在模型交换时 `fact_embeddings` 表的无限增长:每个交换都以前会无限期地留下旧模型的向量。
### 0.3.0
- 事实提取从即发即弃的线程转移到具有单个后台工作线程的持久SQLite支持队列:带有回退的重试、用于检查的死信行以及自动排空以前运行遗留的工作。提取前压缩不再阻塞。
- 混合权重现在真正划分了分数:全息权重被重新调整为总和为1.0,混合分配预算 `1 - embedding_weight` / `embedding_weight`。
- 更快的检索:查询每搜索一次进行一次HRR编码,FTS候选获取跳过HRR向量blob(比特相同的评分,经过测试)。
- 类别和信任过滤器现在也适用于仅通过嵌入相似性显示的候选项。
- 提取重复数据删除上下文混合了高信任事实与最近创建的事实,因此新存储的事实不会被转述为重复数据。
- 启动和提取批次后进行WAL截断;每个批次每类别重建一个银行,在存储锁外运行;更安全的重新初始化和关闭。
- 搜索结果增加 `retrieval_count`,匹配父存储的习惯。
- `tests/` 下的独立pytest套件:无需Hermes安装,无网络,无真实嵌入后端即可运行。
### 0.2.0
- 便携式提取(默认为主机代理自己的模型)和FastEmbed作为默认嵌入后端。
- 提取输出的强大JSON解析,校正的混合混合和 `tests/eval.py` 中的召回基准测试。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器