Skip to main content
Glama

Agent Memory Router

基于 MCP 的 Agent 本地记忆路由系统 —— 先检索摘要,再按需拉取原文。 让 Agent 拥有长期记忆,同时不撑爆上下文窗口。

Python MCP License


问题

Agent 做长期项目时,历史信息只有两种存法:

做法

代价

全部塞进上下文

上下文窗口有限,越往后越贵、越容易丢关键信息

不存,每次重新查

同样的坑踩第二遍,同样的方案重新讨论一遍

这个项目提供第三种:把历史压成一张小索引,原文留在磁盘上。 Agent 先用关键词在索引里搜(每次返回几百字节), 确认相关后再按 ID 取那一条的完整原文。

实测:检索 1000 条记录和检索 10 条记录,返回量完全相同 —— 因为搜索只查索引,不碰原文。

Related MCP server: memory_plus

架构

                  ┌────────────────────────────────┐
                  │      Agent (Claude / Cursor)   │
                  └───────────────┬────────────────┘
                                  │ MCP (stdio)
                  ┌───────────────▼────────────────┐
                  │          mcp_server.py         │
                  │ search / read_raw / save / link│
                  └───────────────┬────────────────┘
                                  │
                  ┌───────────────▼────────────────┐
                  │          memory_core.py        │
                  │  唯一一份 schema 与访问逻辑      │
                  └───────┬────────────────┬───────┘
                          │                │
              ┌───────────▼──────┐  ┌──────▼─────────────────┐
              │ data/memory.db   │  │ raw_data/<id>/raw.txt  │
              │ 索引:小、常驻    │  │ 原文:大、按需加载       │
              │ 可全表扫描        │  │ 不进入上下文直到被点名    │
              └──────────────────┘  └────────────────────────┘

              init_db.py  ── 初始化
              cli.py      ── 人用
              mcp_server.py ── Agent 用
                    ↑ 三者共用 memory_core,没有第二套存储逻辑

目录结构

agent-memory-router/
├── memory_core.py              # 存储核心:schema、连接、增删查改
├── mcp_server.py               # MCP Server(4 个工具)
├── cli.py                      # 命令行(8 个子命令)
├── init_db.py                  # 初始化
├── CLAUDE.md                   # Claude Code 强制规则
├── .cursorrules                # Cursor 强制规则
├── .claude/skills/
│   └── memory-manager/
│       └── SKILL.md            # 检索/写入的标准作业流程
├── tests/
│   └── test_memory_core.py     # 29 个离线测试
├── data/memory.db              # 索引(gitignore)
└── raw_data/<id>/raw.txt       # 原文(gitignore)

快速开始

git clone https://github.com/<your-name>/agent-memory-router.git
cd agent-memory-router
pip install -r requirements.txt

python init_db.py --stats          # 建库
python cli.py add --type decision \
  --title "选 SQLite 而非向量库" \
  --keywords "sqlite,检索,本地" \
  --content "背景:……
做法:……
原因:……
遗留:……"

python cli.py search sqlite
python cli.py show D_0001

接入 Claude Code

在项目根目录建 .mcp.json(Windows 请用绝对路径):

{
  "mcpServers": {
    "memory-router": {
      "command": "python",
      "args": ["D:/agent-memory-router/mcp_server.py"]
    }
  }
}

或用命令行注册:

claude mcp add memory-router -- python D:/agent-memory-router/mcp_server.py

接入 Cursor

.cursor/mcp.json 格式相同。规则文件 .cursorrules 已在仓库根目录。

MCP 工具

工具

作用

返回量

search_memory(keyword, type?, limit?)

在索引中检索

摘要 × N(每条约 200 字节)

read_raw_data(record_id, max_chars?)

按 ID 取单条原文

一条原文

save_memory(type, title, content, keywords?, relates_to?)

写入新记忆

新记录 ID

link_memory(source_id, target_id, relation_type)

建立有向关系

关系确认

所有工具返回 JSON 字符串而不是裸文本:模型更容易区分 「摘要」和「原文」,出错时也知道下一步该干什么。

数据模型

CREATE TABLE entities (
    id             TEXT PRIMARY KEY,   -- D_0001 / C_0002,前缀表示类型
    type           TEXT NOT NULL,      -- decision / code_change / issue / ...
    title          TEXT NOT NULL,      -- 一行摘要,检索时唯一可见的内容
    keywords       TEXT NOT NULL,      -- 逗号分隔
    file_path      TEXT NOT NULL,      -- 指向 raw_data/<id>/raw.txt
    created_at     TIMESTAMP NOT NULL,
    update_version INTEGER NOT NULL
);

CREATE TABLE relations (
    source_id     TEXT NOT NULL,
    target_id     TEXT NOT NULL,
    relation_type TEXT NOT NULL,       -- supersedes / caused_by / implements
    PRIMARY KEY (source_id, target_id, relation_type)
);

ID 前缀:D 决策、C 代码修改、I 问题、N 笔记、R 参考资料、S 会话、M 其他。 扫日志时一眼能看出这条是什么。

设计取舍

为什么索引和原文分离? 因为两者访问频率差了两个数量级。索引每次对话都要查,原文可能一个月读一次。 放在一起意味着每次检索都要拖着一堆正文走。

为什么用 SQLite 而不是向量库? 本地零依赖、单文件、可以直接 sqlite3 打开排查。 关键词检索对「记得大概叫什么」的场景已经够用, 而它的失败模式是可解释的(没匹配到就是没匹配到), 不像向量检索会返回一堆"语义相近但无关"的结果,反而浪费上下文。

为什么 ID 用 D_0001 而不是 UUID? UUID 在日志里无法辨认。前缀 + 序号让「哦这是第 3 个决策」变成零成本判断。

安全:record_id 直接来自模型输出,模型可能被提示词注入。 所以有两道防线:

  1. 白名单正则 ^[A-Za-z][A-Za-z0-9_]{0,63}$ —— 挡掉 /、..、空格

  2. Path.is_relative_to() 二次校验 —— 即使将来正则被放宽,也越不出 raw_data/

测试里覆盖了 ../../etc/passwd、D_0001/../../secret、Windows 反斜杠等 8 种变体。

检索通配符必须转义。 不转义的话搜 50% 会变成「匹配一切」,返回全库 —— 结果看起来"很全",其实全错。

每次操作单开 SQLite 连接。 建连接是微秒级开销,换来的是完全不用考虑线程安全 —— MCP server 可能在任意线程里调工具。WAL 模式保证 CLI 写入时 MCP 仍可读。

原文先写 .tmp 再 rename。 中途崩溃不会留下半截文件;入库失败会删掉刚写的原文,不留孤儿。

测试

pip install -r requirements-dev.txt
python -m pytest tests/ -q

29 个用例覆盖:写入/读取、按类型生成 ID、Unicode 往返、 LIKE 通配符转义、8 种目录穿越变体、大小上限、截断、 版本自增、关系往返、外键拒绝。

局限

  • 只有关键词检索,没有中文分词和向量召回。搜「上下文」能命中「独立浏览器上下文」, 但搜「记忆溢出」不会命中「上下文膨胀」。缓解办法:keywords 里多写几种说法。

  • 单机单用户。多进程同时写靠 SQLite 的锁,没有做分布式。

  • 没有删除命令(有意为之:误删记忆的代价高于留一点噪声)。需要清理时直接操作 SQLite。

  • 单条原文超过 2 MB 会被拒绝加载 —— 超过这个体量就该拆成多条记录了。

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Local-first AI memory layer with hybrid retrieval and brain-inspired namespaces. Enables agents to save, search, and manage memories directly via MCP tools.
    5 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Provides persistent, searchable structured memory management for AI agents, with keyword matching, bidirectional Zettelkasten sync, and six MCP tools.
    6
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to store, recall, link, and manage durable local memories using ranked fuzzy search and optional graph context through a compact MCP interface.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to store and recall durable facts, decisions, and lessons over MCP using hybrid semantic and keyword search, typed memories, graph relations, sessions, and time-travel queries. Runs locally with no server process or API key, persisting everything to files that both agents and a CLI can share.
    14 npm
    MIT