内容
# handoff-mcp
<!-- mcp-name: io.github.kirill-sviridov/handoff-mcp -->
[](https://github.com/kirill-sviridov/handoff-mcp/actions/workflows/ci.yml)
[](https://www.python.org/)
[](https://mypy-lang.org/)
[](https://github.com/astral-sh/ruff)
[](LICENSE)
> **您的代理在会话之间的记忆。** 会话间持久、跨项目交接的记忆,按照模型上下文协议 — 它记得目标、决策、死胡同和下一步骤,因此下一会话永远不会从零开始。
Claude 在会话之间会忘记所有内容。`handoff-mcp` 给它一个可以跨越上下文窗口的记忆:它跟踪您的工作进度 — 目标、决策和它们的理由、死胡同、开放性问题、下一步骤 — 并在会话边界生成一个**优先、按令牌预算的简介**,因此下一个会话已经知道您在哪里停止。
这不是一个上下文转储。 有两件事使其与基于相似性的记忆存储(mem0 / OpenMemory 风格)有所不同:
1. **时间取代。** 一个新的决策会撤销旧的决策;撤销的决策永远不会出现在未来的简介中。 记忆反映了世界的**当前**状态,而不是一个平坦的矛盾事实堆。
2. **确定性简介。** 简介由一个可解释的排名器(最近性 + 重要性 + 取代)组装,而不是由 LLM — 因此它是可复制的,并且在令牌预算内(事件内容的软上限;参见[限制](#限制))。
3. **您的存储库,多设备。** 保险库是您自己的私有 git 存储库,而不是托管服务 — 因此记忆可以跨设备跟随您(启动时拉取,检查点时推送,冲突-free 实体合并),而无需厂商锁定。 参见[多设备同步](#多设备同步可选)。
> **状态:v0.2,早期。** 核心(保险库,简介,取代,关键字搜索,跨项目召回)已经过测试且稳定;语义层、合并层、导入器和多设备同步层是可选的且较新的。 它依赖于代理在正确的时间调用工具 — 参见[限制](#限制)以了解诚实的边缘。
它也是**跨项目**的:一个保险库,按项目命名空间;简介是项目范围的(我在哪里停止 *此处*),但 `search_memory` 在*所有*项目中召回 — 因此当您说 *"在我的一个项目中,我们做了 X"* 时,Claude 可以从另一个项目中找到并提取该决策。
## 目录
- [演示:两个会话](#演示两个会话)
- [基准](#基准)
- [工作原理](#工作原理)
- [MCP 工具](#mcp-工具)
- [成本和 API 密钥](#成本和api-密钥)
- [语义召回(可选)](#语义召回可选)
- [内存合并(可选)](#内存合并可选)
- [导入现有历史记录](#导入现有历史记录)
- [快速入门](#快速入门)
- [连接您的 MCP 客户端](#连接您的mcp客户端) — Claude, Cursor, Codex, Kilo Code, …
- [您的内存存储位置](#您的内存存储位置)
- [多设备同步(可选)](#多设备同步可选)
- [代理集成](#代理集成)
- [限制](#限制)
- [开发](#开发)
- [许可证](#许可证)
## 演示:两个会话
`python examples/two_sessions_demo.py` — 会话 1 工作并停止;会话 2 是一个*全新*的进程,仅凭简介继续。
**会话 1** 记录其进度(并改变主意一次):
```python
log_event("goal", "Ship the finance agent: income/expense tracking…")
d = log_event("decision", "Store transactions in a flat JSON file.")
log_event("decision", "Use SQLite instead of JSON — need queries.", supersedes=[d]) # retracts ↑
note_entity("Architecture", "SQLite-backed; LLM summary calls run in a worker.") # durable
log_event("deadend", "Provider streaming API times out on long months; needs chunking.")
log_event("question", "Recurring transactions: templates or materialised rows?")
log_event("next_step","Write the SQLite schema, then the ingest function.")
checkpoint("Chose SQLite; finance schema is next.")
```
**会话 2** 调用 `get_brief()` 并仅获得*当前*状态 — 撤销的 JSON 决策已消失:
```markdown
# 交接简介 — agent-hub
## 目标
- Ship the finance agent: income/expense tracking with scheduled summaries.
## 下一步
- 编写 SQLite 架构和导入函数。
## 决策
- 使用 SQLite 存储交易数据,而不是 JSON — 需要查询。 参见 [[架构]]。
## 死胡同(尝试过但失败)
- 尝试提供商的流式 API 进行汇总作业 — 对长月份超时。 不要在没有分段的情况下重试。
## 开放性问题
- 应将循环交易建模为模板还是具体行?
## 相关知识
- [[架构]] — SQLite 支持;LLM 汇总调用在工作线程中运行。
```
> 撤销的“平面 JSON 文件”决策永远不会出现。 图链接的 `[[Architecture]]` 注释自动拉取。 简介约为 170 个令牌。
并且跨项目召回 — `search_memory("timeout chunking", scope="all")` 从*不同*项目中提取决策:
```
- [hermes] 在 Hermes 项目中,我们通过分段请求解决了长作业超时问题。
- [agent-hub] 尝试提供商的流式 API 进行汇总作业 — 对长月份超时…
```
## 基准
两个离线、确定性基准 — 无 LLM,无网络 — 因此它们在任何地方都会再生并被 `tests/test_benchmark.py` 固定。 完整的数字、方法和重现方法:[`benchmarks/RESULTS.md`](benchmarks/RESULTS.md)。
**1. 隔离的取代** (`benchmarks/supersession_benchmark.py`). 一个项目的决策随着 8 个陈述跨越 4 个主题而演变;每个都有一个决策,而后者会撤销前者(JSON→SQLite,cookies→JWT,…)。 按**决策**检索(因此每个过时事实都是可达的),平面日志与活动视图:
| 模式 | 过时泄漏 | 当前保留 |
|------|:------------:|:------------:|
| 关闭取代(平面日志) | 4 / 4 | 4 / 4 |
| 开启取代(活动视图) | **0 / 4** | **4 / 4** |
取代功能准确地删除了撤销的决策,同时保留了当前的决策 — 而且 *当前保留* 的评分意味着空答案不能作为胜利。 没有取代概念的相似性存储表现得像关闭的行。
**2. 简介与天真转储** (`benchmarks/brief_reconstruction.py`). 一个恢复会话实际读取的内容 — 有预算意识的简介与粘贴整个日志。 随着历史记录的增长,转储迅速膨胀并继续包含每次撤销;简介保持有界(软上限)且无矛盾,同时保留所有关键项目(例如,在 228 个事件时:3063→217 个令牌,14 倍,3 个矛盾 → **0**).
> 这些衡量机制诚实地衡量,而不是与其他系统进行正面比较 — 公平的跨系统运行需要两者都在相同的检索条件下进行,并且我们无法在 CI 中重现 LLM 端点(参见
> [ADR-0008](docs/adr/0008-honest-benchmark.md)).
>
> 此处的“令牌”估计为 `len(text) / 4`(与模型无关),而非使用实际令牌计数器。
## 工作原理
<p align="center">
<img src="docs/architecture.svg" alt="handoff-mcp architecture" width="760">
</p>
Markdown 保险库是事实的来源 — 人类可读,可在 Obsidian 中打开,您的数据存储在磁盘上。 SQLite + FTS5 索引是从保险库派生的,可以随时重建;它支持排名、令牌预算和跨项目全文搜索。
参见 [`docs/architecture.md`](docs/architecture.md) 和
[ADRs](docs/adr/) 以获取设计原理。
## MCP 工具
| 工具 | Claude 何时调用它 |
|------|----------------------|
| `get_brief(project?, token_budget?)` | 会话开始时 — 加载上一个会话的进度。 |
| `log_event(type, content, importance?, supersedes?, supersedes_query?, project?)` | 工作过程中 — 记录目标、决策、死胡同、文件、问题、下一步骤。 `supersedes` 撤销先前的事件 ID;`supersedes_query` 撤销相同类型的最佳匹配事件(当您没有其 ID 时) ([ADR-0007](docs/adr/0007-supersede-by-best-match.md))。 |
| `search_memory(query, scope=current\|all, limit?)` | 当用户引用过去或跨项目的工作时。 `limit` 限制结果数量(默认为 10)。 每个命中事件的 ID 可直接输入 `log_event` 的 `supersedes`。 |
| `note_entity(name, content, project?)` | 记录持久的项目知识(架构、约定、组件)。 |
| `checkpoint(summary?, project?)` | 会话结束时 — 完成会话并发出简介。 传递与您记录的相同的 `project`(默认为会话的项目)。 |
| `consolidate(project?, older_than_days?)` | 将旧会话压缩为持久的笔记(可选,需要 LLM)。 |
| `sync(remote_url?)` | 在设备之间同步内存 — 拉取、提交和推送保险库的私有 git 远程(可选)。 首次调用具有仓库 URL 配置;然后直接调用同步。 参见[多设备同步](#多设备同步可选)。 |
还公开了一个 MCP 资源 `session://brief` 和一个提示 `resume` 用于在会话顶部自动加载简介。
`log_event` 类型:`goal`, `decision`, `deadend`, `file`, `question`, `next_step`.
## 成本和 API 密钥
**无订阅。 无需 API 密钥。 核心功能免费且完全本地** — 您的内存是磁盘上的普通文件,handoff-mcp 永远不会联网(无托管服务,无遥测)。
| 功能 | 需要模型/密钥? |
|------------|----------------------|
| 内存,简介,取代,关键字搜索,跨项目,导入器 | **否** — 本地,离线,免费 |
| 语义召回 *(可选)* | 默认情况下不使用(`hashing` 或本地 `sentence-transformers`);如果您选择 `openai` 后端,请自带 OpenAI 兼容的密钥 |
| 合并 *(可选,偶尔)* | 模型 — 您自己的密钥(几美分,很少运行;不是热路径) **或** 本地模型 |
因此,大部分价值无需成本且不需要密钥。 可选层要么本地运行,要么使用*您自己的*提供商 — 您永远不会被锁定在我们的系统中。
## 语义召回(可选)
关键字搜索(FTS5/bm25)是默认的,不需要额外操作。 您可以启用一个语义层,使用 Reciprocal Rank Fusion 将关键字和嵌入相似性相结合:
```bash
HANDOFF_SEMANTIC=1 handoff-mcp # 打开该层(默认为哈希后端)
```
**您选择的后端决定了它是否理解同义表达。**
默认的 `hashing` 后端是一个 *词法* 基线 — 它对令牌进行哈希,因此它添加了模糊的词法匹配(并演示混合管道),但 **不** 在单词不同的情况下根据含义进行召回。 对于真正的同义表达容忍度,请选择 `local` 或 `openai`,它们使用学习的嵌入。
**可插拔的嵌入后端**,在单个服务器上,无需分叉:
| 后端 | 安装 | 同义表达? | 备注 |
|---------|---------|:-----------:|-------|
| `hashing` (默认) | — | 否 — 词法 | 确定性,离线,无依赖项基准线(特征哈希)。 适用于演示/测试;不是真正的语义。 |
| `local` (推荐) | `pip install -e ".[semantic-local]"` | 是 | 离线 `sentence-transformers`;无需密钥,拉取 torch。 默认模型 `Qwen/Qwen3-Embedding-0.6B`(多语言,包括俄语,1024-dim,Apache-2.0)。 |
| `openai` | `pip install -e ".[semantic-openai]"` | 是 | 任何 OpenAI 兼容的端点(OpenAI,Together,自托管代理,...)。 设置 `HANDOFF_EMBED_BASE_URL` / `OPENAI_BASE_URL` 和 `HANDOFF_EMBED_API_KEY` / `OPENAI_API_KEY`。 |
```bash
# 示例:通过任何 OpenAI 兼容的端点进行语义召回
pip install -e ".[semantic-openai,semantic]"
export HANDOFF_SEMANTIC=1 HANDOFF_EMBEDDER=openai
export HANDOFF_EMBED_BASE_URL=https://your-openai-compatible-endpoint/v1 HANDOFF_EMBED_API_KEY=sk-…
export HANDOFF_EMBED_MODEL=text-embedding-3-small
```
设计(参见 [ADR-0004](docs/adr/0004-optional-semantic-layer.md)):
- **可插拔的嵌入器背后有一个 `Embedder` 协议** — 默认的 `hashing` 是确定性和无依赖性的;`openai` / `local` 插件提供真正的语义质量而无需更改其他内容。
- **sqlite-vec 是一个加速器,而不是必需品**(`.[semantic]`)— 向量作为 BLOB 持久存在,搜索使用精确的余弦扫描;如果 `sqlite-vec` 已安装且可加载,则 `vec0` 表提供快速 KNN 具有相同的顶级结果。
- 确定的简介永远不会咨询嵌入 — 语义仅影响 `search_memory`。
- **重新排序** — 回忆结果(任何模式)通过相关性 + 新鲜度(时间衰减)+ 重要性的确定性混合进行重新排序,因此新鲜、高优先级的记忆优先显示。 无需 LLM;传递 `rerank=False` 以获取原始相关性顺序。
- **增量嵌入** — 事件是不可变的,因此启动时仅嵌入*新*事件(缓存的向量被重用);嵌入缓存如果嵌入模型的维度更改时会自我失效。 这使得较重的本地模型更实用。
使用更轻/更快的本地模型,请设置 `HANDOFF_EMBED_MODEL`(例如,`Alibaba-NLP/gte-multilingual-base`,305M/768-dim — 还要设置 `HANDOFF_EMBED_TRUST_REMOTE_CODE=1` 因为该模型需要它)。 ## 内存合并(可选)
长期项目上的临时日志无限制增长 — 一个*容量*问题,而不仅仅是索引问题。 合并(“睡眠”)会将其折叠:
```bash
HANDOFF_LLM_MODEL=gpt-4o-mini handoff-mcp # 启用合并工具
```
`consolidate(project?, older_than_days?)` 将旧的已完成会话的**活动**决策提炼成持久的实体笔记(架构、决策、死胡同、...),然后**存档**原始文件到 `<project>/archive/` 并从活动索引中删除它们。 因此,保险库缩小,但有用的知识 — 简介已经显示出来 — 被保留。
- 它是 **唯一** 调用 LLM 的步骤,并且 **关闭** 除非 `HANDOFF_LLM_MODEL` 被设置(OpenAI 兼容;重用嵌入器的端点设置)。简介、搜索和取代仍然是确定性的。
## 导入现有历史记录
从您已经拥有的数据中引导一个项目的内存,以便从一开始就很有用:
```bash
handoff-import git ./my-repo --project my-project # 提交历史 → 内存
handoff-import claude session.jsonl --project my-project # Claude Code 转录
```
- **git** 将每个提交转换为决策(主题)+ 文件-触摸注释,按提交日期标记 — 完全确定,无需 LLM。
- **claude** 从第一个提示中提取目标,从文件编辑中提取文件事件(尽力而为)。
## 快速入门
```bash
uv venv && uv pip install -e ".[dev]"
# 运行两个会话的演示:会话 1 工作,会话 2 读取简介。
python examples/two_sessions_demo.py
```
## 连接您的 MCP 客户端
`handoff-mcp` 使用标准 MCP 接口,因此它适用于**任何**MCP 客户端 — Claude,Cursor,Codex,Kilo Code,Windsurf,Cline,VS Code,Zed 等等。 配置基本相同;只有文件位置(以及 Codex 的格式)不同。
**规范配置** — 由 Claude,Cursor,Kilo Code,Windsurf,Cline 和大多数其他客户端使用的 `mcpServers` JSON 块:
```json
{
"mcpServers": {
"handoff": {
"command": "handoff-mcp",
"env": {
"HANDOFF_VAULT": "/path/to/your/vault",
"HANDOFF_PROJECT": "my-project"
}
}
}
}
```
| 客户端 | 存储位置 |
|--------|---------------|
| **Claude Code** | `claude mcp add handoff -- handoff-mcp` 或项目中的 `.mcp.json` |
| **Claude Desktop** | `claude_desktop_config.json` |
| **Cursor** | `.cursor/mcp.json` (项目)或 `~/.cursor/mcp.json` (全局) |
| **Kilo Code** | 设置 → MCP → 添加服务器 → *本地(stdio)*,或 `.kilocode/mcp.json` |
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |
| **Cline / Roo Code** | 扩展的 MCP 面板 → `cline_mcp_settings.json` |
| **VS Code** (Copilot) | `.vscode/mcp.json` — 注意不同的模式 |
| **Codex CLI** 使用 `~/.codex/config.toml` 中的 TOML(或运行 `codex mcp add`):
```toml
[mcp_servers.handoff]
command = "handoff-mcp"
[mcp_servers.handoff.env]
HANDOFF_VAULT = "/path/to/your/vault"
HANDOFF_PROJECT = "my-project"
```
**VS Code** 使用 `servers` 和显式类型:
```json
{ "servers": { "handoff": { "type": "stdio", "command": "handoff-mcp",
"env": { "HANDOFF_VAULT": "/path/to/your/vault", "HANDOFF_PROJECT": "my-project" } } }
```
注意:
- `handoff-mcp` 必须位于 `PATH` — 将其安装为工具
(`uv tool install git+https://github.com/kirill-sviridov/handoff-mcp`; PyPI 版本即将推出)
或从检查点,使用 `"command": "python", "args": ["-m", "handoff_mcp.server"]`。
- 将 `HANDOFF_PROJECT` 设置为每个代理/仓库;将 `HANDOFF_VAULT` 指向 **相同的共享保险库**,跨越所有这些文件(参见下文)。
## 记忆存储位置
一个**中央存储库**,其中包含每个项目的文件夹:
```
~/.handoff-mcp/vault/ # 默认的中央存储库路径
├── .index.db # 衍生的SQLite索引 — 可重新构建,忽略git
├── 我的项目/
│ ├── 会话/<id>.md # 会话笔记
│ └── 实体/<名称>.md # 持久知识
└── 另一个项目/…
```
- **一个存储库,非每个仓库一个**。跨项目回忆(`search_memory`)仅在每个项目都生活在单个存储库中时才有效。因此,请将每个代理的`HANDOFF_VAULT`指向同一个目录,并仅更改`HANDOFF_PROJECT`。
- **它位于代码仓库之外**,因此它永远不会被意外提交到工作项目中 — 您的项目仓库保持干净。
- **想要备份/多设备同步?** 存储库是纯markdown,因此可以单独版本控制(私人git仓库、Obsidian Sync、Dropbox等)。有关跨设备的内置git同步 — 拉取启动、推送检查点、无冲突的实体合并 — 请参见[多设备同步](#多设备同步可选);`handoff-sync --setup`为您配置远程仓库并忽略衍生索引。
- **更喜欢随存储库一起使用的记忆?** 将`HANDOFF_VAULT`指向存储库内(例如`./.handoff`)— 但搜索仅看到该项目。建议使用共享存储库。
## 多设备同步(可选)
您的存储库只是一个私人git仓库,因此记忆可以跨设备跟随。同步是严格按需的 — 没有配置git,记忆在单个设备上完全有效。
| 层级 | 设置 | 您获得 |
| --- | --- | --- |
| **仅限本地**(默认) | 无 | 完整的记忆,单个设备,无帐户 |
| **手动** | `handoff-sync --setup <私人仓库网址>` 一次 | `handoff-sync`(或`sync`工具)按需拉取、提交、推送 |
| **自动** | 上述 + `HANDOFF_AUTO_SYNC=1` | 在会话开始时拉取,在检查点时推送 |
实体笔记通过git的内置`union`驱动程序(`*/entities/*.md merge=union`在存储库的`.gitattributes`中,由设置写入)跨设备干净地合并。在无shell客户端(例如Cursor)中,只需要求代理同步 — `sync`工具不需要终端。设置需要有效的git身份验证(`gh auth login`或SSH密钥);如果推送失败,命令会告诉您确切的解决方法。
## 代理集成
*知道**何时**使用它。* 服务器永远不会向模型推送任何内容;模型决定何时调用工具。三个层级使该功能可靠,从最便携到最强大:
1. **工具描述**(内置)— 每个工具都说明**何时**调用它。在任何MCP客户端中都有效。
2. **服务器`指令`**(内置)— 服务器在连接时发送的一个简短仪式(在启动时加载简介,在工作时记录,在结束时检查点)。在Claude Desktop、Cursor和其他MCP客户端中可移植。
3. **Claude Code技能**([`技能/`](skills/))— 编码**工作流程**,并关键地根据口语提示触发:
- [`会话交接`](skills/handoff/SKILL.md) — 当您说“转到下一会话” / “今天就到这里”,代理知道自动`checkpoint`。它还在第一次使用时引导项目的指令文件(`CLAUDE.md`或`AGENTS.md` / `.cursor/rules/handoff.mdc` / `.windsurfrules`每个客户端)。
- [`会话规划`](skills/planning/SKILL.md) — 可选的配套工具:将大任务分解为会话大小的块,并在记忆中持久化计划。
通过复制到技能目录进行安装:
```bash
cp -r skills/handoff skills/planning ~/.claude/skills/ # 用户范围
# 或在特定项目的`.claude/skills/`内
```
(技能是Claude Code / claude.ai的功能;其他代理依赖于层级1-2。)
对于非Claude客户端,请使用`handoff-init` CLI(幂等)将记忆块放入项目的指令文件中:
```bash
handoff-init # CLAUDE.md
handoff-init --client codex # AGENTS.md · --client cursor / windsurf
```
对于Claude Code,请在`CLAUDE.md`中添加以下内容,以便在没有技能的情况下自动加载简介:
> 在会话开始时调用`get_brief`。在工作过程中使用`log_event`记录决策、失败和下一步骤 — 每次调用一个原子项(1-2个句子,附带原因),而不是整个会话摘要;引用持久笔记为`[[实体]]`。在停止前调用`checkpoint`。当我提到过去的工作或另一个项目时,请调用`search_memory`。
## 限制
v0.2的诚实边界,这样您就知道自己正在采用什么:
- **超会话是显式的,而不是推断的。** handoff-mcp永远不会自行决定一个记忆被另一个记忆取代 — 代理必须通过`supersedes`(按ID)或`supersedes_query`(按最佳匹配)说明。这是有意的(它保持简介的确定性和可审计性),但这意味着记忆的质量取决于代理实际记录的撤回。它不会像LLM提取存储尝试的那样静默地重复数据。
- **它依赖于代理的纪律。** 服务器永远不会推送任何内容;价值来自模型在正确时刻调用`log_event` / `get_brief` / `checkpoint`。工具描述和技能会提示这一点,但如果客户端从未调用工具,则存储库为空。跨会话记忆仅限于已记录的内容。
- **默认语义后端(`hashing`)是词汇的,而不是同义的。** 真正的同义回忆需要`local`或`openai`(参见[语义回忆](#语义回忆可选))。确定性简介本身从不使用嵌入。
- **令牌预算是软性的。** 它限制事件**内容**;章节标题和“相关知识”块是附加的,因此渲染的简介可以略高于数字。它保持简介的边界和扁平,因为历史增长 — 它不是一个硬字节上限。
- **单进程新鲜度。** 一个存储库可以跨进程/代理共享(SQLite WAL +忙碌超时让写入者共存),但运行的进程在其**启动**时刷新其对存储库的视图(`_sync_index`)— 它实时捕获自己的写入,但另一个进程的新事件仅在下一个启动时(手动或`HANDOFF_AUTO_SYNC`拉取简介 — 在会话中重新索引时拉取新事件)。对于单个进程内的并发**线程**,对共享索引的访问由锁序列化。
- **个人/团队规模。** 排名将一个项目的事件加载到记忆中;这适用于数千个会话,不适用于数百万(参见[ADR-0005](docs/adr/0005-incremental-index-sync.md)以获取本地化修复)。
## 开发
```bash
uv pip install -e ".[dev]"
ruff check . && mypy && pytest
python examples/two_sessions_demo.py # 交接过程演示
python examples/demo_presentation.py # 节奏/旁白版本(用于录制GIF)
python examples/stdio_smoke.py # 作为真正的stdio MCP服务器运行
python benchmarks/brief_reconstruction.py # 简介与天真全转储
python benchmarks/supersession_benchmark.py # 超会话开启/关闭,在隔离状态下
```
## 许可证
MIT — 请参见[LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器