{
  "markdown": "# handoff-mcp\n\n<!-- mcp-name: io.github.kirill-sviridov/handoff-mcp -->\n\n[![CI](https://github.com/kirill-sviridov/handoff-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/kirill-sviridov/handoff-mcp/actions/workflows/ci.yml)\n[![Python](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue)](https://www.python.org/)\n[![Checked with mypy](https://img.shields.io/badge/mypy-strict-2a6db2)](https://mypy-lang.org/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n> **Your agent's memory between sessions.** Persistent, cross-project hand-off for\n> Claude over the Model Context Protocol — it remembers the goal, the decisions,\n> the dead-ends, and what's next, so the next session never starts from zero.\n\nClaude forgets everything between sessions. `handoff-mcp` gives it a memory that\nsurvives the context window: it tracks the *progress* of your work — goals,\ndecisions and their rationale, dead-ends, open questions, the next step — and at\nthe boundary of a session produces a **prioritised, token-budgeted brief** so the\nnext session resumes already knowing where you left off.\n\nIt is not a context dump. Two things make it different from similarity-based\nmemory stores (mem0 / OpenMemory style):\n\n1. **Temporal supersession.** A new decision retracts an old one; retracted\n   decisions never appear in a future brief. Memory reflects the *current* state\n   of the world, not a flat pile of contradictory facts.\n2. **Deterministic brief.** The brief is assembled by an explainable ranker\n   (recency + importance + supersession), not by an LLM — so it is reproducible\n   and stays within a token budget (a soft cap on event content; see\n   [Limitations](#limitations)).\n3. **Your store, multi-device.** The vault is your own private git repo, not a\n   hosted service — so memory can follow you across machines (pull-on-start,\n   push-on-checkpoint, conflict-free entity merges) with zero vendor lock. See\n   [Multi-device sync](#multi-device-sync-optional).\n\n> **Status: v0.4, early.** The core (vault, brief, supersession, keyword search,\n> cross-project recall) is tested and stable; the semantic, consolidation,\n> importer, and multi-device sync layers are optional and newer. It leans on the agent calling the tools\n> at the right moments — see [Limitations](#limitations) for the honest edges.\n\nIt is also **cross-project**: one vault, namespaced per project. The brief is\nproject-scoped (where did I leave off *here*), but `search_memory` recalls across\n*all* projects — so when you say *\"in one of my projects we did X\"*, Claude can\nfind and pull that decision out of another project.\n\n## Contents\n\n- [Demo: two sessions](#demo-two-sessions)\n- [Benchmarks](#benchmarks)\n- [How it works](#how-it-works)\n- [MCP tools](#mcp-tools)\n- [Cost & API keys](#cost--api-keys)\n- [Semantic recall (optional)](#semantic-recall-optional)\n- [Memory consolidation (optional)](#memory-consolidation-optional)\n- [Import existing history](#import-existing-history)\n- [Quickstart](#quickstart)\n- [Connect your MCP client](#connect-your-mcp-client) — Claude, Cursor, Codex, Kilo Code, …\n- [Where your memory lives](#where-your-memory-lives)\n- [Multi-device sync (optional)](#multi-device-sync-optional)\n- [Agent integration](#agent-integration)\n- [Limitations](#limitations)\n- [Development](#development)\n- [License](#license)\n\n## Demo: two sessions\n\n`python examples/two_sessions_demo.py` — session 1 works and stops; session 2 is\na *fresh* process that resumes from the brief alone.\n\n**Session 1** logs its progress (and changes its mind once):\n\n```python\nlog_event(\"goal\",     \"Ship the finance agent: income/expense tracking…\")\nd = log_event(\"decision\", \"Store transactions in a flat JSON file.\")\nlog_event(\"decision\", \"Use SQLite instead of JSON — need queries.\", supersedes=[d])  # retracts ↑\nnote_entity(\"Architecture\", \"SQLite-backed; LLM summary calls run in a worker.\")     # durable\nlog_event(\"deadend\",  \"Provider streaming API times out on long months; needs chunking.\")\nlog_event(\"question\", \"Recurring transactions: templates or materialised rows?\")\nlog_event(\"next_step\",\"Write the SQLite schema, then the ingest function.\")\ncheckpoint(\"Chose SQLite; finance schema is next.\")\n```\n\n**Session 2** calls `get_brief()` and gets back only the *current* state — the\nretracted JSON decision is gone:\n\n```markdown\n# Hand-off brief — agent-hub\n\n## Goal\n- Ship the finance agent: income/expense tracking with scheduled summaries.\n## Next step\n- Write the SQLite schema for transactions and categories, then the ingest function.\n## Decisions\n- Use SQLite for transactions instead of JSON — need queries for summaries. See [[Architecture]].\n## Dead ends (tried & failed)\n- Tried the provider's streaming API for the summary job — times out on long months. Don't retry without chunking.\n## Open questions\n- Should recurring transactions be modelled as templates or materialised rows?\n## Related knowledge\n- [[Architecture]] — SQLite-backed; LLM summary calls run in a worker.\n```\n\n> The retracted \"flat JSON file\" decision never appears. The graph-linked\n> `[[Architecture]]` note is pulled in automatically. The brief is ~170 tokens.\n\nAnd cross-project recall — `search_memory(\"timeout chunking\", scope=\"all\")` pulls\na decision out of a *different* project:\n\n```\n- [hermes]    In the Hermes project we solved long-job timeouts by chunking requests.\n- [agent-hub] Tried the provider's streaming API for the summary job — times out on long months…\n```\n\n## Benchmarks\n\nTwo offline, deterministic benchmarks — no LLM, no network — so they regenerate\nidentically anywhere and are pinned by `tests/test_benchmark.py`. Full numbers,\nmethodology, and how to reproduce: [`benchmarks/RESULTS.md`](benchmarks/RESULTS.md).\n\n**1. Supersession in isolation** (`benchmarks/supersession_benchmark.py`). A\nproject's decisions evolve across 8 statements over 4 topics; each has one decision\na later one retracts (JSON→SQLite, cookies→JWT, …). Retrieved **by decision** (so\nevery stale fact is reachable), the flat log vs the active view:\n\n| mode | stale leaked | current kept |\n|------|:------------:|:------------:|\n| supersession OFF (flat log) | 4 / 4 | 4 / 4 |\n| supersession ON (active view) | **0 / 4** | **4 / 4** |\n\nSupersession removes exactly the retracted decisions while keeping every current\none — and scoring *current kept* too means an empty answer can't pass as a win. A\nsimilarity store with no notion of one fact retiring another behaves like the OFF\nrow.\n\n**2. Brief vs naive dump** (`benchmarks/brief_reconstruction.py`). What a resuming\nsession actually reads — the budgeted, supersession-aware brief vs pasting back the\nwhole log. As history grows the dump balloons and keeps carrying every retraction;\nthe brief stays bounded (a soft cap) and contradiction-free while retaining all\nkey items (e.g. at 228 events: 3063→217 tokens, 14×, 3 contradictions → **0**).\n\n> These measure the mechanism honestly rather than staging a head-to-head against\n> another store — a fair cross-system run needs both under identical retrieval plus\n> an LLM endpoint we can't reproduce in CI (see\n> [ADR-0008](docs/adr/0008-honest-benchmark.md)).\n>\n> \"Tokens\" here and elsewhere in this README are estimated as `len(text) / 4`\n> (model-agnostic), not counted with a real tokenizer.\n\n## How it works\n\n<p align=\"center\">\n  <img src=\"docs/architecture.svg\" alt=\"handoff-mcp architecture\" width=\"760\">\n</p>\n\nThe markdown vault is the source of truth — human-readable, openable in\nObsidian, your data on your disk. The SQLite + FTS5 index is *derived* from the\nvault and can be rebuilt at any time; it powers ranking, the token budget, and\ncross-project full-text search.\n\nSee [`docs/architecture.md`](docs/architecture.md) and the\n[ADRs](docs/adr/) for the design rationale.\n\n## MCP tools\n\n| Tool | When Claude calls it |\n|------|----------------------|\n| `get_brief(project?, token_budget?)` | At session start — load where the last session left off. |\n| `log_event(type, content, importance?, supersedes?, supersedes_query?, project?)` | As work happens — record goals, decisions, dead-ends, files, questions, next steps. `supersedes` retires a prior event by id; `supersedes_query` retires the best-matching active event of the same type when you don't have its id ([ADR-0007](docs/adr/0007-supersede-by-best-match.md)). |\n| `search_memory(query, scope=current\\|all, limit?)` | When the user references past or other-project work. `limit` caps the number of results (default 10). Each hit includes the event id, feedable straight into `log_event`'s `supersedes`. |\n| `note_entity(name, content, project?)` | To record durable project knowledge (architecture, conventions, components). |\n| `checkpoint(summary?, project?)` | At session end — finalise the session and emit the brief. Pass the same `project` you logged under (defaults to the session's project). |\n| `consolidate(project?, older_than_days?)` | To compress old sessions into durable notes (opt-in, needs an LLM). |\n| `sync(remote_url?)` | To sync memory across devices — pull, commit, and push the vault's private git remote (opt-in). First call with a repo URL configures it; then a bare call syncs. See [Multi-device sync](#multi-device-sync-optional). |\n\nAlso exposed: an MCP resource `session://brief` and a prompt `resume` for\nauto-loading the brief at the top of a session.\n\n`log_event` types: `goal`, `decision`, `deadend`, `file`, `question`, `next_step`.\n\n## Cost & API keys\n\n**No subscription. No required API keys. The core is free and fully local** —\nyour memory is plain files on your disk, and handoff-mcp never phones home (no\nhosted service, no telemetry).\n\n| Capability | Needs a model / key? |\n|------------|----------------------|\n| Memory, brief, supersession, keyword search, cross-project, importers | **No** — local, offline, free |\n| Semantic recall *(optional)* | No by default (`hashing` or local `sentence-transformers`); bring your own OpenAI-compatible key only if you pick the `openai` backend |\n| Consolidation *(optional, occasional)* | A model — your own key (a few cents, run rarely; it's not a hot path) **or** a local model |\n\nSo most of the value costs nothing and needs no key. The optional layers either\nrun locally or use *your own* provider — you're never locked into ours.\n\n## Semantic recall (optional)\n\nKeyword search (FTS5/bm25) is the default and needs nothing extra. You can enable\na semantic layer that fuses keyword and embedding similarity with Reciprocal Rank\nFusion:\n\n```bash\nHANDOFF_SEMANTIC=1 handoff-mcp     # turn the layer on (default: hashing backend)\n```\n\n**The backend you pick decides whether this actually understands paraphrases.**\nThe default `hashing` backend is a *lexical* baseline — it hashes tokens, so it\nadds fuzzy lexical matching (and demonstrates the hybrid pipeline) but does **not**\nrecall on meaning when the words differ. For genuine paraphrase-tolerant recall,\nchoose `local` or `openai`, which use learned embeddings.\n\n**Pluggable embedding backends**, selected with `HANDOFF_EMBEDDER` — one server,\nno forks:\n\n| Backend | Install | Paraphrase? | Notes |\n|---------|---------|:-----------:|-------|\n| `hashing` (default) | — | No — lexical | Deterministic, offline, zero-dependency toy baseline (feature-hashing). Good for demos/tests; not real semantics. |\n| `local` (recommended) | `pip install -e \".[semantic-local]\"` | Yes | Offline `sentence-transformers`; no key, pulls in torch. Default model `Qwen/Qwen3-Embedding-0.6B` (multilingual incl. Russian, 1024-dim, Apache-2.0). |\n| `openai` | `pip install -e \".[semantic-openai]\"` | Yes | Any OpenAI-compatible endpoint (OpenAI, Together, a self-hosted proxy, …). Set `HANDOFF_EMBED_BASE_URL` / `OPENAI_BASE_URL` and `HANDOFF_EMBED_API_KEY` / `OPENAI_API_KEY`. |\n\n```bash\n# Example: semantic recall via any OpenAI-compatible endpoint\npip install -e \".[semantic-openai,semantic]\"\nexport HANDOFF_SEMANTIC=1 HANDOFF_EMBEDDER=openai\nexport HANDOFF_EMBED_BASE_URL=https://your-openai-compatible-endpoint/v1 HANDOFF_EMBED_API_KEY=sk-…\nexport HANDOFF_EMBED_MODEL=text-embedding-3-small\n```\n\nDesign (see [ADR-0004](docs/adr/0004-optional-semantic-layer.md)):\n\n- **Pluggable embedder behind an `Embedder` protocol** — the `hashing` default is\n  deterministic and dependency-free; `openai` / `local` plug in for real semantic\n  quality without changing anything else.\n- **sqlite-vec is an accelerator, not a requirement** (`.[semantic]`) — vectors\n  persist as BLOBs and search works with an exact cosine scan; if `sqlite-vec` is\n  installed and loadable, a `vec0` table provides fast KNN with the same top-ranked results.\n- The deterministic brief never consults embeddings — semantics only affect\n  `search_memory`.\n- **Reranking** — recall results (any mode) are reordered by a deterministic\n  blend of relevance + recency (time-decay) + importance, so fresh, high-priority\n  memories surface first. No LLM; pass `rerank=False` to get raw relevance order.\n- **Incremental embedding** — events are immutable, so startup only embeds *new*\n  events (cached vectors are reused); the cache self-invalidates if the embedding\n  model's dimension changes. This keeps heavier local models practical.\n\nTo use a lighter/faster local model instead, set `HANDOFF_EMBED_MODEL` (e.g.\n`Alibaba-NLP/gte-multilingual-base`, 305M/768-dim — also set\n`HANDOFF_EMBED_TRUST_REMOTE_CODE=1` as that model requires it).\n\n## Memory consolidation (optional)\n\nOver a long-lived project the episodic log grows without bound — a *volume*\nproblem, not just an indexing one. Consolidation (\"sleep\") folds it down:\n\n```bash\nHANDOFF_LLM_MODEL=gpt-4o-mini handoff-mcp   # enables the consolidate tool\n```\n\n`consolidate(project?, older_than_days?)` distils old finished sessions' **active**\ndecisions into the durable entity notes (Architecture, Decisions, Dead-ends, …),\nthen **archives** the originals to `<project>/archive/` and drops them from the\nactive index. So the vault shrinks but the lasting knowledge — which the brief\nalready surfaces — is kept.\n\n- It is the **only** step that calls an LLM, and it's **off** unless\n  `HANDOFF_LLM_MODEL` is set (OpenAI-compatible; reuses the embedder's endpoint\n  settings). The brief, search, and supersession stay deterministic.\n- Only **active** events are distilled — a retracted decision is never\n  immortalised. Dead-ends are kept as cautionary facts.\n- Originals are **archived, not deleted** (auditable, reversible).\n\nSee [ADR-0006](docs/adr/0006-memory-consolidation.md).\n\n## Import existing history\n\nBootstrap a project's memory from data you already have, so it's useful from\nminute one instead of empty:\n\n```bash\nhandoff-import git ./my-repo --project my-project        # commit history → memory\nhandoff-import claude session.jsonl --project my-project # a Claude Code transcript\n```\n\n- **git** turns each commit into a decision (the subject) plus a files-touched\n  note, timestamped at the commit date — fully deterministic, no LLM.\n- **claude** pulls the first prompt as a goal and file edits as file events from a\n  Claude Code transcript (best-effort).\n- Import is **idempotent** (ids derive from the source), so re-running only adds\n  what's new. Writes into the same shared vault (`HANDOFF_VAULT`).\n\n## Quickstart\n\n```bash\nuv venv && uv pip install -e \".[dev]\"\n\n# Run the two-session demo: session 1 works, session 2 reads the brief.\npython examples/two_sessions_demo.py\n```\n\n## Connect your MCP client\n\n**Claude Code — one-minute install (recommended):**\n\n```\n/plugin marketplace add kirill-sviridov/handoff-mcp\n/plugin install handoff-mcp@handoff-mcp\n```\n\nThat bundles the server (auto-installed from PyPI via `uvx` — needs `uv` on\nPATH), a session-start hook that loads the memory protocol, and the\n`session-handoff` / `session-planning` skills. Manual setup below is for other\nclients (or if you prefer explicit config).\n\n`handoff-mcp` speaks standard MCP over stdio, so it works with **any** MCP client —\nClaude, Cursor, Codex, Kilo Code, Windsurf, Cline, VS Code, Zed, … The config is\nessentially the same everywhere; only the file location (and, for Codex, the\nformat) differs.\n\n**Canonical config** — the `mcpServers` JSON block used by Claude, Cursor, Kilo\nCode, Windsurf, Cline, and most others:\n\n```json\n{\n  \"mcpServers\": {\n    \"handoff\": {\n      \"command\": \"handoff-mcp\",\n      \"env\": {\n        \"HANDOFF_VAULT\": \"/path/to/your/vault\",\n        \"HANDOFF_PROJECT\": \"my-project\"\n      }\n    }\n  }\n}\n```\n\n| Client | Where it goes |\n|--------|---------------|\n| **Claude Code** | `claude mcp add handoff -- handoff-mcp`, or a `.mcp.json` in the project |\n| **Claude Desktop** | `claude_desktop_config.json` |\n| **Cursor** | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |\n| **Kilo Code** | Settings → MCP → Add Server → *Local (stdio)*, or `.kilocode/mcp.json` |\n| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |\n| **Cline / Roo Code** | the extension's MCP panel → `cline_mcp_settings.json` |\n| **VS Code** (Copilot) | `.vscode/mcp.json` — note the different schema below |\n\n**Codex CLI** uses TOML in `~/.codex/config.toml` (or run `codex mcp add`):\n\n```toml\n[mcp_servers.handoff]\ncommand = \"handoff-mcp\"\n[mcp_servers.handoff.env]\nHANDOFF_VAULT = \"/path/to/your/vault\"\nHANDOFF_PROJECT = \"my-project\"\n```\n\n**VS Code** uses `\"servers\"` and an explicit type:\n\n```json\n{ \"servers\": { \"handoff\": { \"type\": \"stdio\", \"command\": \"handoff-mcp\",\n  \"env\": { \"HANDOFF_VAULT\": \"/path/to/your/vault\", \"HANDOFF_PROJECT\": \"my-project\" } } } }\n```\n\nNotes:\n- `handoff-mcp` must be on `PATH` — install it as a tool\n  (`uv tool install handoff-mcp`; or from source, `uv tool install\n  git+https://github.com/kirill-sviridov/handoff-mcp`)\n  or, from a checkout, use `\"command\": \"python\", \"args\": [\"-m\", \"handoff_mcp.server\"]`.\n- Set `HANDOFF_PROJECT` per agent/repo; keep `HANDOFF_VAULT` pointed at the **same\n  shared vault** across all of them (see below). Since 0.4.0, when `HANDOFF_PROJECT`\n  is unset the project defaults to the git-root/cwd basename, giving per-repo\n  namespaces with zero config; setting it explicitly still wins.\n\n## Where your memory lives\n\nOne **central vault**, with a folder per project inside it:\n\n```\n~/.handoff-mcp/vault/            # HANDOFF_VAULT (default; override per machine)\n├── .index.db                    # derived SQLite index — rebuildable, gitignore it\n├── my-project/\n│   ├── sessions/<id>.md         # episodic notes\n│   └── entities/<Name>.md       # durable knowledge\n└── another-project/…\n```\n\n- **One vault, not one-per-repo.** Cross-project recall (`search_memory`) only\n  works because every project lives in a single store. So point every agent's\n  `HANDOFF_VAULT` at the same directory and just vary `HANDOFF_PROJECT`.\n- **It lives outside your code repos**, so it never gets committed into your work\n  projects by accident — your project repos stay clean.\n- **Want backup / multi-machine sync?** The vault is plain markdown, so version it\n  on its own (a private git repo, Obsidian Sync, Dropbox…). For built-in git sync\n  across devices — pull-on-start, push-on-checkpoint, conflict-free entity merges —\n  see [Multi-device sync](#multi-device-sync-optional); `handoff-sync --setup`\n  configures the remote and gitignores the derived index for you.\n- **Prefer memory that travels with one repo?** Point `HANDOFF_VAULT` inside that\n  repo (e.g. `./.handoff`) — but then search sees only that project. The shared\n  vault is recommended.\n\n## Multi-device sync (optional)\n\nYour vault is just a private git repo, so memory can follow you across machines.\nSync is strictly opt-in — with no git configured, memory works fully on one\ndevice.\n\n| Tier | Setup | You get |\n| --- | --- | --- |\n| **Local-only** (default) | nothing | full memory, one device, no account |\n| **Manual** | `handoff-sync --setup <private-repo-url>` once | `handoff-sync` (or the `sync` tool) pulls, commits, pushes on demand |\n| **Automatic** | above + `HANDOFF_AUTO_SYNC=1` | pull at session start, push at checkpoint |\n\nEntity notes merge cleanly across machines via git's built-in `union` driver\n(`*/entities/*.md merge=union` in the vault's `.gitattributes`, written by setup).\nIn a shell-less client (e.g. Cursor), just ask the agent to sync — the `sync`\ntool needs no terminal. Setup needs working git auth (`gh auth login` or an SSH\nkey); if a push fails, the command tells you exactly what to fix.\n\n## Agent integration\n\n*Knowing **when** to use it.* The server never pushes anything to the model; the\nmodel decides when to call the tools. Four layers make that reliable, from most portable to most capable:\n\n1. **Tool descriptions** (built in) — every tool says *when* to call it. Works in\n   any MCP client.\n2. **Server `instructions`** (built in) — a short ritual the server sends on\n   connect (load the brief at start, log as you work, checkpoint at the end).\n   Portable across Claude Desktop, Cursor, and other MCP clients.\n3. **Claude Code skills** ([`skills/`](skills/)) — encode the *workflow* and,\n   crucially, trigger on colloquial cues:\n   - [`session-handoff`](skills/handoff/SKILL.md) — when you say \"го в следующую\n     сессию\" / \"that's it for today\", the agent knows to `checkpoint` on its own.\n     It also bootstraps the project's instruction file on first use (`CLAUDE.md`,\n     or `AGENTS.md` / `.cursor/rules/handoff.mdc` / `.windsurfrules` per client).\n   - [`session-planning`](skills/planning/SKILL.md) — optional companion: breaks a\n     big task into session-sized chunks and persists the plan in memory.\n\n   Install by copying into your skills dir:\n\n   ```bash\n   cp -r skills/handoff skills/planning ~/.claude/skills/   # user-wide\n   # or .claude/skills/ inside a specific project\n   ```\n\n   (Skills are a Claude Code / claude.ai feature; other agents rely on layers 1–2.)\n\n   For non-Claude clients, drop the memory block into the project's instruction\n   file with the `handoff-init` CLI (idempotent):\n\n   ```bash\n   handoff-init                 # CLAUDE.md\n   handoff-init --client codex  # AGENTS.md   ·   --client cursor / windsurf\n   ```\n\n4. **Claude Code plugin** — layers 1-3 in one install; see [Connect your MCP client](#connect-your-mcp-client).\n\nFor Claude Code specifically, also add this to your `CLAUDE.md` so the brief loads\nautomatically even without the skill:\n\n> At the start of a session call `get_brief`. Record decisions, dead-ends and the\n> next step with `log_event` as you work — one atomic item per call (1-2 sentences\n> with the why), not a whole-session summary; reference durable notes as\n> `[[Entity]]`. `checkpoint` before you stop. When I mention past work or another\n> project, call `search_memory`.\n\n## Limitations\n\nHonest edges of v0.4, so you know what you're adopting:\n\n- **Supersession is explicit, not inferred.** handoff-mcp never decides on its own\n  that one memory retires another — the agent must say so, via `supersedes` (by id)\n  or `supersedes_query` (by best match). That is deliberate (it's what keeps the\n  brief deterministic and auditable), but it means the quality of the memory\n  depends on the agent actually logging retractions. It won't silently\n  de-duplicate contradictions the way an LLM-extraction store attempts to. Exception: `next_step` — a newly logged next step auto-retires prior sessions' active next steps (rule-based, recorded on the event; [ADR-0009](docs/adr/0009-rule-based-next-step-retire.md)). If a retired step was still valid, re-log it.\n- **It depends on the agent's discipline.** The server never pushes anything; value\n  comes from the model calling `log_event` / `get_brief` / `checkpoint` at the\n  right moments. The tool descriptions and the skill nudge this, but a client that\n  never calls the tools gets an empty vault. Cross-session memory is only as good\n  as what got logged.\n- **The default semantic backend (`hashing`) is lexical, not paraphrase-aware.**\n  Real paraphrase recall needs `local` or `openai` (see\n  [Semantic recall](#semantic-recall-optional)). The deterministic brief itself\n  never uses embeddings.\n- **The token budget is soft.** It bounds event *content*; section headings and the\n  \"Related knowledge\" block are chrome on top, so the rendered brief can sit a\n  little above the number. It keeps the brief bounded and flat as history grows —\n  it is not a hard byte cap.\n- **Single-process freshness.** One vault can be shared across processes/agents\n  (SQLite WAL + a busy timeout let writers coexist), but a running process refreshes\n  its view of the vault at **startup** (`_sync_index`) — it picks up its own writes\n  live, but another process's new events only on the next launch (a `sync` — manual\n  or the `HANDOFF_AUTO_SYNC` pull-on-brief — re-indexes mid-session when it pulls\n  new events). For concurrent *threads* inside one process, access to the shared\n  index is serialised by a lock.\n- **Personal/team scale.** Ranking loads a project's events into memory; this is\n  fine for thousands of sessions, not tuned for millions (see\n  [ADR-0005](docs/adr/0005-incremental-index-sync.md) for the localized fixes if\n  that day comes).\n\n## Development\n\n```bash\nuv pip install -e \".[dev]\"\nruff check . && mypy && pytest\npython examples/two_sessions_demo.py        # the hand-off in action\npython examples/demo_presentation.py         # paced/narrated version (for recording a GIF)\npython examples/stdio_smoke.py              # run it as a real stdio MCP server\npython benchmarks/brief_reconstruction.py   # brief vs naive full-dump\npython benchmarks/supersession_benchmark.py # supersession on vs off, in isolation\n```\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 25747,
  "sha": "eb3dda6684aca7ac78f63dec65291f7ca92e9a0c95b6e8a019de63f2cf25906b",
  "repo_slug": "kirill-sviridov/handoff-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kirill_sviridov_handoff_mcp_7d8917fc/readme"
}