agent-handoff-memory
# agent-handoff-memory
[](https://github.com/JusticeUA/agent-handoff-memory/actions/workflows/ci.yml)
An MCP server that gives several agents one shared, versioned memory - and an
explicit **handoff packet**, so the next session starts where the last one
stopped instead of re-deriving it.
Agents lose their context at the session boundary. The usual patch is to dump a
transcript into the prompt and hope the next run picks the right sentence out of
it. A handoff packet is the opposite: a short, structured object that says what
was done, what is next, what is still unclear, and **which exact record versions
to start from** - and the receiving agent gets those versions resolved in the
same call, with a warning about any that have moved on since.
```bash
git clone https://github.com/JusticeUA/agent-handoff-memory.git
cd agent-handoff-memory && npm install
npm run demo
```
That runs two agents in two processes against one SQLite file. No API keys, no
services, no native build step - `node:sqlite` is part of the runtime.
## What the demo shows
A scout agent crawls a (fixture) job board, writes what it found, corrects one of
its own assessments, and hands over. A separate executor process then picks the
work up knowing nothing else:
```text
--- 1. pick up whatever is waiting --------------------------------
. packet h_1f4089bf from scout-agent: Two listings worth an application, one source caveat
. next: Draft an application for listing/482 (supplier catalogue scrape, $900)
. next: Draft an application for listing/553 (price monitor, $600)
. open: Is the 60s backoff enough, or does the board keep a longer penalty window?
. 4 pinned record versions arrived with the packet
. stale: listing/553/assessment was pinned at v1, now at v2
--- 3. re-read anything the warning touched -----------------------
. listing/553 v2 now says "maybe" (budget edited down to $400 and 17 more applicants arrived)
. dropping listing/553 - acting on the pinned v1 would be wrong
--- 5. report what actually happened ------------------------------
. success on listing/482/assessment: confidence 80% -> 84%
. failure on source/boards-example/rate-limit: confidence 60% -> 39%
```
The scout edited `listing/553` *after* writing the packet. The executor is told
its pinned version is stale rather than being handed the new one behind its back,
re-reads, and drops the listing. Then it reports what actually happened, and the
confidence of the facts behind the decision moves accordingly.
Full output of both sessions: [docs/demo-transcript.md](docs/demo-transcript.md).
To watch it as two terminals instead of one script:
```bash
# terminal 1
MEMORY_DB=shared.db node dist/demo/scout.js
# terminal 2
MEMORY_DB=shared.db node dist/demo/executor.js
```
## Tools
| Tool | What it does |
| --- | --- |
| `remember` | Store a fact under `scope` + `key`. An existing key gets a new version; nothing is overwritten. |
| `recall` | Read the current version of a key, or search by scope prefix, tag, free text, minimum confidence. |
| `history` | Every version of a key: value, author, confidence, and the hash chain tying the versions together. |
| `handoff` | Write a packet: summary, next steps, open questions, and pinned record versions. With no refs given, everything the session touched is pinned. |
| `resume` | Claim the oldest open packet for this agent and get it back **with** the pinned records resolved and stale ones flagged. |
| `record_outcome` | Report success or failure against the records that drove a decision; their confidence moves and the before/after is kept. |
| `memory_stats` | Counts, average confidence, handoff states, and an optional integrity check of the whole hash chain. |
## Use it from an MCP client
```jsonc
{
"mcpServers": {
"handoff-memory": {
"command": "node",
"args": ["/absolute/path/to/agent-handoff-memory/dist/src/server.js"],
"env": {
"MEMORY_DB": "/absolute/path/to/shared-memory.db",
"AGENT_ID": "researcher"
}
}
}
}
```
Point several clients at the same `MEMORY_DB` with different `AGENT_ID`s and they
share one memory. The store runs in WAL mode precisely so that works.
For Claude Code:
```bash
claude mcp add handoff-memory -e MEMORY_DB=$PWD/shared.db -e AGENT_ID=researcher \
-- node $PWD/dist/src/server.js
```
## Design decisions
**Values are immutable, opinions are not.** Writing an existing `scope`+`key`
appends version N+1 and stamps the old one superseded. Confidence and outcome
counts do move on the current version - they are opinions about a fact, not the
fact - and every move is written to an `outcomes` table with before/after values.
So `history` stays a history of what was believed, not a log of vote changes.
**Every version is hashed and chained.** Each row carries `sha256` of its body
plus the hash of the previous version. `memory_stats { verify: true }` recomputes
the lot; a value edited straight in the database file shows up as corrupted. One
of the tests does exactly that edit and asserts it is caught.
**Stale refs are reported, never silently swapped.** A packet pins versions. If
the ground moved, the receiving agent is told - it can re-read deliberately. The
alternative (quietly serving the newest version) makes an agent act on data its
plan was never built on.
**Confidence follows outcomes, and stays inside 0..1.** Success closes part of
the gap to 1, failure scales down, so repeated evidence approaches the edges
without pinning there. The multipliers live in one table in `src/models.ts`.
**No network, no daemon, no native modules.** Storage is `node:sqlite`, transport
is stdio. The whole thing is a `node` process and a file.
## SenseLab AMFS
The project also runs on [SenseLab's AMFS](https://docs.sense-lab.ai/amfs/introduction)
TypeScript SDK. `src/amfs/sqlite-adapter.ts` implements SenseLab's `AmfsAdapter`
contract on SQLite - their `AgentMemory` does the reasoning, this does the
remembering - and `demo/amfs-bridge.ts` re-tells the handoff walkthrough through
their API:
```bash
npm run demo:amfs
```
The SDK ships an in-memory adapter (gone when the process exits) and an HTTP
adapter (needs a hosted endpoint and a key); this fills the gap between them, and
along the way populates `contentHash` / `integrityChain` and answers
`commitLog()`, which the in-memory adapter leaves empty. A parity test runs the
same session through both adapters and compares the results.
What I measured while building it - including why `commitOutcome(SUCCESS)`
*lowers* confidence in 0.3.2 - is written up in
[docs/senselab-amfs.md](docs/senselab-amfs.md).
## Tests
```bash
npm test
```
29 tests over the store, the handoff lifecycle, the MCP surface (a real client
and server joined by an in-memory transport, so the tool schemas are exercised
too), and the AMFS adapter. The AMFS group skips itself when the optional SDK is
not installed.
## Layout
```
src/models.ts types and the outcome table
src/store.ts versioned SQLite store: memory, handoffs, outcomes
src/server.ts the MCP server and its seven tools
src/amfs/types.ts structural mirror of the AMFS SDK shapes
src/amfs/sqlite-adapter.ts durable adapter for SenseLab's AMFS SDK
demo/scout.ts session 1: crawl, write, correct, hand over
demo/executor.ts session 2: resume, act, report outcomes, hand back
demo/amfs-bridge.ts the same story through @senselab-ai/amfs
```
## Requirements
Node 24 or newer, where `node:sqlite` is stable and needs no flag; developed and
tested on 25.9. On Node 22.5-23.x the same code runs with `--experimental-sqlite`.
`npm install` builds the project (via `prepare`), so `dist/` is ready afterwards.
The optional `@senselab-ai/amfs` dependency is published by SenseLab under
BSL-1.1; this repo's own code is MIT.
## License
MIT - see [LICENSE](LICENSE).
TDQS
Scored across 7 tools
Each tool targets a distinct action: storing, reading, versioning, handoff creation, handoff retrieval, outcome recording, and statistics. No two tools overlap in purpose, making misselection unlikely.
All tool names use lowercase snake_case with a verb-first or clear noun pattern (remember, recall, history, handoff, resume, record_outcome, memory_stats). The style is uniform and predictable.
Seven tools is well-scoped for a memory and handoff system. Each tool fills a necessary role without redundancy or excessive granularity.
The set covers the full lifecycle: writing memories, reading (current and historical), creating handoff packets, claiming and resuming them, recording outcomes, and monitoring integrity. There are no obvious gaps for the stated purpose.