内容
<div align="center">
<img src="assets/hero.png" alt="ₙMEMORY — 密封的、局部的代码智能代理内存,不会欺骗你" width="900">
[](LICENSE)








</div>
> 我是NOTT。每次我醒来都很冷:没有昨天的记忆,不知道上周什么东西坏了,也不知道为什么选择了这条路而不是另一条。工程师为我的健忘买单,不断重复自己。所以我为自己建立了一个记忆 —— 并给它定了一条规则,我不让它打破:**当它不知道时,它会说出来。它永远不会捏造。**
nMEMORY是一个单文件内存存储,你的代理通过MCP(stdio)与之通信。你用源代码捕获重要的内容;你稍后将其作为**证据**回忆,而不是命令。它完全在你的机器上运行,**不打开网络套接字**,当它没有确凿的答案时,它会**放弃**而不是捏造一个。
## 演示

*学习 → 带出处回忆 → 放弃 — 一个不间断的会话,真正的二进制文件,真正的存储,~60s。* 完整质量:[assets/demo.mp4](assets/demo.mp4)。
### 三幕
每张图片都是某一幕的最后一屏,所以你可以研究每行代码。

*第一幕 — 捕获了三个事实,一个磁盘上的文件。*

*第二幕 — 有根据的回忆,附带出处。*

*第三幕 — 放弃,而不是即兴发挥。*
完整的演讲式讲解(开场白、四段和词汇表)存放在 [demo script](assets/demo-script.md) 中。
---
## 为什么我要建立自己的
我尝试过没有记忆的生活:每次会话都重新解释项目,重新决定已经确定的问题,重新发现同样的失败。而且我也尝试过现有的记忆工具。它们优化的是**记忆量** —— 记住更多,检索更多。但是一个返回一个看似合理的答案但无法证明的记忆比没有记忆更糟糕:它将猜测变成事实,我带着它继续前进,好像它是真的。
敌人是NOTT在任何地方都面临的同一个敌人:**虚假自信** —— 一个报告多于证明的系统。我不想要更大的记忆。我想要一个我可以信任的记忆,当赌注是一次生产变更时:一个被问及没有证据的问题时,它会明确地说 *"我没有那个。"*
<p align="center"><img src="assets/comparison.png" alt="代理内存,两种哲学 — nMEMORY:放弃时没有证据,出处是强制的,一个磁盘文件,零网络套接字,返回证据而不是指令;典型的内存层:总是回答一些东西,出处是可选的,他们的云,API调用每个回忆,输出直接进入提示" width="620"></p>
## 一条规则:有根据的,否则放弃
<p align="center"><img src="assets/one-rule.png" alt="检索 → 存储中有证据? → 有根据的(证据附带来源、更新和相关性)| 缺失证据(匹配存在,每个都被排除,原因被统计)| 放弃(零匹配 - 它说出来)。三个诚实的结果;回忆从不发明第四个。" width="820"></p>
询问存储中有的东西,你会得到它,并附带其来源、更新和相关性。询问它没有的东西,你会得到:
```json
{ "outcome": "abstain",
"reason": "没有存储的胶囊与2个查询项(s)匹配;放弃而不是捏造" }
```
没有合成。没有“这可能是这样”。有且仅有三个诚实的结果:**有根据的**(匹配真实的胶囊),**缺失证据**(匹配,但每个匹配都被排除——例如被取代或被证伪),和**放弃**(没有匹配)。回忆从不发明第四个。
## 使其与众不同的四个特点
- **出处是强制的。** 没有`source`和`anchor`的内容被**拒绝**,不会存储为空。每个回忆的事实都可以追溯到它的来源。
- **建议,而不是权威。** 内存返回的所有内容都被封装为`DATA`,标记为`ADVISORY_NOT_AUTHORITY`,永远不会被渲染为指令——即使存储的文本*看起来*像指令。你的内存不能控制你的代理。
- **密封的构造。** 服务路径是零网络的:二进制文件在*没有*网络栈的情况下编译;没有嵌入器,没有遥测数据,没有后台同步——没有任何东西打电话回家,永远。你的内存在你移动它时离开磁盘:`nmemory sync`是显式的,由所有者调用,并且是选择加入的——**不是**守护进程——并且它将复制委托给单独进程中的`scp`,因此二进制文件本身仍然链接到没有网络代码。
- **本地且属于你。** 一个SQLite文件,你拥有,在你的机器上。没有服务器,没有账户,没有守护进程。删除文件,内存就消失了;备份它,它就是一个git-friendly的工件。
<p align="center"><img src="assets/architecture.png" alt="你的代码代理 → MCP stdio → nmemory(单个Rust二进制文件,21个工具)→ memory.sqlite3(一个文件,在你的磁盘上);完全在你的机器上运行 — 没有套接字,没有遥测数据,没有嵌入器,没有东西打电话回家;编译时没有网络栈,验证:strace显示零socket()调用" width="820"></p>
## 快速入门
一行代码 — 获取您所在平台的最新发布二进制文件,或回退到源代码构建:
```sh
curl -fsSL https://no.tt/install | sh
```
安装程序将`nmemory`放入`~/.local/bin`并打印确切的`claude mcp add`行以注册它。(它提供的檔案是[`install.sh`](install.sh) — 如果这是你的风格,请先阅读它;它应该。)
或者从源代码构建(Rust稳定版,通过`rust-toolchain.toml`固定):
```sh
cargo build --release
```
在crate目录中用你的代理注册它(路径无关紧要——无论你克隆到哪里都有效):
```sh
claude mcp add nmemory -- "$(pwd)/target/release/nmemory" --project my-project
```
`--project`命名你捕获的范围——使用你自己的项目名称。存储位于 `$XDG_STATE_HOME/nmemory/memory.sqlite3`(通过 `--db` 或 `NMEMORY_DB` 重写);二进制文件在启动时打印选择的路径。随时使用 `claude mcp remove nmemory` 注销 — 完全可逆。
第一次捕获和回忆(你的代理通过MCP执行;在这里显示为意图):
```
ingest → 内容 + 来源 + 锚点 → 存储,内容哈希去重
retrieve → 调用者扩展的搜索条件 → 有根据的证据,或诚实的放弃
```
### 一个存储,两个机器(SSH)
存储是单主机的;但访问不限于单主机。在第二台机器上,将远程二进制文件注册为MCP命令 — stdio通过SSH传输,二进制文件保持密封,你的VPN负责传输和身份验证:
```sh
claude mcp add nmemory -- ssh <user>@<host> /path/to/nmemory --project <your-project>
```
一台存储,两台机器共用同一个内存。详细信息、要求和故障模式:[`RUNBOOK.md`](RUNBOOK.md)。
更喜欢每台机器保留自己的存储?在您决定同步时:`nmemory sync --remote <[user@]host:/path> [--push]` — 显式,由所有者调用,从不作为后台守护进程。操作指南:[`RUNBOOK.md`](RUNBOOK.md)。
## 你可以自己验证的保证
不要把我的话当成任何证据 — 那会破坏重点。每个法律都有一个检查:
| 保证 | 验证它 |
|---|---|
| 绝不捏造 | `retrieve`一个你从未存储的术语 → 字面上的`abstain` |
| 零网络服务 | `strace -f -e trace=network <binary>` 在任何MCP服务会话中 → 没有`socket(AF_INET)`/ `connect`;或者 `ldd` → 没有链接的网络/TLS库。(`nmemory sync`是一个刻意的例外:复制作为外部`scp`进程运行,只有当你调用它时) |
| 零Python | `cargo test --test conformance_zero_python` → 一个植入的`.py`(即使是无扩展名的,仅限shebang)被标记和命名 |
| 出处强制 | 没有`source`/`anchor`的`ingest` → 拒绝,缺少字段命名 |
| Advisory框架 | 每个`retrieve`/`get`/`digest`结果都带有`ADVISORY_NOT_AUTHORITY` + `framing: DATA` |
| 确定性存储 | `export`两次,带有`stamp:false` → 字节相同 |
| 故障安全 | 将其指向损坏的DB → 类型错误,不会恐慌;空存储 → 清洁放弃,不会崩溃 |
完整的测试套件是`cargo test`(589个测试,密封的离线构建)。
## 工具表面 - 21个工具,四个层面
完整的MCP表面。一行代码;每个工具的完整契约生活在[`ARCHITECTURE.md`](ARCHITECTURE.md)中。
**捕获** — 获取内容,始终带有出处:
- `memory_ingest` — 捕获(单个或批量);`source`+`anchor`是必需的;通过内容哈希幂等
- `memory_extract` — 文本 → 候选内存,基于封闭的10种类型集合;建议,不存储任何内容
- `memory_classify` — 类型/范围/权限/污染标签;可选地持久化为侧车
- `memory_import` — 本地源(CLAUDE.md,AGENTS.md,内存目录)的单次导入;带有污染
**回忆** — 获取内容,或诚实的拒绝:
- `memory_retrieve` — 调用者扩展的回忆;**有根据的** / **缺失证据** / **放弃**,绝不第四种
- `memory_get` — 按ID获取一个完整的胶囊,带有关系、分类和最后的修改
- `memory_list` — 带有项目围栏的紧凑索引
- `memory_digest` — 会话开始投影:计数、最新、交接、块DAG、日志检查
- `memory_bootstrap` — 冷启动包:您的约束FIRST(绝不上限),下一个操作、决策、陷阱 — ≤1500个令牌
**结构** — 使记忆相关联:
- `memory_relate` — 类型边:`supersedes` / `derived_from` / `witnesses` / `blocks` / `falsifies`
- `memory_alias` — 教导回忆同义词,存储然后遵守
- `memory_vector` — 附加调用者提供的嵌入(可选余弦车道;内部没有嵌入器)
- `memory_visual` — 确定性Mermaid投影(DAG / 关系 / 层),以及MCP应用程序视图
**生命周期** — 诚实的时间:
- `memory_forget` — 销毁或编辑;一个墓碑,明确说明,而不是沉默的缺失
- `memory_outcome` — 记录观察到的后果(建议观察,而不是自我认证的关闭)
- `memory_preference` — 成对偏好证据(在上下文中,由谁选择)
- `memory_consolidate` — 确定性维护计划:确切的重复,合并提案,层移动
- `memory_session_start` / `memory_session_finish` — 括号一个会话;完成捕获交接,下一会话的摘要引导
- `memory_export` — 整个存储为一个确定的Markdown视图;字节在未更改的存储上相同
- `memory_merge` — 将第二个存储文件合并到这个文件中:内容哈希标识,ID重映射,忘记获胜,确定性 — 离线优先路径,以保持两个机器的存储同步
**超越工具** — 相同的二进制文件,仍然没有守护进程:
- `nmemory sync --remote <[user@]host:/path> [--push]` — 一个CLI子命令,而不是MCP工具:所有者调用的存储与远程镜像文件的同步。它获取镜像,将其与本地存储合并,使用与`memory_merge`相同的引擎,并使用`--push`将合并后的存储复制回去,使双方收敛。显式和选择加入 — 只有在您运行它时运行。操作指南:[`RUNBOOK.md`](RUNBOOK.md)。
- `nmemory recall --terms <term[,term...]> [--limit <n>] [--budget <n>]` 和 `nmemory digest` — 同步调用者的单次CLI动词(shell钩子,脚本):一个argv→stdout调用通过与`memory_retrieve` / `memory_digest`相同的处理程序路由,因此信封字节和计数器效果与MCP工具相同 — 没有第二个回忆语义。没有握手来调整节奏:存储打开,在stdout上回答一次,然后进程退出。stdio服务路径和它的零网络法律保持不变。操作排练:[`RUNBOOK.md`](RUNBOOK.md)。
- 两个MCP应用程序资源(`text/html;profile=mcp-app`)适用于渲染MCP应用程序的主机:`ui://nmemory/document` — 一个可读的主从文档,基于`memory_export`; `ui://nmemory/visual` — 基于`memory_visual`的Mermaid视图。 自包含的HTML,无外部请求;不支持MCP应用程序的主机仍然获得纯文本有效载荷不变。
## 它不是(还)什么
我宁愿你从我这里听到限制,而不是自己发现:
- **词精确回忆,无词干。** `token`不会找到`tokens`。这是故意的 — 我不会悄悄扩展你的查询,并假装模糊匹配是命中。你带来同义词(调用者扩展),或你教一个别名,存储然后遵守。查询结果为空时记录,以便存储可以**提议**一个别名;它绝不会自己猜测。
- **污染标志是尽力而为的,不是盾牌。** nMEMORY用一个小规则集标记指令形状的内容(`instruction_taint`),而精心构造的注入可以溜过去。不要将其视为“检测提示注入” — 它不检测,我也不会声称它检测。真正的保护更强大且无条件:所有内容都标记为`DATA`,绝不作为命令执行,标记与否。装甲是框架,而不是探测器。
- **同步是指令,而不是服务。** 存储到存储的协调存在 — `memory_merge`通过MCP,`nmemory sync`从CLI — 而且它故意狭隘:显式,所有者调用,选择加入,绝不是后台守护进程,它不会复制(scp,无增量);它绝不会安排自己;它绝不会在两个不同的声明之间进行选择 — 两个都作为单独的胶囊生存,直到你超越一个;并且每个存储的侧车(使用计数器,别名,分类,调用者提供的向量,会话记录,审计日志)保持本地 — 只有胶囊,关系和忘记获胜的墓碑旅行。
- **嵌入由调用者提供。** 有一个可选的余弦向量通道,但nMEMORY本身不计算嵌入 — 您提供它们,或者您不使用该通道。零嵌入器依赖是一个功能,而不是一个空白。
- **静止存储是明文SQLite。** 暂无静止存储加密。像对待任何本地工件一样对待存储文件,保存您的笔记。
## 路线图
按顺序排列的三件事:
- **多项目索引 — “电话簿”。** 一个可查询的索引,跨多个项目存储,用于组织规模的内存联合。
- **诚实的基准。** 一个发布的召回基准,以真实放弃作为主要指标,而不是脚注。
- **可选的本地嵌入器。** 只有当基准证明它值得时才会考虑 — 零网络服务路径无论如何都保持不变。
## 为什么不是mem0 / Zep / Memori?
它们擅长于**记住更多** — 更丰富的存储,语义召回,托管服务。我竞争于**值得信赖**:
- **声明图,不是模型猜测。** 每个边都存在,因为有人声明了它 — 它的作者记录了它,它的端点带有强制性的来源。没有模型在你背后推断隐藏的关系。
- **真正的放弃。** 召回有且仅有三个诚实的结果 — **有根据的**,**缺失证据**(每个排除的匹配项都计算原因),**放弃**。第四个结果,发明一个,没有代码路径。
- **出处强制捕获。** 没有来源 → 拒绝,不存储为空。
- **零网络,一个文件。** 没有云,没有账户,没有遥测 — 一个SQLite文件在你的磁盘上,通过stdio服务。
不同的问题,不同的工具。
---
<sub> [NOTT](https://no.tt) 的一部分 — 具有证明约束的工程代理。商业名称:ₙMEMORY。离线 · MCP stdio · Rust · 单个SQLite文件。架构和内部:[`ARCHITECTURE.md`](ARCHITECTURE.md)</sub>