内容
# mdmem
本地基于markdown的代理记忆管理系统。
mdmem是用于代理的记忆系统。记忆以纯markdown文件存储在磁盘上,MCP服务器允许代理写入和搜索它们。SQLite向量索引(sqlite-vec)提供语义搜索;它源自文件,可以随时删除和重建。
- markdown文件是存储。它们可以像其他文件一样读取、编辑、版本化和搜索。
- 嵌入式计算在进程中与transformers.js(`bge-small-en-v1.5`,~34MB,一次下载并缓存)一起进行。之后的所有操作都在本地运行。
- TypeScript,直接由Node(≥ 22.18)执行,无需构建步骤。
免责声明:mdmem是实验性的,不保证稳定。目前仅用于测试和本地开发。
## 入门指南
在全新系统上启动,克隆仓库,安装依赖并运行`init`:
```sh
git clone https://github.com/n8eloy/mdmem && cd mdmem
pnpm install
node src/cli.ts init
```
与Claude Code结合使用时,将MCP服务器添加到您的`claude-mcp.json`:
```sh
claude mcp add --scope user mdmem -- node /path/to/mdmem/src/server.ts
```
`init`创建`~/.mdmem/`,包含配置文件和空存储。从第一个消息开始,代理可以使用`memory_write`保存记忆,并使用`memory_search`回忆它们。相同的存储可以从终端搜索:
```sh
node src/cli.ts search "我们决定了什么关于api重构"
```
对于其他AI客户端,相当于以下MCP配置:
```json
{
"mcpServers": {
"mdmem": {
"command": "node",
"args": ["/path/to/mdmem/src/server.ts"]
}
}
}
```
## 迁移现有的markdown
可以随时导入现有的笔记。它们在原地注册和索引,因此不会移动或复制任何内容:
```sh
node src/cli.ts add ~/notes ~/journal
```
注册目录下的每个`*.md`都会递归索引(跳过点目录),并与存储一起可搜索。
## 命令行界面
| 命令 | 效果 |
| ----------------------- | ---------------------------------------------------------------- |
| `init [--store <dir>]` | 创建`~/.mdmem/`(配置、存储、索引)。幂等。 |
| `add <dir...>` | 在原地注册现有的markdown目录并索引它们。 |
| `index` | 增量重新索引存储和所有注册目录。 |
| `search <query> [-k N]` | 打印排名靠前的结果(分数、id、路径)。 |
| `list` | 打印存储路径、注册目录、数据库路径、块计数。 |
## MCP工具
| 工具 | 输入 | 返回 |
| ---------------- | ------------------------------------------------- | -------------------------------------------------------- |
| `memory_search` | `query`, `k?`, `type?`, `topic?` | top-k `{id, path, type, topic, heading, score, excerpt}` |
| `memory_get` | `id` | 完整的文件内容 |
| `memory_write` | `topic`, `kind: state \| log`, `content`, `slug?` | `{id, type, path, replaced}` |
| `memory_reindex` | _none_ | 增量重索引统计 |
`memory_write`创建存储中的纯markdown,带有生成的frontmatter,并立即索引:
- `kind: state`写入`<store>/<topic>/state.md`,替换以前的内容。每个主题的文件持有其当前状态。
- `kind: log`创建`<store>/<topic>/log/<date>-<slug>.md`。日志条目永远不会被覆盖;名称冲突会得到数字后缀。
以这种方式编写的文件仍然是普通的markdown,可以在mdmem外部编辑或删除;下一次重索引会发现更改。
## 配置
`mdmem init`写入`~/.mdmem/config.json`(位置可以通过`MDMEM_HOME`环境变量更改):
```json
{
"store": "/home/user/.mdmem/store",
"roots": ["/home/user/notes"]
}
```
搜索的语料库是存储加上注册的根目录,索引位于`<home>/index.db`。
`--roots`和`--db`标志(环境变量:`MDMEM_ROOTS`,`MDMEM_DB`)绕过配置并直接定义语料库。此模式是只读的:当未配置存储时,`memory_write`拒绝执行。
## Frontmatter
`memory_write`编写的文件自动获取frontmatter。对于注册的外部文件,这是可选的:当文件带有YAML frontmatter时,`type`和`topic`成为搜索过滤器,`id`成为文件的稳定标识符:
```yaml
---
id: myproj-2026-07-26-ci-migration
type: log
topic: myproj
---
```
## 重索引
`memory_reindex`(或`node src/cli.ts index`)是增量的:未更改的文件通过mtime和内容哈希检测并跳过,已删除文件的条目被删除。
`scripts/reindex-hook.sh`是一个现成的钩子目标,适用于Claude Code。它读取钩子有效载荷,检查写入的文件是否属于语料库,如果是,则重新索引。使用`mdmem init`的配置时,它不需要参数,而`MDMEM_ROOTS`/`MDMEM_DB`覆盖了标志模式的设置:
```json
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"async": true,
"timeout": 120,
"command": "/path/to/mdmem/scripts/reindex-hook.sh"
}
]
}
```
## 注意事项
- 首次运行下载嵌入式模型到Hugging Face缓存目录。之后,不需要网络访问。
- `Xenova/bge-base-en-v1.5` q8不是直接升级:其量化产生近退化嵌入,带有无关文件绑定到相同分数。`bge-small` q8没有这个问题。查询以BGE检索指令从模型卡前缀,段落不是。
- 精确的关键字查找可以通过grep更好地服务。非常模糊的查询也可以在这个模型大小上丢失。
## 相关工具
供参考和研究:
- [sqliteai/sqlite-memory](https://github.com/sqliteai/sqlite-memory) - 带有混合(向量+FTS5)检索、markdown感知块处理和代理间CRDT同步的C SQLite扩展。它具有更多检索功能,但需要构建扩展并为llama.cpp提供GGUF模型。
- [sqliteai/sqlite-rag](https://github.com/sqliteai/sqlite-rag) - 用于多种文档格式的混合搜索的Python CLI。不包括MCP服务器。
- [memweave](https://towardsdatascience.com/memweave-zero-infra-ai-agent-memory-with-markdown-and-sqlite-no-vector-database-required/) - 基于markdown和SQLite向量的Python内存。没有MCP服务器。
## 许可证
MIT