Skip to main content

madeleine

Involuntary memory for AI agents. A place, an hour, a sound, a photo or a sensor reading brings back a related memory on its own, without the agent having to ask. It works the way the taste of a madeleine brought back Proust's childhood.

CI · Python 3.11–3.13 · Apache-2.0 · local-first (SQLite) · MCP server + Python library

Most agent memory is voluntary: the agent decides to search, writes a query, and gets results. madeleine adds the other pathway. Every memory is stored with the sensory fingerprint present when it was encoded: where, when, what was said, seen or heard. Incoming cues from webhooks, phones, smart homes and wearables are matched against those fingerprints all the time. When enough channels line up, the memory is queued as an involuntary recollection, and the agent receives it with its next prompt. This is encoding specificity applied to agents (concepts).

60-second quickstart

# 1. See the whole loop, offline (nothing is downloaded, nothing leaves your machine)
uvx madeleine-mcp demo

The demo remembers three moments. Then a cue arrives that carries only a place and a time of day: 30 m from grandmother's house, on a Sunday afternoon. The matching memory surfaces by itself, is delivered through the MCP tool pending_recollections, and does not fire again for a repeated cue:

[madeleine 0.80 via geo+time] Sunday afternoons in grandmother's kitchen: lime-blossom tea and a madeleine (2019-04-14)
  channel similarity geo=0.97, time=1.00; salience x1.16, age decay x0.70
# 2. Give Claude Code the tools
claude mcp add madeleine -- uvx madeleine-mcp serve
  1. Let involuntary recollections reach every prompt. Add this to .claude/settings.json:
{"hooks": {"UserPromptSubmit": [{"hooks": [
  {"type": "command", "command": "uvx madeleine-mcp hook claude-code", "timeout": 10}
]}]}}

The hook works even if your client ignores MCP notifications. See examples/claude_code for options such as --sense-prompt, which treats what you type as a cue too.

Semantic text needs the [text] extra. The base install is small and offline. Without the extra, text is matched lexically by a hashing embedder, so "car" and "automobile" count as unrelated. For real sentence embeddings (this pulls in PyTorch):

claude mcp add madeleine -- uvx --from 'madeleine-mcp[text]' madeleine-mcp serve

Images ([image], open_clip) and sound ([audio], LAION CLAP) are opt-in the same way. [all] installs everything. Models download lazily on first use, never at import.

How it works

flowchart LR
    subgraph Sources
      HA[Home Assistant] -->|webhook| HTTP
      IOS[iOS Shortcuts] -->|webhook| HTTP
      AG[Agent] -->|MCP tools| MCP
    end
    HTTP[HTTP API /cues] --> ENG
    MCP[MCP server] --> ENG
    subgraph ENG[engine]
      direction TB
      CH["channels<br/>adapter → embedder → similarity"] --> FU["fusion<br/>base · coverage · salience · decay"]
      FU --> RF{"push?<br/>threshold + refractory"}
    end
    ENG <--> DB[(SQLite + sqlite-vec)]
    RF -->|enqueue| Q[(recollection queue)]
    Q -->|drain| HOOK[Claude Code hook]
    Q -->|drain / notify| MCP
  • Channels are pluggable senses. Each one has an adapter (raw signal → normalised observation), an embedder (observation → vector) and a similarity function (→ [0, 1]). Built in: text, time (hour, weekday and season, all cyclical), geo (distance on the sphere), biometric, image, audio. smell is a documented stub for future e-noses.
  • Recall combines per-channel similarity over the channels the cue and the memory share, adjusts for how much of the cue is covered, boosts salient memories, and decays with age down to a floor, so decades-old memories stay reachable. The formula is short and explainable; every result says which channels matched (ADR 0001).
  • Two modes. Pull: the agent asks (recall). Push: a cue arrives (sense, POST /cues) and memories that clear the threshold are queued without anyone asking.
  • Habituation. A memory that fired stays quiet for a refractory period (default 6 h). Walking past the same café all afternoon reminds you of it once.
  • Context alone never fires. "It's 9 am" matches every memory made at 9 am, so time (and biometric) can strengthen a match but never cause one.

Sending cues

madeleine serve --transport http          # REST + MCP (streamable HTTP at /mcp/) on 127.0.0.1:8765
curl -s localhost:8765/cues -H 'content-type: application/json' \
  -d '{"lat": 48.30195, "lon": 1.24650, "signals": [{"channel": "text", "value": "smell of lime-blossom tea"}]}'
Route Purpose
POST /cues Generic cue: signals, at, geohash or lat/lon, tags, source
POST /cues/{generic,home_assistant,ios_shortcuts} Payloads straight from those tools (examples)
POST /memories, POST /recall, DELETE /memories/{id} Same as the MCP tools
GET /recollections/pending · POST /recollections/drain Peek / take queued involuntary recollections

Ready-made integrations: Home Assistant (arriving home, media starts playing, wearable sensors), iOS Shortcuts ("Arrive at location" personal automation), curl.

MCP surface:

  • Tools: remember, recall, sense, pending_recollections, forget.
  • Resource: recollections://pending, with resources/updated notifications when something surfaces. These notifications also work across processes that share the database.

Privacy and security defaults

  • Local-first. Everything lives in one SQLite file (~/.madeleine/madeleine.db, owner-only permissions). There is no telemetry and no cloud.
  • Raw media is not kept. Image and audio bytes are embedded and then discarded. Only vectors and metadata are stored, unless you pass store_raw=True (per call or MADELEINE_STORE_RAW=1).
  • No local file access by default. Clients must send media bytes. Allowing file paths takes madeleine serve --allow-file-uris.
  • HTTP binds to 127.0.0.1. Host and Origin checks block DNS rebinding. Set MADELEINE_API_TOKEN to require a bearer token. Binding to another interface without a token is refused unless you pass --insecure-no-auth.
  • forget deletes everywhere: memory, vectors, raw bytes, trigger history and queued recollections, with SQLite secure_delete on.
  • Recollections are data, not instructions. The hook wraps them in delimiters and labels them as such. Anyone who can write memories can still put text in your agent's context, so keep write access to yourself. See SECURITY.md.

Benchmark (and where it fails)

madeleine bench runs an offline synthetic benchmark: 500 memories across 3 cities, 300 cues with known ground truth, distractors and hard negatives. The numbers below are for the offline hash text backend. Full tables, methodology and blind spots are in docs/benchmarks.md.

cue category pull R@1 pull R@10 fires its memory (hit rate) precision of fires
place + hour 0.57 0.60 0.81 0.56
text paraphrase only 0.93 1.00 0.21 1.00
text + place + hour 0.79 1.00 0.67 0.80
exact same photo / sound 1.00 1.00 1.00 1.00
slightly altered photo / sound 0.08 0.21 0.00 –
memory several years old 0.63 0.93 0.40 0.86
negatives (distractors, time-only, same place + other topic) false-trigger rate 0.00

Latency with 500 memories: sense p50 11 ms and p95 23 ms, in pure Python.

Honest limitations

  • Involuntary recall is tuned to be conservative: no negative cue fired, but only about half of positive cues fire their memory.
  • With the hash backend, text alone rarely intrudes (21 %) even when it is findable (R@10 = 1). Semantic embeddings ([text]) should help, but those numbers are not reported here yet.
  • Altered media never matches with the hash backend. That needs the CLIP and CLAP extras, and their similarity rescaling has only been calibrated on synthetic inputs.
  • Place + hour cues saturate (3 fires per cue), and only 56 % of those fall in the strict "same place, same hour ±1 h" ground truth.
  • The fusion is a hand-designed formula, not a learned one. The data is synthetic English text. There is no multi-user auth and no UI. The olfactory channel is a stub.
  • Hour-of-day uses each timestamp's own UTC offset. Send local offsets (+02:00), or "evening" means UTC evening.

Configuration

Env var Default Meaning
MADELEINE_DB ~/.madeleine/madeleine.db SQLite path (:memory: allowed)
MADELEINE_TEXT_BACKEND auto auto (sentence-transformers if installed), sentence-transformers, hash
MADELEINE_CHANNELS all built-ins Comma-separated enabled channels
MADELEINE_STORE_RAW off Persist raw media bytes
MADELEINE_ALLOW_LOCAL_FILES off Let media signals reference local files
MADELEINE_API_TOKEN unset Bearer token for the HTTP API (and the hook's --url mode)
MADELEINE_ALLOWED_HOSTS loopback Extra Host/Origin names accepted by the HTTP server

CLI: madeleine serve | remember | sense | recall | pending | forget | demo | bench | hook claude-code (madeleine --help).

As a Python library

from madeleine.config import load_config
from madeleine.core.serde import context_from_fields
from madeleine.engine import create_engine

engine = create_engine(load_config(db_path="memories.db", text_backend="hash"))
now = engine.now()
engine.remember("Ana proposed at the Eiffel Tower",
                context=context_from_fields(now=now, lat=48.8584, lon=2.2945, salience=0.9))
cue = engine.build_cue(context=context_from_fields(now=now, lat=48.8585, lon=2.2946))
for rec in engine.sense(cue):          # involuntary: fires on place (+ time) alone
    print(rec.to_prompt_line())

Write your own sense with an Adapter, an Embedder and a similarity function (docs/channels.md). ImageBind is deliberately not a dependency because its license is CC-BY-NC, but you can plug it in yourself as an Embedder.

Development

See CONTRIBUTING.md. Run uv sync once, then before every commit: uv run pytest, uv run ruff check src tests, uv run mypy. The architecture is in ARCHITECTURE.md and the design decisions are in docs/adr.

License

Apache-2.0. See LICENSE.

Metadata

Release files for madeleine-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for madeleine-mcp 0.1.0
File Size Uploaded Provenance Actions
madeleine_mcp-0.1.0.tar.gz 234.1 kB With provenance Details for madeleine_mcp-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for madeleine-mcp 0.1.0
File Interpreter ABI Platform Actions
madeleine_mcp-0.1.0-py3-none-any.whl Python 3 none any Details for madeleine_mcp-0.1.0-py3-none-any.whl

Total release size: 395.7 kB

Release files / madeleine_mcp-0.1.0.tar.gz

Download URL madeleine_mcp-0.1.0.tar.gz
Size 234.1 kB
Tags Source
SHA-256 checksum
How to use checksums
be40b58495ff564422c11aae00f5729e2482adf3ac7216c909c4ea34e7c52a4d
BLAKE2b-256 checksum
How to use checksums
f95fc8a2ab1982cc5e4315cc1927e9d75f5d5ea2d3af08dbeefdcc15ac81eb02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / madeleine_mcp-0.1.0-py3-none-any.whl

Download URL madeleine_mcp-0.1.0-py3-none-any.whl
Size 161.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6f7394aea1fe3a9cd3e7ccdbb57f4774e83301a1c1100300cf1ea7459153ab7e
BLAKE2b-256 checksum
How to use checksums
bdc33411023f3dff402c04d2100aa1a3c4e02d414fe54cbbed4864a72d1b0fdf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Security for madeleine-mcp 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for madeleine_mcp-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page