关于 context-keeper
context-keeper 是面向 Claude 的离线优先项目记忆 MCP 服务,用于跨会话保存决策、流程和约束。它将内容存为带类型的可检索条目,并提供生命周期管理与质量检查。
主要功能
- 01记录决策、流程和约束
- 02按相关性或精确字段检索
- 03追踪替代关系与过期条目
- 04检查条目质量和矛盾
- 05导入和导出项目快照
适用场景
- 01在新的 Claude 会话中延续项目规则
- 02检索工作流程或过往设计决策
- 03检查过期或互相矛盾的记忆条目
MCP 相关
向 MCP 客户端提供记录、检索、更新和导出结构化项目记忆的工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx context-keeper-mcpCONTEXT_KEEPER_PROJECTAbsolute path to the project directory. If omitted, uses cwd only when it already contains a .context/ directory; otherwise tools require explicit project_dir.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
14收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| kind | enum | 是 | 要记录的条目类型。decision | constraint | pipeline |
| name | string | 否 | pipeline:名称。 |
| rule | string | 否 | constraint:规则,使用祈使语气。 |
| tags | array | 否 | 用于检索的标签。 |
| scope | string | 否 | constraint:'global' 或文件/模块路径。 |
| steps | array | 否 | pipeline:有序步骤。 |
| origin | enum | 否 | 作者(默认为 agent)。user | agent | import |
| reason | string | 否 | constraint:存在原因。至少 40 个字符。 |
| problem | string | 否 | decision:导致做出该决定的问题。至少 40 个字符。 |
| purpose | string | 否 | pipeline:存在原因。至少 40 个字符。 |
| summary | string | 否 | decision:所做的决定(1–2 句话)。 |
| hardness | enum | 否 | constraint:绝对要求还是建议。absolute | advisory |
| tradeoffs | string | 否 | decision:放弃了什么。建议填写。 |
| related_to | array | 否 | 相关条目的 ID;get_context 会遍历这些条目。 |
| supersedes | array | 否 | decision:此决定取代的先前决定的 ID。 |
| why_chosen | string | 否 | decision:理由。至少 60 个字符。 |
| constraints | array | 否 | 适用的流水线规则。 |
| enforced_by | string | 否 | 约束:用于检查它的测试/命令(仅命名,绝不运行)。 |
| project_dir | string | 否 | 目标项目的绝对路径。如有需要,创建 .context/。 |
| alternatives | array | 否 | 决策:考虑过的选项及拒否原因。 |
| what_we_tried | string | 否 | 决策:之前尝试过的方法。建议填写。 |
| when_to_invoke | string | 否 | 流水线:触发条件。建议填写。 |
| retrieval_hints | array | 否 | 用于检索的 2-4 种替代表述。 |
| constraints_created | array | 否 | 决策:由此引入的新约束。 |
| triggering_incident | string | 否 | 约束:其背后的事件。 |
详细介绍
Context Keeper
xylem 技术栈的一部分。
为 Claude 提供项目记忆。记录设计决策、流水线流程和约束,让 Claude 在不同对话中保持上下文。
问题
随着对话变长,Claude 会忘记早期决策背后的“原因”。新对话则从空白开始。这会导致 Claude 做出破坏既定模式的更改——例如重写它不记得存在的流水线步骤。
解决方案
Context Keeper 为 Claude 提供 14 种工具,用于记录和检索结构化项目上下文:
| 工具 | 用途 |
|---|---|
record_entry | 统一写入工具——通过 kind 记录决策、流水线或约束,并在服务端按类型验证字段。整合了原先的 record_decision/record_pipeline/record_constraint(为保持向后兼容,仍可通过这些名称调用) |
get_context | 按查询、标签、范围或 ID 检索相关条目——按相关性排序,默认提取 related_to 链接 |
query_entries | 按精确结构化字段筛选(状态、来源、标签、范围、硬性程度、取代关系、日期)——结果确定、不排序;区别于 get_context 的相关性搜索 |
get_project_summary | 为对话开始时提供简要概览 |
update_entry | 按 ID 更新任意条目 |
deprecate_entry | 根据原因停用条目(可选的 merge_into 会将重复条目合并到保留条目中) |
prune_stale | 查找近期未验证的条目 |
get_compaction_report | 检查上次压缩是否丢失了上下文 |
verify_quality | 扫描条目,检查理由是否过于简略、标签是否缺失、关联脉络是否孤立(由 PreCompact 钩子自动调用) |
export_markdown | 从决策存储重新生成 DECISIONS.md——这是只读的派生视图 |
reload_constraints | 按需在会话中重新显示仅包含约束的内容块(刷新规则,而非完整存储) |
export_snapshot | 将整个存储写入可提交的 .context-keeper/memory.json.gz,以便通过 git 共享项目记忆 |
import_snapshot | 将已提交的快照导入工作存储——不会破坏现有数据;首次使用且存储为空时会自动运行 |
mirror | 与可选的远程存储同步:op="pull" 将远程合并到本地(保留最新版本),op="backfill" 将本地推送到远程。未配置远程存储时不执行任何操作 |
所有数据都以人工可编辑的 JSON 文件形式存储在项目目录中的 .context/ 内。默认情况下零依赖,也可选用语义检索。
这与 Claude Code 内置记忆的关系
Claude Code 自带两种记忆机制:你手动编写的 CLAUDE.md 文件,以及 自动记忆,Claude 会将自由格式的笔记保存到 ~/.claude/projects/<project>/memory/。Context Keeper 并不取代其中任何一种——它的定位不同,而这些差异正是使用它的理由:
| 自动记忆 | Context Keeper | |
|---|---|---|
| 形式 | 自由格式的 Markdown,包含索引和主题文件 | 带有经服务端验证字段的类型化条目(决策 / 流程线 / 约束) |
| 深度 | Claude 写什么就是什么 | 由模式强制执行:problem ≥40 个字符,why_chosen ≥60 个字符,记录时会拒绝内容过于单薄的条目 |
| 生命周期 | 编辑或删除文件 | supersedes(降级,但仍可检索)、deprecate、merge_into、偏移 + 过时扫描 |
| 冲突 | 无 | 记录时将重述与矛盾分类,并根据来源确定信任优先级 |
| 检索 | 每个会话都会完整加载索引 | 在令牌预算内按相关性排序;对于无法回答的查询,会发出弃答信号 |
| 覆盖范围 | 仅限本机;明确不在多台机器间共享 | 通过镜像跨设备共享,通过已提交的快照与团队共享 |
它们可以互相补充,而非彼此竞争:自动记忆擅长轻松记录偶然偏好,而 Context Keeper 用于管理你希望结构化、可查询、可执行且可移植的决策和规则。两者同时运行没有问题——启用 rules_export 后,Context Keeper 会写入工具环境自身的 .claude/rules/ 目录,而不是绕开它。
如实说明已知缺陷: 子 Agent 不会继承主对话的会话启动注入,因此子 Agent 启动时没有项目摘要。如果你的工作流依赖子 Agent,请让它们显式调用 get_project_summary——检索不可跳过这一特性仅适用于主循环。
功能一览
Context Keeper 是一个轻量、离线优先的记忆层;它的一些功能不太容易被发现,因为它们融入了现有工具,而不是作为独立功能存在。下表用记忆系统术语介绍了这些功能:
| 功能 | Context Keeper 的实现方式 |
|---|---|
| 程序性记忆 | record_entry(kind="pipeline") 会存储有序且考虑依赖关系的工作流(构建/部署/数据流),并附带 purpose + when_to_invoke,形成可重复使用的“我们如何执行 X”,而不仅仅是事实。 |
| 去重 | 每次 record_* 都会针对存储库执行词集 Jaccard 比对;如果新条目重述了现有条目,就会返回 similar_entries,从而在录入时发现重复项。随后,deprecate_entry(merge_into=...) 会将重复项中的独有内容合并到保留项,并以非破坏性的单步操作将重复项退役。 |
| 矛盾检测 | 同样的重叠内容会被归类为 likely_restatement 或 likely_contradiction(基于否定词/反义词的极性);如果内容发生反转,就会生成 contradiction_note,提示 Agent 解决冲突,而不是让两条仍然生效的规则互相矛盾。 |
| 质量优化 | verify_quality 会扫描理由单薄、缺少标签、采用旧版架构的条目,以及孤立(未链接)的关系;PreCompact 钩子会自动运行该功能,以便在上下文压缩前丰富条目内容。 |
| 取代/衰减/遗忘 | supersedes 会降低先前决策的优先级,但仍予以保留(可回溯的历史记录);prune_stale 会列出未经验证的条目供审查;deprecate_entry 则会将条目彻底从检索中移除。 |
| 来源 + 信任/来源归属 | 每个条目都会记录 origin(user/agent/import);检索时会提高用户陈述条目的信任度,并在条目冲突时据此决定默认采用哪一条。 |
| 预期查询 | retrieval_hints 会存储未来会话可能使用的其他措辞,因此即使查询用词不同,也能在没有嵌入的情况下命中。 |
| 混合检索 | 默认使用词法检索(标签 + 词语重叠);选择启用的嵌入余弦混合检索(semantic.enabled)会增强向量召回,并在嵌入器离线时回退到词法检索。 |
| 事实元数据查询 | query_entries 会基于结构化字段的精确谓词筛选条目(状态、来源、任一/全部标签、范围、硬性程度、取代关系、日期),并以 AND 组合,结果具有确定性。这是一条精确查找路径,与 get_context 的模糊相关性排序不同。 |
| 便于缓存的注入 | 会话启动时的记忆块采用确定性排序,包含稳定前缀;每个会话唯一易变的行(质量扫描 ID)会放在最后,因此存储内容未变时,各会话注入的文本逐字节相同。 |
| 由路径触发的规则 | 范围限定的约束会投射为 Claude Code 自带的 .claude/rules/*.md 格式,并带有 paths: 前置元数据(rules_export);这样一来,Agent 读取受覆盖文件时,工具框架就会加载相应规则,早于编辑操作,且无需钩子。对于不支持规则的客户端,scope_guard PreToolUse 钩子会覆盖写入路径。 |
| 叙述 + 聚类 | get_project_summary 会将超过阈值的决策按主题聚类,并生成简洁叙述;DECISIONS.md 投影则将存储内容呈现为易读的文字。 |
| 数据导出/离线/隐私 | .context/ 中的数据采用纯 JSON 格式,可供阅读、编辑、grep 和提交;完全离线运行,无需任何依赖,也不会有数据离开本机。 |
评测与基准测试(开放方法)
检索和诚实性属性是通过测量得出的,而非仅仅声称如此——测试框架位于 evals/,无需联网即可复现:
- Token 减少率 — 会话开始时注入与倾倒完整存储内容的对比:四个真实存储的结果分别为 97.3% / 94.1% / 85.5% / 73.3%(
evals/token_reduction.py)。真正有意义的属性是:随着存储增大,注入成本仍大致保持稳定。 - 检索质量 — 在冻结的 7 个存储语料库上测试 59 个案例,每个问题都根据条目要解决的问题编写,而不是改述条目摘要。选择启用的语义混合将 recall@5 从 0.42 提升至 0.64,MRR 从 0.37 提升至 0.55(
evals/run_retrieval_eval.py)。这些数值低于 2026-08-05 之前发布的数据(hit@5 为 80% → 93%),这正是重点:旧测试集的部分问题是通过改述得出的,因此查询与目标共享表面词汇,导致词汇检索率虚高。提升主要集中在大型、以散文为主的存储上;小型存储的词汇检索率已经达到 1.000。 - 弃答 — 测量
get_context遇到无答案查询时是否会回答“没有相关内容”,而非凭空编造;在评测集上,0.20 的相关性下限是不会导致错误弃答的最高值(evals/abstention.py)。
每个数据集、指标和注意事项都已提交到代码库——详见 evals/README.md。语料库已冻结在 evals/fixtures/corpus 下,因此任何克隆版本都能复现这些数值,回归测试也可以将其固定;如果需要查看当前情况而非可对比的结果,可以使用 --live 对真实存储运行测试。
v0.19:以替代关系作为信号,并统一范围规则
-
存储可以说明什么是真的,却无法说明发生了什么变化。 它保存了 36 条记录,其中 36 条有效、0 条已弃用、0 条替代链接;而
dec-013明确表示它取代了旧的仅追加同步方式,con-006也在正文中标注了“(原为仅追加)”。deprecate_entry从 v0.10 起就支持superseded_by,但一次也没用过:知道答案时没有任何机制提出询问,之后也没有任何机制利用这个链接。现在,
record_*会指出涵盖同一主题的同类型有效记录——possible supersession of <id>: <summary>——评分依据是共有标签和整体组件范围的重叠,特意不使用similar_entries已经计算的文本重叠(替代项往往与被替代项几乎没有相同措辞,却恰好针对同一件事)。它不会建立链接、阻止写入或改动旧记录:启发式关联可能会悄悄降低一条仍然有效的规则的优先级。get_context会在最前面添加一行简短信息,说明直接前序记录的内容及其变更原因,因为只有检索时会利用链接,建立它才有价值。只追溯一层;预算紧张时会移除这一行,并保留predecessor_id线索,而不是移除记录。远程 Worker 会生成完全相同的一行,因此两种传输方式不会出现差异。scripts/survey_supersessions.py以只读方式提出补建链接的建议——它不会打开存储进行写入,也不会调用任何生命周期工具。 -
con-011是错误的。 它要求所有判断“此范围是否涵盖该文件”的入口保持一致;实际上有五个入口,却有四套实现,其中两套已被证实存在分歧,而且score_entry仍使用规则本来要禁止的原始子字符串测试——因此,查询scope="hooks/"时,范围为webhooks/的记录获得的加分与真正匹配的记录相同。现在scope_rules.py是唯一实现,它不导入任何内容,因此 PreToolUse 钩子可以在con-010的约束下加载它。 -
检索现在有了固定指标。 一个包含负例和历史记录案例的 59 案例黄金集,以及一个锁定词法分支行为的回归测试。第一个锁定测试在一天内就失效了,尽管排名代码一行未改——它读取的是实时存储,所以记录三条内容就会改变测试结果。现在语料库已冻结。
-
内部结构:
server.py从 4,270 行缩减至 3,787 行(拆分出ranking.py、mojibake.py、quality_checks.py,并全部重新导出);verify_quality中内联的七项检查改为使用注册表;测试套件也从一个 5,213 行的文件拆分为六个主题文件。
v0.17:弥补交付缺口
v0.16 在编辑之前将规则呈现给模型。之后对真实存储进行测量,发现了两个会相互叠加的问题,以及一个从一开始就存在的问题。
-
被截断的条目不再无法发现。 SessionStart 钩子只会打印摘要文本,不会输出其他内容,因此因预算限制而被丢弃的条目不仅没有摘要——Agent 也无从得知它的存在,自然无法主动索取。在最大的真实存储中,有 116 行记忆悄然消失。现在,摘要末尾会列出被丢弃条目的 ID,并将其计入同一预算之内(单独的 ID 只占其所代表条目的一小部分预算),同时按稳定的存储顺序输出,以确保提示词缓存前缀的字节完全一致。
summary_dropped_ids也会以结构化形式携带相同列表。必要时,约束条目的最低预算仍会优先于总预算——某个真实存储中的 19 条约束就占用了 2429 个令牌,超过 2000 的预算——因为丢失规则比超出预算更糟糕。超出情况会被报告,不再悄无声息。
-
global范围现在会被标记为质量问题。 真实项目中,47% 的约束范围为global,这使它们无法参与偏移检查、.claude/rules/投影以及scope_guard——因此,截断的摘要成了它们触达模型的唯一途径。这两个问题会相互叠加。现在,verify_quality会标记global_scope,但只有在能从条目的enforced_by或匹配真实目录的标签中确定具体路径时才会标记。对每条全局规则都笼统地建议“考虑添加范围”,只会制造噪声,让读者习惯性地忽略;而且有些规则确实应该是全局的。 -
子 Agent 也会获得项目规则。 SessionStart 不会为子 Agent 触发,它们也不会继承父 Agent 注入的上下文,因此此前每个子 Agent 启动时都没有项目记忆——一次扇出就会产生十几个从未读过规则的贡献者。新的
subagent_start.py钩子通过SubagentStart注入仅包含约束的区块。只注入约束,并且设有上限:每个启动的 Agent 都会触发一次,因此扇出会使注入量成倍增加。"SubagentStart": [ { "hooks": [{ "type": "command", "command": "python /path/to/context-keeper/hooks/subagent_start.py", "timeout": 5 }] } ] -
发布不再会悄无声息地部分失败。 v0.16.0 已发布到 PyPI 和 MCP registry,随后 bundle 工作流却因清单验证失败而中止——而缺少某个资产的发布,与从未包含该资产的发布无法区分。新增的
verify-release工作流会确认三个渠道都确实收到了该版本;清单也改为在每个 PR 上验证,而不是等到发布时才验证。 -
constraint_reinject不再走重量级导入路径。 它的匹配器是"",因此每次工具调用后都会运行——这是整个钩子集合中触发最频繁的钩子——而它之前为了格式化字符串列表还会导入server。约束渲染逻辑已移至store_paths,因此它和subagent_start现在都能在 Python 自身启动耗时的基础上约 5ms 内运行。仍然只有一份实现,供所有调用入口共享。
v0.16:按路径触发规则,以及编辑前的规则
此版本的两部分功能都源于阅读 Anthropic 自己的记忆文档,并思考 context-keeper 还没有利用其中哪些机制。
-
将带范围的约束投影到
.claude/rules/*.md(可选启用)。 Claude Code 在读取匹配文件时会加载带有paths:frontmatter 的规则文件——这比任何钩子都更早触发,而且无需接入任何钩子。rules_export.enabled会在每次约束变更的写入路径上,将每条带范围的约束镜像到.claude/rules/context-keeper/,每个范围对应一个文件。其契约与DECISIONS.md投影相同:JSON 保持为规范数据,Markdown 是派生内容,始终整体重新生成,且绝不会被解析回去。 该投影拥有的是一个子目录,而不是.claude/rules/本身, 并且只清理带有自身生成标记的文件——因此,已弃用约束的规则文件会在下次写入时消失,而放在同一文件夹中的手写规则则绝不会被触碰。该标记是块级 HTML 注释,Claude Code 会在文件进入上下文前将其剥离:它以零 token 成本向工具和人工读者标识该文件。如果作用域无法转换为实际生效的模式,则会拒绝而非输出:glob 元字符(
[ ] { } * ?),因为 Claude Code 会将[视为方括号表达式,而未闭合的方括号表达式不会匹配任何内容;以及双引号或控制字符,因为每个模式都会输出为双引号括起来的 YAML 标量,嵌入的引号会提前结束标量,导致整个 frontmatter 块失效——随后,运行环境不会从该文件加载任何规则,连原本有效的模式也不例外。被拒绝的作用域会返回在skipped_scopes中,因为悄无声息地不生效的规则文件比没有规则更糟:它看起来像是已有覆盖。rules_export.path必须位于项目目录内。该目录不仅会被写入,还会被清理——每次重新生成时,其中所有带标记的.md文件都会成为删除候选项——因此,配置中的../../..或绝对路径会将删除操作指向用户从未与 context-keeper 关联的目录。路径必须位于项目目录内也合乎情理:Claude Code 只会发现工作树内的.claude/rules/,因此外部目录无论如何都无法加载。越界路径会被拒绝并返回错误,而约束写入本身仍会成功——投影是派生数据,JSON 存储才是规范数据,配置错误的投影绝不能导致记录失败。 -
scope_guard现在在 PreToolUse 下运行。 它原先在 PostToolUse 上触发,也就是说规则是在写入已经完成之后才到达——那只是审查备注,不是护栏。PreToolUse 的additionalContext会与工具结果一同注入,因此模型仍能采取行动时就能收到约束。 一个脚本即可支持两种接线方式:它读取hook_event_name,并以匹配的hookEventName响应;如果字段缺失,则默认使用 PostToolUse,因此现有安装可原样继续工作,升级只需编辑配置,无需重写。可选择启用
scope_guard.confirm_absolute,将 PreToolUse 命中的绝对约束升级为permissionDecision: "ask",暂停操作并等待用户确认,而不只是添加注释。默认关闭。坦率地说,其限制是:它会根据作用域而非违规情况升级——这里没有读取 diff,因此无法判断编辑是否真的违反规则。 -
编辑路径现在有了不能悄悄超出的延迟预算。 改用 PreToolUse 意味着钩子会在工具执行之前运行,因此它的耗时会落在每次 Edit 和 Write 的关键路径上,而不是在操作结束后才产生。Windows 上的测量结果显示,钩子耗时约 142ms,其中约 73ms 是
import server——mirror引入了urllib.request→http.client→email.parser,还包括secrets、usage、code_drift,而这个钩子根本不会用到它们。更糟的是,没有匹配任何约束的路径也要耗费同样的 142ms,而这种情况占大多数编辑。存储位置和原始读取现在都位于
store_paths.py中,它只导入json和os。server.py现在从该文件导入路径解析逻辑,而不再自行定义,因此优先级顺序(环境变量 → Xylem 指针 → cwd → 向上遍历父目录)只有一个实现——第二份副本会逐渐偏离,而偏离的那份恰恰是无人执行的副本。 ~142ms → ~69ms,接近 ~62ms 的纯解释器开销下限。剩余部分是 Python 进程启动开销,shell hook 无法避免。现在,TestEditPathHookCost会在编辑路径 hook 再次导入server,或store_paths除json/os之外还引入其他导入时失败——这项特性已被固定,而非只是修好。按下方所示,用"timeout": 5配置 hook,以限制极端情况;对于卡住的 hook,没有其他机制能设定上限。 -
超出预算的摘要现在会被截断并报告。 截断会话启动摘要时,会从末尾删掉行;预算足够小时,它会一路删到约束块,导致摘要被清空——这是 v0.9 故障(状态看似正常的存储却没有注入任何内容)的另一种表现。现在,截断会在约束块处停止;触及上限时,响应会带上
summary_truncated/summary_lines_dropped/summary_truncation_note。这些键仅在发生截断时出现,因此未截断的常见情况仍能保持字节稳定,便于提示词缓存。Anthropic 的自动记忆索引在遇到读取上限时会报错,而不是静默丢弃内容;原则相同。 -
存储现在可以报告并修复自身的编码损坏。
con-008修复了 v0.11 乱码的成因:Windows 上的标准输入默认使用 cp1252,因此客户端的原始 UTF-8 字节在json.loads运行之前就被错误解码,导致破折号以â€"的形式写入存储。强制传输使用 UTF-8 后,就不会再有新条目损坏。但这无法处理已写入的条目,也没人检查它们。这种损坏以最糟糕的方式隐而不见:文本仍然足够易读,以至于没人会重新阅读该条目,因此损坏的理由会悄然影响每次检索到它的结果。现在,
verify_quality会将其标记为mojibake,而repair_mojibake处理程序可以修复它:context-keeper repair_mojibake '{}' # dry run, writes nothing context-keeper repair_mojibake '{"apply": true}' # repair修复过程会精确逆转损坏,并且会验证修复结果——对候选内容重新应用损坏过程后,必须逐字节还原为输入;未通过检查的内容会保持原样。对他人记录的推理进行不完整修复,比保留清晰可读的损坏更糟,因为它看起来像是已经修好。刻意不刷新
verified_at:修复编码并不意味着有人重新确认了条目仍然属实;重置陈旧度计时器会抹掉prune_stale和漂移检查本应发出的信号。updated_at会更新,这样修正后的副本就会在镜像的“最新优先”合并中胜出,而不会被损坏的远程副本覆盖。
在 .context/config.json 中启用投影,并执行一次回填:
{ "rules_export": { "enabled": true } }
context-keeper export_rules '{}'
export_rules 刻意不加入 tools/list——con-004 限制了每个客户端在会话启动时都要承担的 schema 负载,而这是一次性回填。此后,写入时渲染会让目录保持最新,无需调用工具。
要么提交它,要么忽略它,但务必与存储保持一致。 生成的目录派生自 .context/。如果你的工作存储被 gitignore 忽略(默认情况),也要将 .claude/rules/context-keeper/ 加入 gitignore——否则,新克隆的仓库首次写入约束时,会从一个并不存在的存储重新生成内容,并删除所有已提交文件。如果你通过 export_snapshot 共享记忆,提交 rules 目录没有问题,还能让新队友立即获得适用范围明确的规则。
v0.15:双向镜像(本地 <-> 远程)
可选的容错镜像功能,让第二台设备(例如外出时用来记录决策的手机)既能接收桌面端的记忆,也能贡献自己的记忆。本地 .context/ JSON 存储仍然是规范数据源;context-keeper-remote Cloudflare Worker 是同步界面,绝不是事实来源。(这与提交到 Git 的 export_snapshot 机制不同——镜像是实时的、跨设备的,而 snapshot 是仓库中的版本化数据包。)
冲突解决采用按 updated_at 时间戳后写入者胜出的策略,且两个方向的规则完全相同,因此在任一设备上进行的编辑最终都会在所有设备上保持一致。任何一端都不会删除内容——弃用只会更改状态,并像其他编辑一样传播。
坦诚说明时钟偏差带来的问题。“后写入者”通过比较 ISO-8601 updated_at 字符串来确定,而这些时间戳来自执行写入的机器——本地编辑使用桌面设备的系统时间,远程编辑使用 Worker 的系统时间。若在两台机器的时钟偏差范围内(实际通常不到一秒,且发生在不同设备上)对同一条目进行两次编辑,顺序就可能错乱:时间戳较晚的副本不一定是较晚写入的,因此保护逻辑可能会选错胜出版本。没有逻辑时钟或向量时钟来打破平局——只有系统时间。影响范围有限(单次拉取只会将远程端自身的时间戳相互比较,且这些时间戳来自同一时钟,因此远程端内的顺序准确;只有在本地写入与远程写入同时修改同一 id 时,时钟偏差才会造成影响),而且绝不会悄无声息地发生:每当“最新者胜出”覆盖了内容实质不同的副本时,落败版本都会追加到 .context/.mirror_conflicts.json(见下文),以便你手动协调。
- 镜像同步(本地 -> 远程)。 每次写入(
record_entry/record_*、update_entry、deprecate_entry)后,都会通过远程服务的upsert_entriesMCP 工具推送该条目(每种类型调用一次)。upsert_entries会保留传入的 ID,且仅当推送条目的updated_at更新时才会替换已有的远程副本,因此编辑或弃用条目会实际覆盖过时的远程副本,而不会被跳过。如果远程服务无法访问,该条目会加入.context/.mirror_queue.json队列(去重并保留最新状态),并在下次成功推送时刷新队列。推送失败绝不会阻止或导致本地写入失败。 - 镜像拉取(远程 -> 本地)。
pull_remote会调用远程服务的query_entries,并按时间戳合并所有返回的行:如果本地已有同 ID 的远程条目,仅当远程条目的updated_at更新时才会覆盖本地副本;如果本地副本更新,则保留本地副本,并在下次写入时推送回远程服务。.context/.mirror_watermark现在仅作记录提示:每条获取的记录都会按每个条目执行“保留最新版本”的合并,因此绝不会仅因远程条目的时间戳不晚于水位标记就将其丢弃(早期版本会在合并前按水位标记过滤,这可能因设备间时钟偏差而悄悄丢失手机记录的条目——而这正是镜像同步要解决的情况)。此功能已接入 SessionStart 钩子(因此桌面会话开始时就能看到手机记录的条目),并通过mirrorMCP 工具以op="pull"的形式提供。 - 保留冲突,而不是丢弃。 任一方向覆盖实质内容不同(而非仅时间戳不同)的副本时,落败版本会追加到
.context/.mirror_conflicts.json。最后写入者胜出已决定哪个副本作为当前版本;此记录用于追踪被替换的内容。没有冲突解决界面——如需手动协调,可查看此记录。 - 回填(
mirror配合op="backfill")会将整个本地存储推送到远程服务(每种类型调用一次upsert_entries),用于初始化全新的远程服务。此操作幂等:服务器端会跳过相同或更旧的重复推送。 - 避免 ID 冲突(方案 B:随机后缀)。 两个存储各自独立生成递增 ID 会产生冲突——桌面端和 Worker 都会为不同的决策分配
dec-013,而按(project, id)键控的 upsert 会导致一方悄悄覆盖另一方。解决方法:启用镜像同步后,新 ID 会添加一个短随机十六进制后缀,例如dec-013-a7f3。数字仍在开头(便于排序和搜索);旧 ID 永不重写;关闭镜像同步时,ID 仍保持原样dec-013(只有一个写入方,不会发生冲突)。 - 不新增依赖(仅使用标准库
urllib),代码中不存放密钥:远程 URL 将身份验证令牌作为最后一个路径段(/mcp/<token>),且仅从环境变量读取。
在运行 MCP 服务的位置设置一个环境变量即可启用:
CONTEXT_KEEPER_REMOTE_URL=https://context-keeper-remote.<acct>.workers.dev/mcp/<AUTH_TOKEN>
# CONTEXT_KEEPER_REMOTE_TIMEOUT=5 # optional per-request seconds
如果未设置 CONTEXT_KEEPER_REMOTE_URL,所有镜像路径都会静默地不执行任何操作——行为与 v0.15 之前完全相同。
**传输方式:**基于 Streamable HTTP 的无状态 JSON-RPC。每次写入都会向 /mcp/<token> URL 发送一个 POST(tools/call → upsert_entries);拉取操作(mirror 配合 op="pull")会调用远程服务的 query_entries。无需 initialize/session 握手(服务无状态);响应为单个 application/json 正文。
v0.14:去重合并(deprecate_entry(merge_into=...))
捕获时的检测已经能发现近似重复项(similar_entries 中带有 likely_restatement 关系),但要解决此类问题,仍需手动分两步操作:弃用重复项,然后对原条目执行 update_entry,补入它遗漏的内容。v0.14 将这两步合并为一个原子性、非破坏性的操作。
- 在现有工具中加入可选参数,而不是新建工具。
deprecate_entry(id=<dupe>, reason=..., merge_into=<survivor>)会将重复项中独有的内容并入保留条目,然后弃用重复项,并设置superseded_by=<survivor>。如果未提供merge_into,deprecate_entry的行为与之前完全一致——逐字节不变。 - 只增不减,非破坏性。 保留条目只会增加内容:列表字段(
tags、retrieval_hints、related_to、constraints/constraints_created)会合并去重,空文本字段会从重复项补齐——保留条目中已有内容的字段绝不会被覆盖。重复项不会被硬删除;它会作为已弃用条目保留在磁盘上,并指向保留条目,因此合并过程完全可审计、可逆。 - 类型相同、单次写入、先验证。 合并要求两个条目类型相同(以确保它们的架构一致);在任何写入之前,先解析并验证目标;然后在一次文件写入中修改两个条目,避免两项更新相互覆盖。无效的
merge_into(目标不存在、类型不同或指向自身)会明确报错,不会弃用任何条目。 - 保留 Roots。 操作由用户明确发起,也可由 Agent 调用(与其他所有生命周期工具一样);不增加新依赖、不调用大模型,且结果确定。它简化了捕获循环已有规范的重述处理流程,而不是新增后台进程。
// dec-002 restates dec-001 — merge and retire it in one call
{ "id": "dec-002", "reason": "Restatement of dec-001", "merge_into": "dec-001" }
// -> dec-001 gains dec-002's unique tags/hints/related_to + any text it lacked;
// dec-002 becomes deprecated with superseded_by = dec-001
v0.13:结构化字段查询(query_entries)
get_context 回答的是*“与我正在做的事情相关的内容有哪些?”*——它按相关性排序,融合可选语义信息,并通过弃答信号标记低相关性结果。这适合模糊召回,但不适合你已明确所需字段值的情况。query_entries 填补了这一空白:对每个条目已有的结构化字段进行确定性筛选,不排序,也不弃答。
- 精确条件,按 AND 组合:
types、status(active/superseded/deprecated)、origin(user/agent/import)、tags_any、tags_all、scope(精确匹配,区分大小写)、hardness(absolute/advisory)、supersedes/superseded_by,以及与get_context相同的since/before时间筛选条件。每个条件都严格匹配现有字段——查询要么匹配,要么不匹配。 - 不涉及相关性,也不会编造。 结果按稳定的自然 ID 顺序返回,不包含分数,也没有
min_relevance下限——空结果集就是实际且诚实的答案,不是弃答消息。弃答机制适用于模糊文本查询;结构化条件无需弃答。 - 相同的数据存储,相同的预算。 它复用
get_context使用的完全相同的数据存储读取和条目序列化路径,并将匹配结果集装入相同的令牌预算(默认为 4000,每次调用可通过token_budget设置),因此宽泛查询不会一次性倾倒整个数据存储——matched_entries与entries_returned,以及budget_truncated标记会告知你是否有内容因上限而被截断。 - 增量式且独立完整。 不增加新依赖,仅使用标准库;不使用嵌入,也不调用大模型——只对磁盘上已有的 JSON 进行纯内存筛选。
get_context、语义融合、评分和所有钩子均不受影响;每个现有工具的默认行为都逐字节不变。
与 get_context 有一个有意的区别:query_entries 不应用默认状态筛选,因此除非你传入 status,否则也会返回 superseded 和 deprecated 条目。get_context 始终隐藏已弃用条目;结构化工具则允许你按需查询它们。
示例:
// Absolute constraints scoped to the hooks/ directory
{ "types": ["constraints"], "hardness": "absolute", "scope": "hooks/" }
// User-stated decision that superseded dec-005
{ "origin": "user", "supersedes": "dec-005" }
// Active pipelines tagged "release"
{ "types": ["pipelines"], "status": "active", "tags_any": ["release"] }
// Everything a user asserted this month, across all types
{ "origin": "user", "since": "2026-07-01" }
v0.12:矛盾检测 + 缓存稳定的注入
- 在捕获时区分重述与矛盾。 相似条目检查已经能发现大量重叠;现在还会对每组重叠进行分类。两种无需依赖项的信号——否定不对称(“X 是必需的”与“X 不是必需的”)和反义词极性(这里是“始终”,那里是“从不”;“启用”与“禁用”)——会将匹配项标记为
likely_restatement或likely_contradiction。重述会提示你合并;矛盾会提出contradiction_note,告知 Agent 应确定哪条规则仍然有效(使用带有superseded_by的deprecate_entry),而不是悄悄保留两条互相冲突的有效规则。仅供参考,而且只会检查 Jaccard 已标记为重叠的条目对——写入始终会继续。没有新增依赖项,不调用 LLM,记录时也不增加令牌数。 - 会话开始注入保持缓存稳定。 注入的记忆块会按顺序排列,使其中较大的稳定部分——约束、决策、管线和固定的捕获指南——在存储未变化时,能在不同会话间逐字节保持一致,形成前缀;唯一易变的那一行(质量扫描标记的 ID)会放在最后。这样,记忆块就能留在模型可缓存的提示词前缀中,而不会在每个会话中都导致缓存失效。令牌数还会减少,而不是增加。
v0.11:会话中途重新注入约束(可选启用)
SessionStart 钩子会在第一轮注入一次你的约束。随着长会话中逐渐积累工具输出,这些规则会从模型的工作注意力中逐渐淡出,实际上形同衰减——模型可能仅仅因为一小时前获知的约束已被埋没,就违反该约束。v0.11 会在长会话进行中重新呈现这些约束,而不只是在会话开始时呈现。
有两种方式,都仅限约束——它们会重新注入 SessionStart 显示的完整 Absolute/Advisory 区块,不包含存储中的其他内容(无决策、无管线)。这是一次轻量级的规则刷新,而不是再次完整倾倒所有内容。
reload_constraints工具——按需返回当前约束区块。始终可用;想让规则重新进入上下文时,随时调用。constraint_reinject.py钩子(PostToolUse)——可选启用,默认关闭。 启用后,它会统计每个会话中的工具调用,并通过additionalContext每隔 N 次调用重新注入约束区块(every_n_tools,默认值为 25)。
说实话,什么会触发它。 自动路径是 PostToolUse 钩子——这个界面会注入模型,而它的触发频率与工具输出量同步;正是工具输出量实际埋没了规则。它不是计时器:MCP 服务在上下文窗口内没有墙上时钟,因此重新注入由工具调用次数驱动,而不是由经过的秒数驱动。它也不是 PreCompact——PreCompact 的标准输出只会显示给用户,不会注入模型(压缩边界已经由 SessionStart 钩子重新覆盖,该钩子会以 compact 为来源再次触发)。
默认行为保持不变。 未配置(或设为 enabled: false)时,钩子不会执行任何操作,SessionStart 的工作方式与之前完全相同。在 .context/config.json 中启用它:
{ "constraint_reinjection": { "enabled": true, "every_n_tools": 25 } }
v0.10:弃答 + 将取代关系用作排序信号
从研究 Curion 中借鉴了两个想法,同时保持零依赖:
get_context现在可以表示“没有相关内容”。 以前它总是返回得分最高的条目——但综合得分无论相关性如何,都会从时效性/状态/来源中累积约 55 分,因此即使查询中没有相关记忆,也会悄悄返回看起来很有把握的结果。测得无答案查询的编造率为 100%(evals/abstention.py)。现在,响应会包含top_relevance;当排名最高的条目的标签/文本相关性低于min_relevance(配置项,默认值为 0.20)时,会返回no_confident_match: true,并附上指导,提醒 Agent 不要把这些条目当作已证实的事实。它只添加标注,不会屏蔽结果——仍会返回匹配度较低的条目,因此retrieval_hints和语义混合所保留的词汇不匹配召回能力不会受到影响。0.20 是在评估集上不会产生误弃答的最高阈值。- 将取代关系用作排序信号,而不只是过滤条件。
record_decision接受supersedes: [ids]:先前的决策会变为superseded——在排序中降级,但仍可召回(例如“我们为什么从 X 改为现在这样?”);这与会将条目完全移出检索范围的deprecate_entry不同。prune_stale/verify_quality会跳过已取代的条目(它们是有意保留的历史记录,不是过时工作),并在DECISIONS.md投影中标记为**SUPERSEDED** by dec-NNN。
我们刻意没有采用 Curion 的 LLM 控制器架构(每次存储和召回都要调用 API)。context-keeper 仍保持零依赖,且默认离线运行。
v0.9:主题聚类、更多嵌入后端,以及测量发现的一个错误
- 关键修复:大型存储的会话开始注入内容为空。 摘要截断循环在判断条件中检查的是原始文本,因此只要存储的摘要超过令牌预算(约 30 多个条目),就会悄悄弹出所有行,并在会话开始时注入空摘要。这个问题是在测量令牌减少量时发现的:一个包含 78 个条目的存储注入了约 0 个令牌的记忆。现在会正确截断至预算范围内。
- 主题聚类。 当决策超过 8 条时,
get_project_summary会根据决策中出现频率最高的共同标签对其分组,而不再生成一份平铺列表——包含 59 条决策的存储现在会按十几个主题呈现。 - 兼容 OpenAI 的嵌入。
semantic.api: "openai"可将语义混合指向任何/v1/embeddings端点,例如 LM Studio、llama.cpp server 或 OpenAI 本身(api_key_env用于指定保存密钥的环境变量名称)。默认仍使用 Ollama,并保留相同的故障安全词法回退机制。现在只有 nomic 模型会使用 nomic 任务前缀。 - 感知可信度的冲突指导。
similar_entries匹配结果现在会包含每个条目的origin,指导内容也会说明优先级:用户陈述高于 Agent 推断,Agent 推断高于导入内容。 - 令牌减少量测量(
evals/token_reduction.py),针对四个真实存储运行:
| 存储 | 活跃条目 | 完整存储(令牌) | 会话开始时注入量 | 减少量 |
|---|---|---|---|---|
| balatron | 78 | ~75,277 | ~2,057 | 97.3% |
| clark | 55 | ~35,445 | ~2,102 | 94.1% |
| context-keeper | 13 | ~5,692 | ~828 | 85.5% |
| conductor | 9 | ~1,538 | ~411 | 73.3% |
基准 = 将所有活跃条目都写入上下文;注入量 = SessionStart Hook 输出的 get_project_summary 内容。需要坦诚说明的是:摘要受预算上限约束(默认 2000 个令牌),因此大型存储的部分减少量是由设计本身决定的——真正重要的是,随着存储增长,注入成本仍保持稳定。
- 新增六种 MCP 客户端文档(OpenCode、Copilot CLI、Antigravity、OpenClaw、Hermes、pi/oh-my-pi)——请参阅下方的 Other MCP clients。
v0.8:DECISIONS.md 投影(写入时渲染)
可选启用:将决策存储镜像到项目根目录下的人类可读文件 DECISIONS.md。在 .context/config.json 中启用:
{ "markdown_export": { "enabled": true, "path": "DECISIONS.md" } }
- 写入时渲染。 每次会修改决策的工具调用(
record_decision、update_entry、deprecate_entry)都会在写入 JSON 后、工具返回前,根据decisions.json重新生成整个文件——因此后续的git commit会在同一次提交中包含这两个文件。特意不使用 git/PostToolUse 钩子:在提交快照之后渲染会重新引入不一致。 - JSON 仍是规范数据;Markdown 是派生且只读的。 每次都会重新生成整个文件——绝不追加、合并或反向解析。手动编辑不会保留;重新生成的投影不会产生不一致。
export_markdown工具可按需重新生成(也可以指定自定义path),因此现有仓库无需启用此标志即可补齐文件。- 仅使用标准库进行字符串格式化;关闭此标志时,默认行为逐字节保持不变。
此功能源于实际使用:Balatron 一直靠手动编辑镜像文件,让 DECISIONS.md 与存储保持同步,每次提交都要编辑一次。现在,这一约定已实现自动化。
v0.7:预期查询、来源可信度、时间线筛选
retrieval_hints(所有record_*工具):提供 2-4 种未来会话可能搜索的替代表述——同义词、症状描述、错误消息。系统会同时为词法和语义检索建立索引,因此即使词汇不匹配(例如“value network diverging”和“value head saturating”),也能在不使用嵌入的情况下命中。这是语义混合检索的零依赖补充。origin+ 可信度加权(所有record_*工具):条目会记录作者身份——user(明确陈述)、agent(根据会话推断)或import(回填)。检索评分会提高用户陈述条目的可信度权重;Agent 推断的条目权重次之,导入条目再次之。v0.7 之前的条目按agent计分,以保留它们之间的相对顺序。get_context中的since/before:根据每个条目的已验证时间戳或创建时间戳进行时间筛选——现在可以查询“这个月我们做了什么决定”。
v0.6:捕获时防护机制
- 注入指定范围的约束。 新增
scope_guard.py钩子(在Edit|Write|NotebookEdit的 PostToolUse 上运行):当 Agent 编辑受某个约束scope覆盖的文件时,该约束会立即通过additionalContext注入上下文。会话开始时的注入只会在第一轮向模型简要说明一次;此机制则会在规则即将发挥作用的确切时刻强制执行。每个约束在每个会话中最多触发一次。 - 记录时显示相似条目。
record_*现在会将新条目与存储中的条目进行比较(使用词集 Jaccard,相似度阈值可通过similar_threshold配置),并在现有条目高度重叠时返回similar_entries——这样可以在捕获时发现重复陈述和矛盾,而不必依靠 MMR 在检索时缓解重复。此功能仅提供提示:写入始终会继续。
v0.5:数据完整性与检索修复
- 原子写入。 条目文件会先写入临时文件,再通过
os.replace替换,因此即使写入期间发生崩溃,也不会再留下被截断的 JSON 文件。 - 损坏存储保护。 如果条目文件存在但无法解析,
record_*/update_entry/deprecate_entry现在会拒绝写入(以前,损坏的文件会被读取为空文件,下一次记录时便会悄无声息地用一条记录替换整个历史)。只读工具仍会优雅降级。 update_entry强制执行架构。 结构化字段(why_chosen、problem、reason、purpose等)在更新时也会进行最小长度校验,因此条目在记录后不会变得空洞。- 更好的预算打包。
get_context会跳过超出令牌预算的条目,继续打包较小的条目,而不是在遇到第一个超大条目时停止。 - 更新鲜的压缩报告。 SessionStart 钩子现在会自行运行快照比较(压缩后会立即以来源
compact触发 SessionStart,早于任何 Stop),因此注入的报告不会落后一次压缩。它还会注入一行质量扫描提示,这是verify_quality对模型可见的入口(PreCompact 的标准输出只会显示给用户,而不会显示给模型)。 - 语义层已包含在软件包中(
semantic_index.py之前缺失于 wheel/sdist),并加入了批量嵌入请求,每次查询还减少了一次 HTTP 往返。
v0.4:结构化理由与脉络关联
早期版本使用单个自由格式的 rationale 字段。实际上,Agent 写出的往往是一行摘要,而非完整推理,违背了设计初衷。v0.4 从三个方面解决了这个问题:
- 架构强制深度。
record_decision要求提供problem(至少 40 个字符)、why_chosen(至少 60 个字符),并接受可选字段what_we_tried和tradeoffs。record_pipeline要求提供purpose。record_constraint要求reason至少 40 个字符,并接受可选字段triggering_incident。服务端会拒绝内容单薄的条目,并针对具体字段给出指导——走捷径不再能生成有用的条目。 - 通过
related_to关联脉络。 每个条目都可以引用相关条目的 ID。默认情况下,get_context会遍历这些关联(深度为 1),因此检索某个决策时,也会一并获取其余相关脉络。关联信息能够跨会话保留。 - 质量验证。 新增的
verify_quality工具会扫描旧版条目、推理单薄、缺少标签和孤立条目(标签重叠但没有related_to关联)。PreCompact钩子会自动调用该工具,并标出相关条目,以便在上下文被压缩前补充完善。
旧版条目(v0.4 之前)仍然有效——它们不会被自动拒绝,只会由 verify_quality 标记,以供选择性补充。为保持向后兼容,已弃用的 rationale 参数仍可用于 record_decision(会自动映射到 why_chosen),但仍必须提供 problem。
安装
根据客户端不同,有两种安装方式。Claude Desktop 用户可以使用一键安装包;其他用户则使用标准的 stdio 服务。
选项 A — Claude Desktop 一键安装包(.mcpb)
Context Keeper 以 MCPB 桌面扩展的形式提供:
一个 .mcpb 文件,无需修改任何配置即可安装。
- 从发布页面下载
context-keeper-<version>.mcpb。 - 双击该文件(或将其拖入 Claude Desktop → Settings → Extensions)。
- 出现提示时,选择一个存储目录——存放项目记忆的文件夹(系统会在其中创建一个
.context/子文件夹,用于存储可读的 JSON 文件)。然后启用扩展。
就是这么简单——无需使用 pip,也无需编辑 JSON。该安装包仅使用 Python 标准库,因此无需安装第三方依赖。(Claude Desktop 为 .mcpb Python 扩展提供 Python 运行时;你需要确保系统中有可用的 Python,才能启动扩展。)
该安装包通过 scripts/build-mcpb.sh 从此仓库可复现地构建,CI 会自动将其附加到每个版本的 GitHub Release 中。
选项 B — pip + stdio(Claude Code、Cursor、Codex 及任何 MCP 客户端)
pip install context-keeper-mcp
Claude Code
claude mcp add --scope user context-keeper -- python /path/to/context-keeper/server.py
Claude Desktop(手动配置)
相比 .mcpb 安装包,你更倾向于手动编辑配置?将以下内容添加到 claude_desktop_config.json:
{
"mcpServers": {
"context-keeper": {
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": {
"CONTEXT_KEEPER_PROJECT": "/path/to/your/project"
}
}
}
}
其他 MCP 客户端(Cursor、Codex CLI、Gemini CLI、Windsurf,……)
此服务是标准的 stdio MCP 服务,因此任何支持 MCP 的客户端都可以使用它——钩子是 Claude Code 的附加功能,并非必需。将客户端的 MCP 配置指向 python /path/to/context-keeper/server.py,并设置 CONTEXT_KEEPER_PROJECT:
Cursor(~/.cursor/mcp.json 或项目级 .cursor/mcp.json)和 Windsurf(~/.codeium/windsurf/mcp_config.json)使用与 Claude Desktop 相同的配置格式:
{
"mcpServers": {
"context-keeper": {
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
OpenAI Codex CLI(~/.codex/config.toml):
[mcp_servers.context-keeper]
command = "python"
args = ["/path/to/context-keeper/server.py"]
env = { "CONTEXT_KEEPER_PROJECT" = "/path/to/your/project" }
Gemini CLI(~/.gemini/settings.json)使用与上方 Cursor 相同的 mcpServers JSON 格式。
GitHub Copilot CLI(~/.copilot/mcp-config.json)和 oh-my-pi(mcpServers 配置)使用带有 "type": "stdio" 的 mcpServers 格式:
{
"mcpServers": {
"context-keeper": {
"type": "stdio",
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
OpenCode(opencode.json):
{
"mcp": {
"context-keeper": {
"type": "local",
"command": ["python", "/path/to/context-keeper/server.py"],
"environment": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
Antigravity(~/.gemini/config/mcp_config.json 或工作区 .agents/mcp_config.json)和 OpenClaw(openclaw.json)使用带有 command/args 的 mcpServers 格式,与上方的 Copilot 相同。
Hermes(~/.hermes/config.yaml):
mcp_servers:
context-keeper:
command: "python"
args: ["/path/to/context-keeper/server.py"]
env:
CONTEXT_KEEPER_PROJECT: "/path/to/your/project"
如果不使用 Claude Code 钩子,你将失去自动的会话启动注入和编辑时约束保护——请在对话开始时调用 get_project_summary,并在工作过程中调用 record_*(工具说明中也会提示你这样做)。
将 CONTEXT_KEEPER_PROJECT 设置为项目根目录。如果未设置,服务会按以下顺序解析项目目录:
CONTEXT_KEEPER_PROJECT环境变量(显式选择——可信)- 当前工作目录,前提是其中已包含
.context/目录 - 从当前工作目录向上遍历父目录,查找已有的
.context/(类似 Git 的发现方式——从项目的任意子目录启动服务时,也能找到项目) - 否则:拒绝操作,且
record_*会返回“项目未解析”错误
步骤 2 和 3 只会解析已包含 .context/ 的目录。服务绝不会隐式创建该目录,因此你不会因在错误位置启动服务而意外污染父目录。向任意工具显式传入 project_dir,即可强制创建新项目。
工作原理
记录上下文
做出设计决策时:
You: Let's use JSON files instead of SQLite for storage.
Claude: [calls record_entry(kind="decision") with summary, problem, why_chosen,
alternatives, and optionally what_we_tried + tradeoffs + related_to links]
确定工作流时:
You: The deploy pipeline is: run tests, build, push to registry, deploy.
Claude: [calls record_entry(kind="pipeline") with ordered steps]
设定规则时:
You: Never run Conductor from source. Always use the exe.
Claude: [calls record_entry(kind="constraint") with rule, reason, and hardness=absolute]
检索上下文
对话开始时,SessionStart 钩子会将项目摘要(以及任何压缩差异报告)直接注入上下文——无需调用工具,因此即使任务导向的第一轮对话中,也不会跳过检索。你仍可按需调用 get_project_summary。进行修改前,Claude 会使用相关标签调用 get_context,以检查是否存在冲突。
相关性评分
无需嵌入或外部服务,Context Keeper 会通过以下方式为条目评分:
- 标签匹配 — 查询与条目标签的重合度
- 文本匹配 — 查询词在摘要/理由/规则文本中出现
- 新近度 — 最近验证过的条目得分更高
- 状态 — 优先显示有效条目,而非已被取代的条目
结果会受可配置的令牌预算限制(默认:4000 个令牌)。
Claude Code Hook 设置
Context Keeper 包含一些 hook,可在会话开始时注入项目记忆、提醒 Claude 在每次 git 提交后进行记录、在 Claude Code 压缩上下文前创建快照,并在之后检测是否有内容丢失。
将以下内容添加到 Claude Code hooks 配置(~/.claude/settings.json):
{
"hooks": {
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/pre_compact.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/post_compact.py"
}
]
}
],
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/session_start.py"
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/scope_guard.py",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/commit_capture_reminder.py"
}
]
},
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/constraint_reinject.py"
}
]
}
]
}
}
只有当 .context/config.json 中设置了 constraint_reinjection.enabled 时,constraint_reinject.py 条目才会启用(默认关闭)——添加配置不会产生影响,除非你主动启用它。它的匹配器为 ""(每次工具调用),因此每个会话的计数器会在所有活动中递增。
将 /path/to/context-keeper 替换为实际安装路径。如果项目不在当前工作目录中,请设置 CONTEXT_KEEPER_PROJECT 环境变量。
Windows 用户: 使用正斜杠(C:/Users/.../context-keeper/hooks/pre_compact.py),或在 JSON 中将反斜杠双重转义。单个反斜杠会被 shell 错误处理。
这些 hook 构成了完整的记录与检索闭环:
- SessionStart — 导入服务器自身的处理程序,并将项目摘要(以及任何压缩差异报告和一行质量扫描提示)直接打印到 stdout,Claude Code 会在第一轮将其注入上下文。它还会在读取报告前自行运行压缩后的快照比较。压缩后,SessionStart 会立即以来源
compact触发,早于任何 Stop 钩子,因此可以确保注入的报告保持最新。这取代了旧做法:打印一条要求调用工具的指令——由于工具会延迟提供,这种请求总会输给以任务为先的第一轮。若项目尚无.context/,则保持静默;输出仅包含 ASCII 字符,因此不会因 Windows cp1252 stdout 而崩溃 - PostToolUse (Bash) — 每次 Bash 工具调用后触发;当命令包含
git commit时,会注入提醒,要求在同一工作周期内记录相应的决策/约束/注意事项。提交是最佳的记录触发时机——那正是某件事变得足够确定、值得持久保存到版本控制中的确切时刻。源于实际使用经验:在事故频发的会话中,Agent 会把记录工作攒到“之后”,结果一夜之间提交了十几个 commit,用户不得不三次要求“update context keeper” - PreToolUse (Edit|Write) —
scope_guard.py:当 Agent 即将写入受某项约束的scope覆盖的文件时(例如,约束的范围限定为hooks/,而编辑目标是hooks/session_start.py),该约束会通过additionalContext在写入执行前注入。会话开始时会简要说明规则;此机制则会在编辑时刻将规则呈现在模型面前。每个会话中,每项约束仅触发一次。直到 v0.15,它都接在 PostToolUse 下——这样仍然有效(钩子会检测自身事件,并默认使用 PostToolUse),但规则是在编辑完成后才到达,因此它更像审查意见,而非护栏。可选项scope_guard.confirm_absolute会在命中绝对约束时升级为向用户请求确认;请注意,它在范围匹配时触发,而非在实际违规时触发 - PostToolUse (任意工具) —
constraint_reinject.py:需主动启用,默认关闭。 设置constraint_reinjection.enabled后,它会统计每个会话中的工具调用次数,并每隔every_n_tools次调用通过additionalContext重新注入仅含约束的区块,避免会话开始时注入的规则随着工具输出不断增加而逐渐淡出。选择 PostToolUse 是经过刻意考虑的:它是模型可见的界面(与 PreCompact 不同),且其触发频率会随工具输出量变化。这不是计时器——MCP 服务在上下文窗口中没有墙上时钟 - PreCompact — 为所有启用的
.context/条目创建快照并运行质量扫描(verify_quality),将标记出的条目(推理单薄、缺少标签、脉络孤立)打印到对话记录中。注意:PreCompact 的 stdout 仅对用户可见——Claude Code 不会将其注入模型上下文,因此模型可见的质量提示改由 SessionStart 钩子提供 - Stop — 运行与 SessionStart 相同的快照比较作为安全网,以防会话结束后没有新会话启动(具备幂等性——若快照自上次比较后未发生变化,则跳过)
这就闭合了记录循环:SessionStart 在第一轮注入检索内容,提交提醒让记录工作锚定在变更落地的时刻,PreCompact 充当压缩前的安全网,Stop 则负责完整性检查。检索不可避免;而记录现在会在恰当的时机得到提示,不再依赖 Agent 在任务中途自行决定。
数据存储
your-project/
.context/
decisions.json # Design decisions with rationale
pipelines.json # Multi-step workflows
constraints.json # Rules and invariants
config.json # Token budget, stale threshold
embeddings.json # Semantic-retrieval vector cache, keyed by entry text (auto-generated, only when semantic enabled)
compaction_snapshot.json # Pre-compaction snapshot (auto-generated)
compaction_report.json # Post-compaction diff report (auto-generated)
reinject_state.json # Per-session tool counter for constraint re-injection (auto-generated)
scope_guard_state.json # Per-session record of already-injected scoped constraints (auto-generated)
.mirror_queue.json # Queued mirror-out writes pending a reachable remote (auto-generated)
.mirror_watermark # Newest remote timestamp already pulled (auto-generated)
.mirror_conflicts.json # Substance-differing versions overwritten by newest-wins (auto-generated)
hook.log # Hook activity log
mirror.log # Mirror (local<->remote) activity log (auto-generated)
.claude/
rules/
context-keeper/ # Scoped-constraint rules projection, one file per
# scope (only when rules_export.enabled). Derived
# and regenerated whole -- do not hand-edit. Files
# without the generated marker are left alone.
所有文件均为人类可读的 JSON。你可以直接编辑。ID 按顺序排列且易于识别:dec-001、pipe-001、con-001。
配置
创建 .context/config.json 进行自定义:
{
"project_name": "my-project",
"token_budget": 4000,
"max_entry_tokens": 1000,
"stale_threshold_days": 30,
"min_relevance": 0.20,
"markdown_export": {
"enabled": false,
"path": "DECISIONS.md"
},
"rules_export": {
"enabled": false,
"path": ".claude/rules/context-keeper"
},
"constraint_reinjection": {
"enabled": false,
"every_n_tools": 25
},
"scope_guard": {
"confirm_absolute": false
},
"semantic": {
"enabled": false,
"weight": 150,
"model": "nomic-embed-text",
"url": "http://localhost:11434",
"api": "ollama",
"api_key_env": ""
},
"mmr": {
"enabled": false,
"lambda": 0.7
}
}
mmr(可选启用,默认关闭)会按最大边际相关性重新排列排序结果:候选条目会因与已选条目的词汇相似度而受到惩罚,因此同一主题的近似重复表述不会挤占令牌预算,另一个相关主题也能获得一席之地。lambda 用于权衡相关性(1.0 = 纯相关性排序)与多样性。通过 related_to 关联的条目不受此限制——这些关联本就旨在让相关条目一同呈现。就目前的存储规模而言,影响较小(redundancy@5 已经很低);随着存储规模增长并积累被取代或重新表述的条目,它的价值会逐渐显现。
语义检索(可选启用)
默认情况下,get_context 使用纯词汇匹配(标签 + 词语重叠)为条目排序——零依赖,可离线运行。其不足在于词汇不匹配:关于“价值网络发散”的查询不会找到关于“价值头饱和”的决策,因为两者没有共同关键词。
设置 semantic.enabled: true,即可通过本地 Ollama 服务(ollama pull nomic-embed-text)将嵌入余弦相似度信号融入排序。在三个真实项目存储上进行的留出评估中,这将 hit@5 从 80% 提升到 93%,MRR 从 0.63 提升到 0.88(检索测试框架位于 evals/)。条目嵌入会按存储缓存在 .context/embeddings.json 中,并以条目文本的哈希为键,因此编辑条目后会自动重新生成嵌入。
同一余弦相似度也会用于拒答信号(top_relevance),因此仅凭含义找到的条目不会仅仅因为使用了与查询不同的词语,就被标记为 no_confident_match。它经过校准,而非直接使用原始值:嵌入余弦相似度的下限较高——即使对于存储无法回答的问题,nomic-embed 的最高余弦相似度也不会低于约 0.51。因此,将原始余弦相似度与 0.20 的拒答下限比较,会导致所有查询都高于该阈值,从而在不知不觉中禁用拒答机制。当低于 semantic.relevance_floor(默认值 0.72,略高于评估集中所有无答案查询的最高余弦相似度)时,余弦相似度不会产生贡献;达到 semantic.relevance_ceiling(默认值 0.85)时,贡献则升至 1.0。这两个值都与模型相关——使用不同的嵌入模型时,请重新校准。
此功能仅作增补,并且具有故障保护机制:如果无法访问 Ollama 或缺少模型,检索会静默回退到词汇排序。默认值仍为 enabled: false,因此开箱即用时仍保持零依赖。
任何兼容 OpenAI 的端点也都可用:设置 "api": "openai",并将 url 指向 LM Studio / llama.cpp 服务(http://localhost:1234)或 OpenAI 本身,同时将 "api_key_env" 设为保存密钥的环境变量名称。
跨项目上下文
通过传入 project_dir 查询另一个项目的上下文:
Claude: [calls get_context with project_dir="/path/to/other-project"]
也可以使用其他项目名称为条目添加标签,以便交叉引用。
命令行界面
每个工具也都可以从命令行调用,并分派到 MCP 服务使用的同一组处理程序:
context-keeper <tool> '<json-args>'
# examples
context-keeper get_project_summary '{}'
context-keeper record_entry '{"kind":"constraint","rule":"...","reason":"..."}'
context-keeper query_entries '{"kind":"decision","text":"storage","limit":5}'
context-keeper export_rules '{}' # backfill the .claude/rules/ projection
context-keeper --help # list tools
项目解析方式与服务相同(CONTEXT_KEEPER_PROJECT、包含 .context/ 的当前工作目录,或 JSON 中的 project_dir 键)。退出代码:成功时为 0,工具返回错误时为 1,用法错误时为 2。不带任何参数运行时,会像之前一样提供 stdio MCP 协议。
团队共享记忆(选择加入的快照)
.context/ 中的工作存储按机器隔离(通常会被 git 忽略)。若要通过 git 与团队共享项目记忆,请导出一个压缩且可提交的快照:
context-keeper export_snapshot '{}'
这会在项目旁写入 .context-keeper/memory.json.gz,并添加一行 .gitattributes,将其标记为 merge=ours,这样这个二进制文件就不会导致合并冲突。是否提交由你选择:
git add .context-keeper/memory.json.gz .gitattributes
git commit -m "Share project memory"
# one-time, per clone, for the merge=ours guard to take effect:
git config merge.ours.driver true
在快照存在但工作存储为空的新克隆中,Context Keeper 会在首次使用时(首次调用 get_project_summary/启动会话时)自动导入快照,让新队友快速了解项目情况。导入是非破坏性的——已有条目的存储绝不会被覆盖;运行 context-keeper import_snapshot '{}' 可手动触发导入。
编解码说明:快照使用标准库 gzip,而非 zstd——真正的 .zst 文件需要第三方 zstandard 软件包,这会破坏项目零依赖的保证。存储内容不变时,快照的字节内容也保持稳定,因此重新导出不会扰乱 git 历史记录。
隐私政策
Context Keeper 是一款仅限本地使用的工具。所有数据——每项决策、约束、流水线和配置文件——都以纯 JSON 格式存储在你选择的存储目录中(该目录内的 .context/ 文件夹),位于你自己的机器上。具体来说:
- 不会向任何地方传输数据。 服务不会自行发起网络请求,也不会向作者或任何第三方发送数据。
- 无遥测、无分析、无跟踪。 不会以任何形式报告使用情况。
- 你拥有数据,并可随时读取/编辑/删除数据——数据只是保存在你所选文件夹中的 JSON 文件。
- 唯一可选的网络功能完全由你主动启用,且目标地址由你指定。 如果你启用语义检索(
semantic.enabled,默认关闭),服务会将条目文本发送到你配置的嵌入端点——按设计,这通常是 Ollama 或 LM Studio 等本地服务。除非你主动启用,否则该功能绝不会开启;它也只会联系你设置的 URL。如果你将其指向第三方端点,则你发送的数据将受该端点自身隐私政策约束。
由于该工具只会将数据存储在你选择的本地目录中,且不会自行传输数据,因此默认情况下没有外部服务处理你的数据。
相关项目
- context-keeper-remote — 此服务的托管 Cloudflare Worker 传输层 — 以及 xylem 中心。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。