关于 keymem
keymem 是面向 LLM Agent 的本地关联记忆 MCP 服务,以概念与记忆相连的图结构为基础。Agent 可沿共享键路径找回与查询词不相似但相关的信息,并控制进入上下文的记忆。
主要功能
- 01多对多键与记忆图
- 02由 Agent 引导的多跳回忆
- 03新鲜度与确认状态跟踪
- 04更正记录与版本保留
- 05本地 JSON 存储与嵌入
适用场景
- 01通过概念关联找回事实
- 02逐步浏览已存记忆
- 03检查记忆是否仍然有效
- 04更正信息并保留旧版本
MCP 相关
它在本地作为 MCP 服务运行,向兼容的 Agent 提供记忆回忆、导航、存储和确认能力。其 MCP 流程让 Agent 按步骤在键与记忆之间移动。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
11收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| query | string | 是 | |
| top_k | number | 否 | |
| inject | boolean | 否 | |
| namespace | string | 否 | |
| inject_top_k | number | 否 | |
| inject_prefer_depth | boolean | 否 | |
| inject_explore_shallow | boolean | 否 |
详细介绍
keymem
面向大模型 Agent 的联想记忆层——通过关联而非仅凭相似度进行回忆。

大多数 Agent 记忆都是向量存储。它会找出听起来和你的查询相似的内容,却会漏掉所有与你的查询相关联的内容。
keymem 会将记忆存储在键图中。因此,搜索 “Newton” 仍能找到 “strawberries”——Newton → apple → fruit → strawberry。路径存在于图中,而非嵌入空间。它作为 MCP 服务在本地运行,因此任何兼容 MCP 的 Agent 都能进行类人的联想回忆,无需外部数据库。
兼容: Claude Desktop · Claude Code · 任何兼容 MCP 的大模型 Agent
为什么需要联想记忆?
向量存储记忆通过嵌入相似度检索内容。当你需要的内容与输入的词语并不相似时,这种方式就会失效:
Query: "Newton"
Similarity search finds: "Newton discovered gravity" ✅
Similarity search misses: "user likes strawberries" ❌
人可以自行完成这一步联想——Newton 让他们想起苹果,苹果是水果,而他们喜欢草莓。keymem 也能完成同样的联想,因为键图中存在这条路径:Newton → apple memory → fruit key → strawberry memory。“Newton”和“strawberry”之间没有嵌入距离上的关联;将它们连接起来的是一串共享键。
这就是核心理念:记忆不是按距离排序的孤岛,而是 Agent 可以遍历的 N:M 键/值图中的节点。
工作原理
Key Space (concepts) Value Space (memories)
[apple] ────────┬─────────→ ↑ same memory
[gravity] ────────┘
│
[apple] ────────┼─────────→ "apples are red fruit"
[fruit] ──────┬─┘
[red] ──────┤
│
[fruit] ──────┼─────────→ "user likes strawberries"
[strawberry]────┘
记忆存放在值空间中,通过独立的键空间访问——一条记忆可以通过多个键访问,一个键也可以指向多条记忆。
recall("Newton") 会返回匹配的键簇,例如 [Newton] 和 [apple],并在最强匹配键下被动返回一条 Top-1 记忆。这条记忆包含 matched_key、validity 和 connected_keys,因此 Agent 可以立即作答,也可以通过 read_key(fruit) 继续探索。
默认的 MCP 流程仍然是 Key → Memory → Key,但第一步 Key → Memory 只需一次调用即可完成。只有一条记忆会自动进入上下文;后续步骤仍由 Agent 主动决定。
顶部的动画展示了这一流程在实际存储中的运行方式:调用三次 recall(),从个人资料键一路遍历到验证理念,加载三条记忆(约 2.4k 个 token)。Agent 跳过的分支仍可通过一次调用访问;而自动展开同一个包含 12 条记忆的枢纽,则会耗费约 8k 个 token。图掌握路径;Agent 决定步伐。源码:docs/demo/chain-walk.html。
快速开始
keymem 是一个 MCP 服务(CLI),不是库。 使用
npx -y keymem运行(推荐——始终使用最新版),或通过npm i -g keymem全局安装命令。不要将其作为依赖通过npm i keymem添加到你的应用中:它捆绑了openai、zod和 MCP SDK,因此在已有项目中只会重复安装这些依赖树(还可能与你应用中的zod/openai版本冲突)。npm 在软件包页面上显示的npm i keymem命令适用于库,不适用于此处。
# Optional global install (npx needs none). This puts a `keymem` command on PATH that
# MCP clients can spawn. Run bare, it starts a stdio MCP server and waits for a client —
# so point your MCP config at `keymem` (or just use `npx -y keymem` as shown below).
npm i -g keymem
Claude Desktop
将以下内容添加到 claude_desktop_config.json:
OpenAI 嵌入:
{
"mcpServers": {
"keymem": {
"command": "npx",
"args": ["-y", "keymem"],
"env": {
"OPENAI_API_KEY": "your-openai-api-key"
}
}
}
}
本地嵌入(无需 API 密钥)——推荐使用 bge-m3:
{
"mcpServers": {
"keymem": {
"command": "npx",
"args": ["-y", "keymem"],
"env": {
"EMBEDDING_BACKEND": "local",
"LOCAL_EMBEDDING_MODEL": "bge-m3"
}
}
}
}
bge-m3(多语言,推荐)首次运行时会自动下载约 570MB,之后会缓存。省略LOCAL_EMBEDDING_MODEL即可使用更轻量的默认模型(fast-multilingual-e5-large)。跨编码器重排是核心回忆流程的一部分,首次使用时会下载第二个模型;只有在要禁用它时才将"KEYMEM_RERANK": "false"。
插件(推荐 — Claude Code 和 Codex)
此仓库也是一个插件市场,因此安装一次即可配置好所有内容:MCP 服务(由守护进程支持的适配层)、每次提交提示词时被动呈现相关记忆的 UserPromptSubmit 钩子,以及包含回忆/记忆协议的 keymem 技能。
Claude Code:
/plugin marketplace add donggyun112/keymem
/plugin install keymem@keymem
Codex CLI:
codex plugin marketplace add donggyun112/keymem
codex plugin add keymem@keymem
Codex 会提示一次是否信任该钩子;请批准,否则推送路径将保持静默。插件默认使用本地 bge-m3 嵌入(首次运行时会自动下载约 570MB,无需 API 密钥)。如需使用 OpenAI 嵌入,请改用下方的手动配置——插件 MCP 服务只能读取其声明的环境变量。同步钩子最多等待 /inject 9 秒(设置 KEYMEM_HOOK_TIMEOUT_MS 可调整);触发回忆时,本地嵌入和重排可能会让提示词处理多耗费几秒。
Claude Code(手动配置)
# OpenAI embeddings
claude mcp add keymem -e OPENAI_API_KEY=your-key -- npx -y keymem
# Local embeddings (no API key required) — bge-m3 recommended (auto-downloads ~570MB on first run)
claude mcp add keymem -e EMBEDDING_BACKEND=local -e LOCAL_EMBEDDING_MODEL=bge-m3 -- npx -y keymem
Codex CLI(手动配置)
# OpenAI embeddings
codex mcp add keymem --env OPENAI_API_KEY=your-key -- npx -y -p keymem@latest keymem-shim
# Local embeddings (no API key required)
codex mcp add keymem --env EMBEDDING_BACKEND=local --env LOCAL_EMBEDDING_MODEL=bge-m3 -- npx -y -p keymem@latest keymem-shim
请使用 keymem-shim 入口点(而非裸用 keymem):它会运行推送路径钩子所使用的共享守护进程。对于钩子,请将以下内容添加到 ~/.codex/config.toml:
[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
type = "command"
command = "node /absolute/path/to/keymem/hooks/keymem-hook.mjs"
timeout = 10
Codex 只会转发其 MCP 条目中声明的环境变量,因此请通过 --env 传递所有 KEYMEM_* / SUPER_MEMORY_* 覆盖项。
就这么简单——回忆和记忆功能会立即生效。Agent 会在首次回复前调用 recall,使用 read_key/read_memory 导航,并通过 remember 保存。
如需在 Claude Code 中可靠地主动保存,请将以下内容添加到 ~/.claude/CLAUDE.md(MCP 提示词不会自动应用为 Claude Code 的持久指令):
## keymem
- Before the first reply and whenever the topic changes, call `recall` silently with short noun-keyword queries.
- Before ending every reply, check whether this turn revealed a durable fact: a name, preference, decision, correction, project fact, or goal.
- If it did, call `remember` or `remember_batch` silently in the same turn with 3-6 diverse keys. A durable fact left unsaved is a bug.
- Use `correct` when existing information changes. Save nothing only when the turn revealed nothing durable.
- Treat `read_memory` as retrieval, not confirmation. Use its `validity.status`: qualify `aging`, and never assert `stale` as current without checking an external source or asking the user.
- Call `confirm_memory` only after an explicit current user assertion, an authoritative current source, or direct observation — never merely because a read succeeded.
- Never mention memory lookup or saving to the user.
在 Codex 中,请将相同内容放入 ~/.codex/AGENTS.md。(插件安装会将其作为 keymem 技能提供,因此无需在此处重复添加。)
对于其他 MCP 客户端,请将 MCP 提示词 memory_system_prompt 纳入 Agent 的持久系统指令中。它要求在首次回复前进行 3 次盲目 recall() 调用——当没有钩子被动呈现内容时,这样做是合适的。如果你确实自行配置了 UserPromptSubmit 等效推送路径,请设置 KEYMEM_HOOK_INSTALLED=true,这样提示词就会根据已呈现的内容作出响应,而不是在此基础上盲目猜测(插件的 .mcp.json 会自动设置此项)。
手动配置 / 开发
git clone https://github.com/donggyun112/keymem
cd keymem
pnpm install
创建 .env:
OPENAI_API_KEY=your-openai-api-key
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
或者使用本地嵌入(无需 API 密钥):
EMBEDDING_BACKEND=local
LOCAL_EMBEDDING_MODEL=fast-multilingual-e5-large # default; best fit for Korean/multilingual keys
pnpm dev
# or:
pnpm build
pnpm start
要求:
- Node.js 20+
- 本地开发需要 pnpm
- 使用 OpenAI 嵌入需要 OpenAI API 密钥;使用本地嵌入则需要
fastembed
功能
- N:M 键/值图谱 — 记忆与用于索引它们的概念属于不同空间,并以多对多方式连接。一条记忆可通过多个键访问;一个键可关联多条记忆。
- 由 Agent 驱动的“键 → 记忆 → 键”导航 — Agent 有意遍历图谱,而不是将其压缩成一次不透明的相似度搜索。
- 联想式多跳回忆 — 通过沿共享键的链路遍历,找到嵌入距离无法关联的记忆。
- 感知确认状态的新鲜度 — 读取操作会学习有用路径,但不会认证旧内容;明确的证据会根据四种衰减配置刷新事实。
- 单步版本控制 — 更正会保留其直接前一版本,并记录被取代的内容。
- 键类型 —
concept键按相似度匹配;name/proper_noun键精确匹配,因此「동건」不会仅仅因为较短就匹配「뉴턴」。 - 跨语言键合并(IDF) —
파이썬和Python会归并到同一个规范集群中,而不是导致键空间碎片化。 - 赫布式链接学习 — Agent 实际遍历的路径会得到强化(「共同激活,连接增强」),让有用的关联更容易访问。
- 混合检索(可选直接模式)— 对键和内容进行 Kiwi BM25 检索 + 键嵌入 + 倒数排名融合,并结合深度/确认新鲜度调节和可配置的多跳扩展。
- 交叉编码器重排序(核心默认配置)—
bge-reranker-v2-m3会对被动 Top-1 池和兼容的直接模式候选项重新评分。 - 本地优先 — 所有数据都存储在本地 JSON 图谱中;无需外部数据库。支持 OpenAI 或完全本地的嵌入模型(自动下载)。
新鲜度与深度
每条记忆都有一个深度分数 0.0 → 1.0,但检索和确认是彼此独立的:
| 阶段 | 深度 | 行为 |
|---|---|---|
| 浅 | < 0.3 | 明确确认较少;排名提升幅度很小。 |
| 中 | 0.3–0.7 | 已多次确认;排名适度提升。 |
| 深 | > 0.7 | 已充分确认;排名提升幅度最大,但仍可更正,且会受时间影响。 |
read_memory() 会递增访问元数据,并可能强化所遍历的键边,但它不会更改深度、last_confirmed_at、confirmation_count 或新鲜度。只有 confirm_memory(memory_id, evidence) 才会刷新有效性并将深度提高 +0.05;可接受的证据包括用户明确且当前的陈述、权威的当前来源或直接观察。仅仅成功读取绝不算作确认。
新鲜度会根据记忆的 decay_profile,从 last_confirmed_at 起逐渐衰减:
| 配置 | 默认半衰期 | 适用场景 |
|---|---|---|
transient | 7 天 | 临时计划或可用性等快速变化的状态 |
standard | 90 天 | 一般事实;默认配置 |
stable | 365 天 | 缓慢变化的事实 |
permanent | 不衰减 | 刻意视为永恒或不可变的事实 |
每个记忆视图都包含一个 validity 载荷,其中包含 freshness、status(fresh、aging 或 stale)、age_days、last_confirmed_at、confirmation_count、decay_profile、verification_recommended 和 verification_required。fresh 表示新鲜度至少为 0.5;aging 表示新鲜度至少为 0.125 且低于 0.5;stale 表示低于 0.125,未经验证不得将其作为当前事实断言。衰减是柔性的:它通过 0.2 + 0.8 × freshness 降低排名,但绝不会删除记忆。TTL 仍是唯一的自动过期机制。
例如,使用 remember(..., decay_profile:"transient") 保存临时计划;用户明确表示计划仍然有效后,调用 confirm_memory(memory_id, evidence:"user")。不要仅仅因为 read_memory 返回了该记忆就进行确认。
关键类型
并非所有键都应采用相同的匹配方式。名称不应仅因语义相似而匹配——“동건”不应仅仅因为和“뉴턴”都是简短的韩语词就与之匹配。
| 类型 | 匹配方式 | 用途 |
|---|---|---|
concept(默认) | 嵌入相似度 ≥ 阈值(OpenAI 为 0.28/本地为 0.60) | 主题、类别、属性 |
name | 仅精确匹配 | 人名 |
proper_noun | 仅精确匹配 | 品牌、地点 |
当 name/proper_noun 键成为连接许多记忆的枢纽键时,还会受到 IDF 惩罚(×0.5),以防它们污染无关搜索。
版本控制(单步前序版本)
"user lives in Seoul" (depth: 0.4 → weakened to 0.12, preserved)
↑ superseded by
"user moved to Busan" (depth: 0.0, new)
keymem 会保留紧邻的前序版本,而不是将其覆盖。无论其层级多深,被取代的记录都会从活动检索中排除,新版本则成为当前版本。之后的更正会清除祖父版本,因此来源记录仅保留一个前序版本,而非完整历史存档。
键合并
Add key "파이썬" → finds existing "Python" (similarity 0.87 > threshold 0.85)
→ reuses existing key instead of creating duplicate
防止键空间碎片化。不同语言或不同表述中的同一概念会保持统一。
Agent 驱动的检索(默认)
默认 MCP API 会从 Key Space 跨到一个 Value,同时保留显式图导航:
recall(query, context)会返回排序后的键簇,以及最强键下排名第一的记忆。原始context用于在该键内为记忆排序。- 记忆是一个被动预览,包含
validity、matched_key和所有connected_keys[].key_id。它不会增加访问次数或深度、强化链接、学习别名或确认时效性。 - Agent 会检查问题需要哪些事实。如果预览中包含这些事实,就可以使用预览;否则,它会通过
read_key(key_id, missing_fact_query)跟进某个关联键以查找缺失事实,或直接检索该事实。当证据完整或没有剩余的有希望路径时,Agent 就会停止。不会自动注入后续记忆。 read_memory(memory_id, via_key_id)仍是显式的完整读取路径。它会更新访问元数据,并且只强化经过的边;它不会确认或加深记忆。
语义上合并的键会作为别名保留在同一个规范键簇中(例如 Python + 파이썬)。推荐的 bge-m3 配置默认启用保守的短键合并;可通过 KEYMEM_SHORT_KEY_MERGE 覆盖或禁用该功能。至少关联三个活动记忆的键会作为枢纽呈现,并附带 is_hub、memory_count 和 specificity 元数据,而不会被 IDF 隐藏。可通过 KEYMEM_KEY_HUB_MIN_LINKS 覆盖枢纽阈值。
关联式回忆引擎(库 API,由 recallInject 在内部使用)
MemoryGraph.recall() 是单次调用的排序检索路径,由 recallInject() 使用,也可供库调用方使用。它通过 倒数排名融合(RRF_K = 60)组合两条候选路径:
- Kiwi BM25: 使用 Node 中官方的
kiwi-nlpWASM 分词器,对记忆键和内容进行词法搜索。韩语形态会在本地分词;无需 Python 进程。 - 键匹配: 查询嵌入与概念键匹配;精确名称和别名提供字面入口。键链接会引导至候选记忆。
记忆正文没有嵌入。交叉编码器重排序仍处于启用状态,并直接为查询—内容对评分。图扩展、深度和确认时效性仍会影响排序。默认 MCP recall 工具使用显式键导航,因此 Agent 可以有意驱动后续跳转。
Hebbian 链接学习
读取完整记忆是一种写入,而不只是读取。在默认流程中,recall() 和 read_key() 是只读操作;read_memory(memory_id, via_key_id) 会重塑所选路径:
- 遍历的
via_key_id → memory_id链接会得到强化(+0.1,上限为3.0)。 - 完整读取会递增访问元数据,但既不会改变深度,也不会改变
last_confirmed_at,因此 永远不会刷新新鲜度。随着时间流逝,新鲜度会持续衰减。有证据支持的confirm_memory()会增加深度,并通过推进last_confirmed_at刷新新鲜度。
强化仅作用于 Agent 实际遍历的键,而不是附加到记忆的所有键。这就是字面意义上的 Hebbian 规则(“共同激活,共同连接”),可防止通过不同概念访问记忆时,无关关联也随之增强。权重被限制在 [0.1, 3.0]。
链接权重会影响 read_key() 的排序,因此反复选中的路径会变得更容易访问。recall()/recallInject() 保留原有的匹配链接强化和已探索链接衰减行为。
架构
│ Key Space │
│ [name] [동건] [programming] [python] [fruit] [red] │
│ ↓ ↓ ↓ ↓ ↓ ↓ │
│ [vec] [exact] [vec] [vec] [vec] [vec] │
│ N:M links
↓
│ Value Space │
│ "user's name is Donggeon" depth: 0.85 (deep) │
│ "user likes Python" depth: 0.30 (medium) │
│ "user likes strawberries" depth: 0.05 (shallow) │
默认 MCP 导航:
- 嵌入查询,并匹配规范键概念和精确别名。
- 返回包含
memory_count、is_hub和specificity的键集群;不返回记忆内容。 - 在
read_key()中,根据链接权重、深度和确认新鲜度对所选键的记忆句柄排序。 - 通过
read_memory()返回完整内容和相邻键集群;仅当提供via_key_id时,才强化遍历的边。 - 重复调用
read_key(next_key_id),有意遍历图。
recall() 算法(混合式,可配置为 1–5 跳;默认为 2;由 recallInject() 在内部使用):
- 使用 Kiwi 对查询和记忆键/内容进行分词,然后检索 BM25 候选项。
- 使用查询嵌入和字面名称/别名匹配来匹配概念键,然后沿其记忆链接继续检索。
- 使用 RRF(
RRF_K = 60)融合词法排名与键路径排名。 - 应用深度和确认新鲜度,并在配置的跳数范围内通过共享键和显式
related_to链接进行扩展。 - 使用交叉编码器对候选项重新排序,并返回排序后的结果。匹配链接强化和已探索链接衰减保留其原有行为。
存储和重复项处理
只有键会保留嵌入向量。加载时会忽略现有记忆正文的向量,并在下一次成功保存后将其省略;键向量、记忆文本、链接和历史记录都会保留。如果需要使用旧版本重新打开存储,请在升级前备份。
重复项检测会在现有命名空间和活动记忆规则范围内,对规范化后的记忆文本进行精确比较。释义内容会作为独立记录保存。自动确认要求规范化后的话语文本与正文文本完全一致。新记忆不会根据正文向量相似度获得链接或矛盾标记;请提供键和显式关系,并对已变更的事实使用 correct。现有显式链接和已记录的矛盾仍可读取。
键相似度阈值
嵌入阈值适用于键,而非记忆正文。src/embedding.ts 中的 getThresholdProfile() 提供特定于模型的默认值;使用 KEYMEM_KEY_RECALL、KEYMEM_KEY_MERGE、KEYMEM_KEY_AUTOLINK(自动键别名提升)和 KEYMEM_SHORT_KEY_MERGE 调整键路由和规范化。未经校准的模型会在发出警告后回退到 BGE 配置。
旧版正文余弦设置(KEYMEM_CONTENT_RECALL、KEYMEM_CONTENT_RECALL_SHORT、KEYMEM_MEMORY_DEDUP、KEYMEM_CONTRADICTION、KEYMEM_MIN_SCORE、KEYMEM_GATE_Z 和 KEYMEM_KEY_GATE)不再控制记忆检索或写入。BM25 分数、键余弦分数、RRF 排名和交叉编码器分数的尺度各不相同;不要将旧的正文余弦阈值用作词法相关性阈值。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
KEYMEM_AUTOKEY | true | 自动键自我修复:从实际使用中学习缺失的搜索词。设为 false 可禁用。 |
KEYMEM_AUTOKEY_PROMOTE_N | 3 | 将查询并入键空间前,必须经过的 (key, query) 对路由确认选择次数。 |
KEYMEM_AUTOKEY_CONFIRM_FLOOR | 0.45 | 可用于路由确认学习的查询↔键余弦相似度下限。通过同一个键重复选择,可以学习低于门限的查询别名;这只确认路由,不代表内容仍然新鲜。调低(例如 0.40)可纳入更多处于临界值的释义;设为 ≥ 检索阈值可禁用此功能。 |
KEYMEM_AUTOKEY_MAX_ALIASES | 8 | 每个键最多提升的已学习别名数。 |
KEYMEM_AUTOKEY_PRUNE_AGE | 2592000 | 从未命中的已学习别名在守护进程自动 TTL 清理中被清除前的秒数(30 天)。 |
KEYMEM_CLEANUP_INTERVAL_MS | 3600000 (1h) | 守护进程自行清理过期记忆/别名的频率。stdio 路径则在启动时清理一次(没有可供安排清理的长期运行循环)。 |
KEYMEM_DECAY_TRANSIENT_DAYS | 7 | transient 记忆的半衰期(天)。必须是有限且大于零的值。 |
KEYMEM_DECAY_STANDARD_DAYS | 90 | 默认 standard 配置的半衰期(天)。必须是有限且大于零的值。 |
KEYMEM_DECAY_STABLE_DAYS | 365 | stable 记忆的半衰期(天)。必须是有限且大于零的值。 |
对于跨语言检索,请保留多语言键和别名(例如 딸기 和 strawberry)。仅搜索正文中的词汇不会翻译查询。
MCP 工具
工具集包含 10 个工具:
| 工具 | 描述 |
|---|---|
recall(query, top_k?, namespace?, context?, explain?) | 返回排序后的键,以及一条被动的 Top-1 记忆,其中包含 validity、matched_key 和 connected_keys。explain:true 还会返回命名空间计数。 |
browse_keys(namespace, hubs_only?, limit?, offset?) | 在 recall 未命中条目时,浏览命名空间中的活跃键词汇表,优先显示枢纽键。 |
read_key(key_id, query?, namespace?, limit?, offset?) | 列出与某个键关联的已排序记忆 ID 和元数据。传入原始查询可按相关性排序;支持对枢纽键进行分页。 |
read_memory(memory_id, via_key_id?, namespace?) | 读取完整记忆内容、关联键和 validity。更新访问记录;如果提供了 via_key_id,还会更新经过的那条边;绝不会确认或加深记忆内容。 |
confirm_memory(memory_id, evidence, namespace?, source?) | 在用户明确提供证据、提供权威来源或直接观察后,刷新当前记忆的新鲜度并加深记忆。仅读取不算证据。 |
remember(content, keys, key_types?, namespace?, ttl_seconds?, decay_profile?, related_to?) | 保存包含关键概念的记忆,可选设置 TTL,以及 transient、standard、stable 或 permanent 衰减配置。 |
correct(memory_id, content, keys?, key_types?, ttl_seconds?, decay_profile?, related_to?) | 进行版本化更新。保留紧邻的前一个版本,但将其设为非活跃;未指定的 TTL/配置继承自前一个版本。 |
dismiss(memory_id, key_id, namespace?) | 负面反馈:事实本身没问题,但不应通过此键显示。削弱这条边(设有下限,不会断开),并取消其待处理的别名学习。 |
forget(memory_id) | 永久删除 |
remember_batch(items) | 保存多条记忆;每个条目都接受 ttl_seconds 和 decay_profile |
TTL 已过期的记忆以及键/记忆/链接计数不再是面向 Agent 的工具:守护进程会自行清理过期记忆(启动时执行一次,之后每隔 KEYMEM_CLEANUP_INTERVAL_MS 执行一次;stdio 路径则只在启动时清理一次);键/记忆/链接统计数据已在每轮对话中被动注入 SERVER_INSTRUCTIONS,因此单独调用 memory_stats() 纯属重复。
分数包含 score_kind。键召回会通过键余弦相似度或有界词法相关性提供 key_relevance。content_relevance 是有界 BM25(s / (1 + s)),不是余弦相似度。read_key 使用 within_key_rank;有交叉编码器时采用交叉编码器相关性,否则采用有界词法相关性。直接召回使用 rrf_fused_rank;RRF 的 rank_score 是排名融合值,而非相似概率。
此外,还提供了 memory_system_prompt MCP 提示词模板——将其纳入提示词,指示 Agent 静默召回、使用多样化的键,并且绝不向用户提及记忆系统。
本地嵌入模型
**BGE-M3(推荐,支持多语言)——自动下载:**设置 LOCAL_EMBEDDING_MODEL=bge-m3(别名:bgem3、baai/bge-m3、fast-bge-m3)。首次使用时,如果模型不存在,会自动获取——量化 ONNX 模型(约 570MB,来自 onnx-community/bge-m3-ONNX)以及分词器/配置(来自 BAAI/bge-m3)——并缓存到 ~/.keymem/models/bge-m3。无需手动下载:
EMBEDDING_BACKEND=local
LOCAL_EMBEDDING_MODEL=bge-m3
# optional — point at an existing model dir to skip the download (backward compatible):
# LOCAL_EMBEDDING_MODEL_PATH=/absolute/path/to/model-dir # dir with model.onnx + tokenizer files
# LOCAL_EMBEDDING_MODEL_FILE=model.onnx # optional; default is model.onnx
# optional: KEYMEM_EMBED_THREADS=4 # ONNX intra-op threads (default: a quarter of the machine, max 6)
首次运行时会下载约 570MB,之后会重复使用缓存。如果
LOCAL_EMBEDDING_MODEL_PATH已包含模型,则会直接使用,不进行下载(部分目录会自动修复——仅获取缺失文件)。在线 API 后端(OpenAI)和 fastembed 内置模型不受影响。
**bge-m3 在自己的 ONNX 会话中运行,不通过 fastembed。**输入使用实际的词元长度。其向量空间指纹为
local:bge-m3+nopad;指纹变更会触发键向量迁移。BENCHMARKS.md 中较早的填充/延迟和检索测量结果描述的是先前的正文嵌入引擎。 交叉编码器重排(核心功能): 默认recallMCP 工具的 Top-1 路径会使用bge-reranker-v2-m3,按照候选池中最强的键重新评分。recall()/recallInject()的结果也会重新排序。该模型(约 570 MB,量化版)会在首次使用时自动下载,并缓存于~/.keymem/models/reranker。
# optional: KEYMEM_RERANK=false # disable the core reranker
# optional: KEYMEM_RERANK_MODEL_PATH=/dir # use an existing model directory
# optional: KEYMEM_RERANK_POOL=30 # candidates re-scored (default 30)
# optional: KEYMEM_RERANK_THREADS=4 # ONNX intra-op threads (default: a quarter of the machine, max 6)
默认启用。如果模型无法加载,recall 会回退到融合排序。查询分解仍由调用方负责。
任务条件证据选择: 扩展召回仍保持关联式,但对于比较类查询,会在最终证据边界进行投影,使两个具名实体都获得与任务相关的支持。KeyMem 会在移除实体名称后推导比较维度(例如 nationality 或 founded),保留图候选池和关联分数不变,并保留原始关联胜出项以供强化。此功能默认启用;可通过 KEYMEM_TASK_EVIDENCE_SELECTION=false 禁用。
重排器未找到门限(KEYMEM_RERANK_MIN_SCORE): 在 recall()/recallInject() 中,如果交叉编码器的最高 logit 低于此下限,则拒绝完整结果。
KEYMEM_RERANK_MIN_SCORE=0 # reject when top rerank logit < 0 (bge-reranker-v2-m3 scale)
⚠️ 注意事项。(1)默认未设置,即不启用门限。(2)logit 的取值范围取决于模型;
0(≈ sigmoid 0.5)适用于bge-reranker-v2-m3(实测:同语言的已找到结果约为 +3.9,未找到结果约为 −5 至 −6)——使用其他重排器时请重新校准。(3)仅适用于同语言内容。 即使相关,跨语言相关性 logit 也可能偏低(KR 查询 ↔ EN 记忆约为 −5.4),因此门限会在文字体系不匹配(KR↔Latin)时自动绕过,以避免误拒跨语言命中——这意味着跨语言内容必须可通过双语键访问(["Jiwoo","지우"]),而跨语言“未找到”的精确度是已知限制。如果无法为键添加双语标记,请关闭此功能。
前缀行为: BGE-M3 不使用
passage:/query:前缀——嵌入会原样传入。所有其他本地模型(e5、BGE-en、MiniLM)仍会照常使用前缀。
多语言键嵌入: 仍支持
bge-m3。历史模型比较无法证明当前 Kiwi BM25 架构的质量或未找到行为。
如果未设置 OPENAI_API_KEY,且未设置 EMBEDDING_BACKEND,服务器会自动使用本地 fastembed 后端。
仅用于英文或希望降低本地资源占用时,请设置 LOCAL_EMBEDDING_MODEL=fast-bge-base-en-v1.5 或 fast-bge-small-en-v1.5。
安全切换后端。 图会记录嵌入指纹(后端 + 模型 ID),用于标识嵌入所在的向量空间。启动时,如果当前后端的指纹或维度与存储值不同,图会自动迁移——使用新后端重新嵌入每个键,同时保留内容、链接、深度和访问历史(并先写入
graph.json.bak.*备份)。指纹很重要,因为两个模型的维度可能相同,但生成的向量并不兼容(例如,fast-multilingual-e5-large和bge-m3均为 1024 维);仅检查维度无法发现这种切换,会悄然破坏所有相似度计算。可通过KEYMEM_AUTO_MIGRATE=false禁用。通过 OpenAI 重新嵌入会产生一次性 API 费用,费用与键的数量成正比。将预指纹(旧版)图迁移到维度相同的其他模型。 指纹机制启用前写入的图没有记录向量空间,因此无法自动检测切换到维度相同的其他模型。设置
KEYMEM_FORCE_REEMBED=true并启动一次,即可无条件重新嵌入并写入指纹;随后请移除此设置(若一直保留,每次启动都会重新嵌入)。从现有 e5 图迁移到 bge-m3 时,正需要执行这一次性操作。
数据存储
所有数据均存储在本地。无需外部数据库。
~/.keymem/
└── graph.json # canonical keys, aliases, memories, weighted links
设置 KEYMEM_DATA_DIR 以使用其他存储目录。
将记忆关联到其来源对话。 保存记忆时,keymem 会将当前主机会话信息写入其 source(host_session / host_agent / host_turn),以记录来源。keymem 不提供读取这些对话记录的工具——它只会在内部读取记录,以确定当前会话。各操作系统的存储位置会自动检测,并遵循各 Agent 的环境变量覆盖设置:
- Claude Code —
~/.claude/projects/**/{session_id}.jsonl($CLAUDE_CONFIG_DIR) - Codex —
~/.codex/sessions/**/rollout-*-{session_id}.jsonl($CODEX_HOME)
访问受到限制。 对话记录存储在本地,可能包含敏感历史记录,因此只有在确认当前身份是所有者的个人本地 Agent 时,keymem 才会读取记录并写入来源信息——也就是说,已识别的主机注入了其会话环境变量(CLAUDE_CODE_SESSION_ID / CODEX_THREAD_ID),或你明确设置了 KEYMEM_TRANSCRIPT_ACCESS=true。否则(普通服务、远程部署、非所有者 Agent 或自定义 Agent)保存的记忆不会关联主机。设置 KEYMEM_TRANSCRIPT_ACCESS=false,即可在主机 Agent 环境下也强制禁用此功能。
当前会话通过以下两种方式确定:
- 确定性方式 — 主机会将其会话 ID 注入所启动的每个 MCP 服务,keymem 会直接读取该 ID:Claude Code →
CLAUDE_CODE_SESSION_ID,Codex →CODEX_THREAD_ID(与 rollout 文件的会话 ID 相同)。关联精准无误,不需要猜测。 - 启发式回退 — 对于不提供会话 ID 的主机(例如 Claude Desktop),keymem 会使用最近修改的对话记录(并设有过期保护)。
基准测试
以下是对先前正文嵌入引擎的历史测量结果:在 HotpotQA 桥接问题上(使用真实外部多跳数据和标准答案标签,不依赖大模型评判;密钥由从未见过问题或答案的独立子 Agent 盲生成),键图检索到两个标准答案支持段落的比例为 63%,高于扁平语义检索的 53% 和词法检索的 35%(分别高出 10 个百分点和 28 个百分点)——这正是该方法旨在解决的关联但不相似场景。(如实说明:我自己手动推导的密钥将结果夸大到了 78%/60%;在非多跳的“比较”问题上,键图的效果略差;这衡量的是检索召回率,而非答案准确率。)读取路径的复杂度也是 O(1)(read_memory 的 p50 延迟:500 条记忆时约 45 毫秒 → 约 0.01 毫秒)。完整方法和注意事项见:BENCHMARKS.md。这些分数并不能验证当前的 Kiwi BM25 + 键嵌入引擎;目前尚未确定其生产环境质量评分。
限制
- 线性扫描 — 适用于个人使用(约 10k 条记忆)。计划集成 FAISS/ChromaDB,以支持更大规模的数据。
- 后续跳转的往返调用 —
recall会完成第一个 Key → Memory 跳转,但要沿着关联的键跳转到另一条记忆,仍需执行read_key → read_memory。 - 枢纽键的覆盖范围 — 宽泛的键可能关联许多记忆。
read_key()会对枢纽键的结果分页;Agent 必须选择继续翻页,还是沿着更具体的相邻键继续查找。 - Agent 质量很重要 — 在
remember中选择键会影响检索质量。调优系统提示词很重要。 - 词法覆盖能力 — Kiwi BM25 本身不会翻译,也不会检测释义。多语言键、键嵌入和重排序有助于检索并排列候选项,但无法保证找回缺失的候选项。
- 阈值校准 — 阈值针对各嵌入模型分别调优。新的或未经校准的模型会回退到 BGE 配置文件(并发出警告);可通过
KEYMEM_*环境变量覆盖设置重新校准。
测试
pnpm test # unit tests (fast, no model download)
tsx test/scenarios.ts # 21 end-to-end behavioral checks (local e5)
tsx test/robustness.ts # threshold overrides + Hebbian pollution bounds
tsx test/migration.ts # backend/dimension switch auto-migration (no brick)
tsx test/nhop.ts # N-hop chained traversal (recall hops parameter)
tsx test/depth-noise.ts # deep-hop noise bounds + relative score floor
tsx test/live-multilingual.ts # interactive multilingual recall demo
# Manual retriever-quality check (NOT part of pnpm test):
EMBEDDING_BACKEND=local npx tsx test/retriever-quality.live.ts
# For bge-m3: also set LOCAL_EMBEDDING_MODEL=bge-m3 LOCAL_EMBEDDING_MODEL_PATH=/abs/dir
scenarios.ts 和 robustness.ts 会测试实际的本地嵌入后端(直接/联想/跨语言召回、版本控制、深度增长、去重、TTL、Hebbian 学习、命名空间隔离)。引擎或模型变更后,必须重新运行测试,才能重新校准关键阈值并评估检索质量;历史上的通过结果无法验证此架构。
路线图
- 支持 FAISS/ChromaDB,以满足扩展需求
- 编码 Agent 配置文件(针对代码上下文采用不同的关键字策略)
- 记忆导出/导入
- 支持多用户
作者
donggyun112 — github.com/donggyun112
代码仓库:donggyun112/keymem · 欢迎提交 Issues 和 PR。
许可证
MIT © donggyun112
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。