内容
# engram
一个 [engram](https://en.wikipedia.org/wiki/Engram_(neuropsychology)) 是记忆在大脑中留下的物理痕迹。这里的 engram 是一个**无缝的代码上下文记忆层**,适用于代码智能代理:项目上下文被自动捕获和回忆 —— 无需“记住”提示 —— 通过 Claude Code 钩子和 MCP 工具,存活于上下文窗口压缩,并且每个项目都相互隔离。
**100% 本地化。仅限 SQLite。无需 LLM。** 调用代理(Claude)已经是一个前沿模型,因此它提供结构化的提取;engram 的任务是持久的、版本化的、可搜索的存储。搜索仅需安装 Python(SQLite FTS5 / BM25)即可工作;本地 [Ollama](https://ollama.com) 嵌入模型是一种可选的升级,它将搜索转变为混合(关键词 + 语义,互惠排名融合)—— 并且响应始终报告实际服务的模式。
## 为什么
代码智能代理会忘记。会话中辛苦获得的上下文在上下文窗口边界处会丢失:压缩将其挤出,新会话从冷启动开始,而“重新阅读代码库”无法恢复**决策**(“我们选择 flock 而不是 lockfile,因为…”)或**陷阱**(“沙箱无法在挂载上写入 SQLite”)。普通的 RAG 记忆会追加所有内容并且永远不会忘记,因此矛盾会积累并且过时的事实可以被检索到。
engram 修复了两端:
- **自动捕获/回忆** —— 钩子确定性地触发:`SessionStart` 注入预算的上下文摘要,`PreCompact` 在压缩前保存会话提取,`Stop` 保留一个滚动的会话状态笔记。MCP 工具涵盖了刻意的写入(决策、陷阱、事实)。
- **版本化,而非仅追加** —— 双时间解析器 *取代* 过时的事实(旧版本保留在历史中,不在索引中),分层整合会衰减和淘汰冷内容,并且在写入时标记近似重复的内容。
## 架构
服务路径拥有一个 SQLite 文件的代码 —— 无需 Cognee,无需 LLM 堆栈,大约 150 毫秒导入:
| 层 | 文件 | 角色 |
| --- | --- | --- |
| 笔记 | `core/notes.py` | 原始文本笔记(决策/进度/陷阱/参考),按键更新,约毫秒写入。 |
| 索引 | `core/index.py` | 增量 FTS5 关键词索引(始终)+ 向量索引(可选),RRF 混合;`search()` 永远不会引发异常。 |
| 上下文 | `core/context.py` | `build_context(query, token_budget)` —— 预算的当前唯一摘要,自我衡量与原始历史。 |
| 项目 | `core/projects.py` | 按项目的数据集键入(`proj_<slug>_<hash>`);没有服务路径操作是全局的。 |
| 嵌入 | `core/embeddings.py` | 可选的 Ollama 嵌入客户端(标准库 HTTP),60 秒故障冷却断路器。 |
| 解析器 | `core/resolver.py` | 双时间事实版本控制;取代而非覆盖;近似写入保护。 |
| 整合 | `core/consolidation.py` | 热/暖/冷分层,衰减淘汰,确定性遗憾信号。 |
| 重复数据删除 | `core/dedupe.py` | 批量近似重复扫描;仅标记而无需 LLM(从不自动合并)。 |
| MCP 服务器 | `mcp_server/server.py` | FastMCP 标准输入输出:`memory_note` / `memory_write` / `memory_context` / `memory_search` / `memory_stats` / `memory_forget`。 |
| 钩子 | `scripts/hooks/` | SessionStart 注入 / PreCompact 保存 / Stop 滚动状态 —— 轻量导入,故障开放。 |
| 仪表盘 | `dashboard/` | FastAPI + Chart.js;来自真实内存数据的实时图表。 |
旧引擎,仅用于图形演示和基准测试(不在服务路径上):`core/store.py`(Cognee 包装器),`core/ingest.py` / `core/router.py`(本地 LLM 提取级联 —— 现在使用代理不提供三元组时的备用)。
## 测量(真实运行,本仓库数据)
- 笔记写入 **p50 27.5 毫秒 / p95 34.2 毫秒**,带有实时嵌入 —— 与重建所有内容的预先写入路径的 3.2 秒相比(**约 116 倍**)。无 Ollama 底线:p50 约 5 毫秒。(`scripts/measure_pivot.py`)
- MCP 服务器导入 **约 156 毫秒**(以前在 Cognee 启动时需要数秒)。
- **29.6% 的令牌节省**,用于摘要与重放原始历史,测量真实累积数据。诚实的警告:在小规模(≤ ~8 项,无取代修订)时,摘要的 markdown 搭建可能会稍微花费更多 —— 节省来自于流失,指标无论如何都报告自身。
## 快速入门
```bash
uv sync # Python 3.12 虚拟环境 + 依赖
uv run pytest -q -m "not integration" # 快速套件 —— 无 Ollama,无网络
uv run --directory . python -m mcp_server.server # MCP 服务器(标准输入输出)
```
上述命令无需 `.env`、模型或 Ollama。(一个警告:两个旧 Cognee 路径的快速测试会在首次运行的 HuggingFace 标记器上获取一个小模型;之后永远缓存。)服务路径本身从不接触网络。可选的额外功能:
- **语义搜索**:运行 Ollama 并拉取 `nomic-embed-text`;索引自动拾取(待矢量恢复后下一次搜索)。
- **基准测试/图形演示/本地 LLM 备用**:复制 `.env.example .env` 并拉取它命名的模型;`uv run pytest -m integration -q` 演练完整堆栈。
## 将其连接到项目
1. **MCP 工具**:在项目的 `.mcp.json` 中注册服务器,`MEMORY_PROJECT_DIR` 指向该项目 —— 请参阅 [`mcp_server/REGISTRATION.md`](mcp_server/REGISTRATION.md)。
2. **自动层**:将三个钩子添加到项目的 `.claude/settings.json` 中 —— 请参阅 [`docs/HOOKS.md`](docs/HOOKS.md)。
本仓库同时包含这两个(已提交 `.mcp.json` + `.claude/settings.json`)。
## 运行仪表盘
```bash
uv run uvicorn dashboard.app:app --host 127.0.0.1 --port 8010
# → http://127.0.0.1:8010 (图表来自真实内存数据,4 秒轮询)
```
## 设计笔记/诚实
指标被故意标记为它们所代表的内容:
- **始终报告退化,从不沉默。** 搜索响应表示它们所服务的模式(`hybrid` / `keyword` / `unavailable`);上下文摘要报告其实际使用的排名;无法嵌入的写入表示向量待定。
- **令牌计数是文档化的 `chars/4` 估计**(`tiktoken` 的词汇表在首次使用时下载,这会破坏离线保证)。标题比率在任何一致的估计器中都存活下来。
- **报告中的“$ 成本”是指参考云定价,而不是实际支出** —— 所有内容都在本地运行;实际支出为 $0,每个报告都带有 `pricing_note` 说明这一点。
- **钩子设计为故障开放。** 任何错误都以 0 退出。内存永远不会阻塞会话、回合或压缩。代价是钩子故障仅在其标准错误中可见,而不是阻塞会话。
- **重复数据删除在没有裁判的情况下从不自动合并。** 无 LLM 底线标志疑似重复项(`memory_stats.possible_duplicates`)并询问;错误的合并会隐藏事实,标志仅提出问题。
- **本地结构化输出提取(旧备用路径)确实不稳定** —— 在 [PROGRESS.md](PROGRESS.md) 中记录的艰难事实中说明,而不是掩盖。这就是**代理提供的三元组**是主要路径的原因。
参阅 [PROGRESS.md](PROGRESS.md) 获取会话日志、锁定决策和艰难事实;参阅 [SECURITY.md](SECURITY.md) 获取信任模型(本地、单用户、设计时无需身份验证)。