Cross-agent memory handoff via MCP — agents pass the baton instead of re-exploring the repo
Project description
Baton 🏃♂️→🏃
Cross-agent memory handoff via MCP. Claude Code, Codex, and Antigravity hand off project context to each other, so a fresh agent starts warm instead of burning quota re-exploring the codebase.
The problem
You hit Claude Code's usage limit mid-task. You open Codex to continue — and watch it re-list directories, re-read files, and re-derive decisions you already made. That re-exploration costs 5–10% of the new agent's quota before any real work happens. Multiply by every handoff, every teammate, every morning you re-open a project.
How it works
┌─────────────┐ ┌──────────┐ ┌─────────────┐
│ Claude Code │ │ Codex │ │ Antigravity │
│ (hooks + │ │ (hooks + │ │ (rules + │
│ CLAUDE.md) │ │AGENTS.md)│ │ MCP config)│
└──────┬──────┘ └────┬─────┘ └──────┬──────┘
│ MCP (stdio, pull-based) │
└───────────────┼────────────────┘
┌──────┴──────┐
│ Baton MCP │ FastMCP, Python
│ server │ 4 tools, no LLM calls
└──────┬──────┘
┌──────┴──────────────┐
│ .agent-memory/ │ ← lives IN the repo,
│ digest.md │ committed to git
│ events.jsonl │
└─────────────────────┘
Three principles:
- The server is dumb. No LLM calls, no API keys, no embeddings. The agent already has the session in context — it writes the digest and passes it as a tool argument. The server stores strings.
- Automation lives client-side. MCP is pull-based; hooks and standing instructions make agents actually use the tools (see Reliability below).
- Memory travels with the repo.
.agent-memory/is committed to git. Clone the repo, get the memory — teammates and CI included. No cloud, no accounts, no sync.
Quickstart
Baton is on PyPI as baton-mcp; all
you need is uv:
# in any project you work on:
cd ~/my-project
uvx --from baton-mcp baton init
Running from a source checkout instead
git clone <this repo> && cd baton && uv sync
cd ~/my-project
uv run --project /path/to/baton baton init
baton init detects how it was launched and writes matching configs: the
uvx install emits uvx --from baton-mcp baton-server entries, the source
checkout emits uv run --project /path/to/baton baton-server entries.
baton init is idempotent and does everything for Claude Code: creates
.agent-memory/, registers the MCP server (.mcp.json), installs hooks and
pre-allowed permissions (.claude/settings.json), and appends workflow
instructions to CLAUDE.md/AGENTS.md. It prints the config blocks for
Codex (~/.codex/config.toml) and Antigravity
(~/.gemini/antigravity/mcp_config.json) rather than silently editing
global files.
Watch memory move between agents live:
uvx --from baton-mcp baton watch
The four tools
| Tool | What it does |
|---|---|
memory_load |
Returns the trusted digest + recent events. Called at session start, before exploring. |
memory_checkpoint(digest) |
Overwrites the digest (≤2000 tokens, enforced). Called after each task. |
memory_log(type, text) |
Appends one event: decision, blocker, or todo. |
memory_handoff(to_agent, note) |
Writes a handoff block into the digest and logs the event. |
Agent identity comes from BATON_AGENT in each client's MCP config — no
per-call parameters.
Reliability: making agents actually use it
Instructions alone don't guarantee tool calls. Baton makes both directions deterministic in Claude Code:
- Loading — a
SessionStarthook injects the digest into context before the first prompt. Zero tool calls, can't be skipped. - Saving — a
PostToolUsehook marks.agent-memory/.dirtyon any file edit;memory_checkpointclears it; aStophook blocks the agent from finishing while the flag exists, telling it to checkpoint first (with a loop guard viastop_hook_active). Q&A turns pass through untouched. - Permissions —
baton initpre-allowsmcp__baton__*and pre-approves the project server, so there are zero prompts.
Codex gets the same SessionStart digest injection via ~/.codex/hooks.json
(same schema), plus default_tools_approval_mode = "approve" so tool calls
run unprompted. Antigravity relies on the AGENTS.md instructions.
Storage
digest.md — one overwritten markdown file, hard-capped at ~2000 tokens:
current task, handoff note, architecture summary, annotated file map,
decisions & constraints, tried-and-failed (saves the next agent from
repeating dead ends), next steps. Its header tells the reading agent to
trust it rather than re-verify — that's where the token savings come from.
events.jsonl — append-only log: checkpoint | decision | blocker | todo | handoff | load. Loading appends a load event, so the viewer shows a
handoff being received.
Cold start vs. warm start
Measured on a real repo (Codex 0.142 / gpt-5.5, identical prompt: "You are taking over this project from another agent. State the current task and the concrete next steps."):
| Cold (no Baton) | Warm (Baton) | |
|---|---|---|
| Exploration commands | 12 file reads/listings | 0 (one memory_load call) |
| Total tokens | 128,830 | 54,270 (−58%) |
| Orientation cost¹ | ~77k tokens | ~2k tokens |
¹ Tokens beyond Codex's fixed per-session system overhead (~52k), i.e. what getting oriented actually cost.
The key ingredient: memory_load prepends a server-side trust preamble
("do NOT re-explore what this states"). Without it, agents load the digest
and then re-explore anyway — we measured that too (210k tokens).
Honest claim: the digest reduces exploration, it doesn't eliminate it. An agent will still verify what it's about to change — it just stops re-deriving what's already known.
Limitations
- Deterministic save enforcement (Stop hook) is Claude Code-only today; Codex and Antigravity rely on instructions plus explicit prompts.
- Pre-allowed permissions take effect after the repo is trusted once.
events.jsonlcan merge-conflict across git branches (append-only helps; see roadmap).
Roadmap (deliberately not built)
- Vector search / semantic retrieval — the digest fits in one read today
- Server-side summarization — would add API keys; the agent summarizes
- Cloud sync / multi-machine — git already does this
- Web dashboard —
baton watchdoes this without ports - Merge-friendly event log (per-branch segments)
- Multi-project global memory
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file baton_mcp-0.1.0.tar.gz.
File metadata
- Download URL: baton_mcp-0.1.0.tar.gz
- Upload date:
- Size: 17.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27a1a18a83881f230227f37f5ea317eb04579074a2e98e80d2eb8127e9ed2be2
|
|
| MD5 |
97dbddb213e33db23ee5eb5c252f7f9c
|
|
| BLAKE2b-256 |
9173927926d4d1a60b4b9575206815dce1f2bfba2343a021dffff3ef31aded0c
|
Provenance
The following attestation bundles were made for baton_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on P-aveen06/baton
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
baton_mcp-0.1.0.tar.gz -
Subject digest:
27a1a18a83881f230227f37f5ea317eb04579074a2e98e80d2eb8127e9ed2be2 - Sigstore transparency entry: 2177673409
- Sigstore integration time:
-
Permalink:
P-aveen06/baton@7c199517ef1abcb783d834bcf5eb9887f83b8bcd -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/P-aveen06
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c199517ef1abcb783d834bcf5eb9887f83b8bcd -
Trigger Event:
push
-
Statement type:
File details
Details for the file baton_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: baton_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afea9e0837c4b70ead7c78682c4a6059c744e039f63cec3eee71aa995170300c
|
|
| MD5 |
ffb83f5b9a53868a86a001ce064212ac
|
|
| BLAKE2b-256 |
b26f4c68940c24253647d4bdefb897ef377ffd13a443341dfb440fff800c6ca8
|
Provenance
The following attestation bundles were made for baton_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on P-aveen06/baton
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
baton_mcp-0.1.0-py3-none-any.whl -
Subject digest:
afea9e0837c4b70ead7c78682c4a6059c744e039f63cec3eee71aa995170300c - Sigstore transparency entry: 2177673642
- Sigstore integration time:
-
Permalink:
P-aveen06/baton@7c199517ef1abcb783d834bcf5eb9887f83b8bcd -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/P-aveen06
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c199517ef1abcb783d834bcf5eb9887f83b8bcd -
Trigger Event:
push
-
Statement type: