Leteo
Leteo is a local persistent-memory MCP server for AI coding agents: it saves, organizes, searches, and curates project memories, sessions, and prompts.
Save memories & artifacts —
mem_savestores observations (bugfixes, decisions, architecture, discoveries, etc.),mem_session_summarysaves structured end-of-session summaries as memories,mem_save_promptrecords user prompts,mem_updaterevises stored memories, andmem_suggest_topic_keyproposes stable keys for upserts.Auto-extract learnings —
mem_capture_passivepulls Key Learnings from subagent output and files each as its own memory.Recall & browse context —
mem_contextreturns the pinned/recent memories, sessions, and prompts an agent opens with;mem_searchruns full-text queries with project/scope/type filters;mem_get_observationfetches a full body;mem_timelineshows chronological neighbors;mem_statsaggregates store totals;mem_current_projectdetects the active project;mem_doctorruns read-only diagnostics.Session lifecycle —
mem_session_startopens a session andmem_session_endcloses one (optionally attaching a summary).Curate & organize —
mem_pin/mem_unpincontrol what surfaces first,mem_reviewlists memories due for review or marks them reviewed, andmem_merge_projectsfolds project-name variants into one canonical project.Handle conflicts —
mem_comparepersists a semantic verdict between two memories, andmem_judgerecords a manual verdict on a pending relation surfaced by a save.Delete —
mem_deletesoft-deletes by default or hard-deletes permanently on request.
Exports memories from Leteo into an Obsidian vault as Markdown, allowing users to browse and manage their memories in Obsidian.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Leteo what do we have on the auth implementation?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Leteo — persistent memory for AI coding agents
Your coding agent forgets everything when the session ends, and most of it when the context is compacted. Leteo is the memory it keeps: decisions, bug fixes, conventions and the discoveries that were expensive to make, stored in a local SQLite database and handed back when they are relevant.
One binary, no server, no API key. Nothing leaves your machine unless you turn on cloud replication for a project you name.
Measured against itself — the same agent, the same questions, once with it and once without:
Four right answers out of four, where the agent without it got none out of three. That is the finding that survives the variance, and the tokens are the noisier half of it. Every run is here — both baselines, the fixed cost of about 15,400 tokens a session that both arms pay, and the pairing from these same seven runs that says Leteo costs 3.7x as much.
Works with: Claude Code · ZCode · Codex · Cursor · Gemini CLI · OpenCode · Windsurf · VS Code Copilot · Kilo Code · Qwen · Kiro · Antigravity · Pi · DeepSeek Harness · Command Code
What it feels like
You never prompt it to remember. That is the whole idea. Your agent opens each session already holding what the project knows, and saves as it goes while you work — a bug fixed, a convention agreed, something non-obvious learned. Those notes are written for its future self rather than for you, so they stay out of the conversation.

The rest is a SQLite file you own. leteo tui opens it, leteo export takes it
with you, and leteo delete means it.

Related MCP server: basic-memory
Does it pay for itself?
Measured rather than asserted, over thirteen runs: four right answers out of four on the questions the code cannot answer, where an agent without it got none out of three — or three out of three against that same none out of three, on the strict protocol. That is the finding that survives the variance.
The tokens are noisier. On those same questions the median saving is 27% and the best case 70% — or 17% and 44% if you drop the one run whose prompt differed by a field, which the article does for you. On questions the code does answer the difference is inside the noise, and the same thirteen runs support "costs 3.7x as much" if you pick the opposite pair.
does memory save tokens? shows every run, both baselines, the fixed per-session cost of about 15,400 tokens, and where the number is weak. The honest summary is that it does not reliably save you tokens — it stops your agent confidently answering something else.
Install
# Linux and macOS
curl -fsSL https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/install.sh | sh# Windows
irm https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/install.ps1 | iexOr through a package manager, if you already keep your tools in one. Homebrew covers macOS and Linux, on both architectures:
brew tap asanabrial/leteo && brew install leteo# Windows
scoop bucket add leteo https://github.com/asanabrial/scoop-leteo
scoop install leteo# Windows, through winget
winget install asanabrial.leteowinget takes each release as a pull request that its maintainers review, so a new version can reach it a day or two after the others.
# Anywhere with Node, if that is what you already have
npm install -g @asanabrial/leteoThat last one puts leteo on your PATH for the command line, and is the one
route that cannot configure an agent: leteo setup refuses from a binary npm
is holding, because the path it would write down is one npm deletes. Configure
the agent with the npx line instead, or
install by any of the routes above.
Then open Leteo and set your agent up from the Setup screen:
leteo tuiThat is all of it. Nothing else to install first — not Rust, not SQLite, not a
runtime: the archives are prebuilt binaries with SQLite compiled in, and each
script checks its download against the published SHA256SUMS before installing
anything. From the scripts the binary lands in ~/.local/bin, or
%LOCALAPPDATA%\leteo\bin on Windows — Homebrew, Scoop and winget put it where
they put everything else. LETEO_INSTALL_DIR moves that, LETEO_VERSION takes a
tag other than the latest release, and LETEO_BASE_URL downloads from
somewhere other than GitHub releases. Those three belong to the scripts rather
than to the binary, which is why they are not in the
Environment table.
Releases carry five builds — x86-64 Linux, Windows and macOS, and arm64 Linux and macOS. The two Linux ones ask for nothing newer than glibc 2.34, so they run on Debian 12, Ubuntu 22.04, RHEL 9 and anything later. That floor is pinned in the release workflow rather than inherited from whichever image GitHub calls latest: v0.1.0 inherited it, and wanted a glibc newer than Debian stable's. The Windows build asks for Windows 10 or later and nothing installed first: its C runtime is linked into the binary, so not even the Visual C++ Redistributable. On anything else, build from source, which is the one route that needs Rust:
cargo install leteoThat builds the released version from crates.io.
cargo binstall leteo fetches the same release binary instead of compiling it,
which is the faster half of that sentence. To build whatever is on main
instead, including work that has not been released yet, ask for the repository:
cargo install --git https://github.com/asanabrial/leteo.
Building your own checkout
To install what is in your working copy, run the script for your system from any directory:
# Linux and macOS
scripts/build-install.sh# Windows
scripts\build-install.ps1It needs rustup and cargo, installs Rust 1.97.0 if that toolchain is missing,
builds in release mode with --locked, and installs into Cargo's own root —
CARGO_INSTALL_ROOT, then CARGO_HOME, then ~/.cargo — never into the
directory the release installers use, so a source build does not overwrite a
release install. It then installs the model from assets/model/ in the checkout
(an unreleased build has no release tag to download it from), runs setup again
for each agent that already has Leteo configured, and finishes with doctor. setup here writes only the
MCP entry, which is what has to follow the binary to its new path, and it
resets that entry's --tools and --project to the defaults, so an entry you
edited by hand needs the edit again. Add
--instructions or --hooks to an agent yourself where you want them. An agent
that does not have Leteo yet is left alone; add one with leteo setup <agent>.
Set LETEO_SETUP_AGENTS to a space-separated list of agent names to set up
exactly those, or to the word none, alone, to set up no agent; empty or blank
means unset. Any step that fails stops the script
with a non-zero exit and names the command that failed. To try it without
touching your own store or agent files, point HOME, CARGO_INSTALL_ROOT and
LETEO_DATA_DIR at a temporary directory.
The search model
Search by meaning, for a question that shares no word with the memory it wants,
reads a 13 MB model that lives in a file beside the binary and not inside it. The
release archives, the install scripts, the npm wrapper and the Docker images carry
it. Every other install -- cargo install, a build from source, a package that left
it out -- fetches it once, from the files committed at the tag of its own version:
leteo model install # downloads the model of this version, checks it
leteo model install --from ./model # from a copy, with no networkleteo setup does the same when it finds none. Until a verified model is there,
search works by words only, as it always did, and leteo doctor says which of
three things is true: found and verified (and where), not installed, or there
and not the model this build accepts. The model is only ever loaded after every
file has been checked against hashes the binary was built with.
Without installing anything
Most MCP documentation assumes npx, so there is a wrapper on npm that fetches
the release binary for your platform, checks it against the same published
SHA256SUMS, and hands it every argument:
{
"mcpServers": {
"leteo": {
"command": "npx",
"args": ["-y", "@asanabrial/leteo", "mcp"]
}
}
}bunx @asanabrial/leteo mcp works the same way — it is the same package from
the same registry, and the wrapper depends on nothing but what both runtimes
already have. npm install -g above is that same package installed once
instead of fetched per run.
The npm version is the release tag, so pinning one in npm pins the binary it
fetches — a guard holds the two numbers together, because published one behind
it would quietly serve the previous release to everybody arriving this way. It
is a way in rather than the way to run it: a binary on your PATH starts
without a download and is what leteo setup writes into your agent.
As a plugin
Claude Code takes Leteo as a plugin, which registers the same MCP entry and the
same five lifecycle hooks leteo setup writes, through Claude Code's own plugin
machinery:
/plugin marketplace add asanabrial/leteo
/plugin install leteo@leteoThe plugin carries configuration, not the binary. Install leteo by one of the
routes above first: the MCP entry and every hook the plugin registers run
leteo from PATH, so without it they are five commands that are not there.
What the plugin replaces is the setup step, and removing it takes those entries
away again.
Pick one of the two, not both. Registered twice, every lifecycle event runs
twice — which stored each prompt twice, 23 identical pairs on the machine where
it was found, before anybody noticed anything was wrong. leteo setup --hooks
now looks for an installed bundle and refuses rather than adding a second
registration, naming the file it found.
Codex has the same bundle under plugin/codex, and there it is
the only route to the hooks — leteo setup codex registers the MCP server and
no hooks at all, which leaves Codex holding the tools with nothing telling it
when to reach for them.
ZCode has one under plugin/zcode, from the same marketplace.
Adding a marketplace is a desktop action, not a command: Settings → Plugins →
Create → Add marketplace, pointing at asanabrial/leteo. Then install
leteo-zcode from the list.
Its CLI does not add marketplaces — zcode plugins lists and enables what is
already installed, and nothing more. Checked against zcode 0.16.5:
$ zcode plugins --help
plugins List and enable installed plugins (`plugins list`)It registers three hooks rather than five, because that client fires neither
SubagentStop nor SessionEnd. It is also the route that does not depend on
somebody else's switch: ZCode runs configuration-file hooks only while
hooks.enabled is true in ~/.zcode/cli/config.json, which starts off, and
enabling the plugin is what enables the plugin's hooks.
Pi takes Leteo as a package. leteo setup pi writes
plugin/pi under Pi's agent directory (PI_CODING_AGENT_DIR, or
~/.pi/agent), where it is listed in settings.json, and takes out the leteo
entry an earlier setup left in mcp.json. The package opens and closes the
session, keeps your prompts and the Key Learnings of what tools return, saves
the compaction summary, appends the memory protocol to the system prompt, and
registers leteo mcp as an MCP server itself, with its tools loaded on demand
from Pi 1.2.0 on and declared on every request below it.
An entry in mcp.json would override that registration by name, which is why
setup no longer writes one. The package is also on npm, for pi install npm:@asanabrial/leteo-pi; take one route or the other, not both.
Uninstall
leteo uninstall
leteo uninstall --yes
leteo uninstall --yes --keep-dataThe first reports what would go and changes nothing. The second carries it out: Leteo leaves every agent it configured, and then the machine, search model included. On Windows it also registers itself in Installed apps, so it can be removed from there instead.
--keep-data is for the one thing that cannot be fetched again. With it, the
data directory is left where it is — the store, settings.json, and any
migration copy beside them — and only the agents and the binary go. Without it,
the store and its -wal/-shm files are removed.
Neither form removes a file Leteo did not create. A copy you made by hand — the
leteo.db.bak-before-migrate the upgrade notes used to ask for — is yours and
stays, whatever it is named, and so does a leteo.db.pre-schema-N copy a
migration left. leteo uninstall with no --yes lists exactly what would go
and what would stay before anything is touched.
To leave one agent and stay in the rest:
leteo setup claude-code --uninstallThat takes out the MCP entry, the lifecycle hooks and the protocol block, and nothing else — other servers, other tools' hooks and your own notes stay where they are.
MCP
A 23-tool MCP server over standard input/output, run directly or written into a
client by leteo setup:
leteo mcpRun like that it offers all of them. Three of the twenty-three change or count
the whole store, and leteo setup leaves those out: what it writes into an
agent is --tools=agent, the twenty an agent reaches for while it works.
--tools picks a profile or a single tool, and --project fixes the project
for the process.
Alongside it there is a JSON command line, an interactive terminal UI, and setup support for fifteen MCP clients — the list at the top of this page.
Its name in the MCP Registry is
mcp-name: io.github.asanabrial/leteo, which is what server.json
publishes. The line is written out rather than hidden in a comment because that
is what the registry reads to believe this repository owns the crate, and
crates.io strips HTML comments when it renders this file.
HTTP
For a script or a plugin that already speaks HTTP, leteo serve opens the same
tools on the loopback — no MCP client, and no process per call:
leteo serveGET /tools names the endpoints, GET /health says it is up, and
POST /tools/<name> takes that tool's arguments as its JSON body — the same
name and the same arguments the MCP tool takes, so there is one vocabulary to
learn:
curl -s http://127.0.0.1:7437/tools/mem_save -d '{"title":"A decision","content":"what and why","type":"decision"}'It is the same store and the same rules as the MCP and CLI paths, because it is
the same handler: a save over HTTP is indexed, normalised and queued for
replication exactly as one from an agent. It binds the loopback by default;
--bind 0.0.0.0:7437 exposes it to the network and the log says so, because the
store holds everything the agent has remembered.
What you type
Rarely anything: the saving and the recalling happen without you. This is the
store from the outside, for the times you want to look yourself. Every command
that answers prints JSON — tui is the exception, being a screen rather than an
answer — and the default database is ~/.leteo/leteo.db.
Reading it. search is the one you will actually use, and --all-projects
widens it past the project you are standing in. recent is the last few in time
order; context is the block an agent is handed when a session opens, so it
shows what yours are starting with; timeline reads what was saved either side
of one memory; stats counts what is there. tui is all of it on one screen.
leteo search "connection pool" --project leteo
leteo search "connection pool" --all-projects
leteo recent --project leteo --limit 20
leteo context leteo --scope project
leteo timeline 42 --before 5 --after 5
leteo stats
leteo tuiWriting by hand. Seldom needed, since the agent saves as it works — but a
memory you want in your own words, the session boundaries an agent would
otherwise draw for you, and consolidate, which folds several memories into one
and records what it replaced rather than deleting it:
leteo save "SQLite architecture" "One writer, many readers" --project leteo --type architecture
leteo consolidate 12 13 --title "SQLite architecture" --content "One writer, many readers" --type architecture
leteo session-start session-1 --project leteo
leteo session-end session-1Setting an agent up. On its own it walks through it; naming an agent does
that one. --hooks adds the lifecycle hooks that make memory automatic, and
--dry-run reports every file it would touch without writing one:
leteo setup
leteo setup claude-code --hooks
leteo setup opencode --dry-runKeeping it well. doctor runs every check and says which one failed and
why; --repair carries out the repairs that are safe to make on their own —
restoring missing full-text triggers, rebuilding the indexes, recomputing
stale hashes, folding the memory types an older adoption left unfolded, and
replaying any captures a busy hook kept beside the store.
export and import move a store between machines, and obsidian-export
writes it into a vault as Markdown:
leteo doctor
leteo doctor --repair
leteo export --project leteo --output leteo-export.json
leteo import leteo-export.json
leteo obsidian-export --vault C:\Vaults\Notes --project leteoProjects. A project is worked out from the directory, so the same work can
end up filed under two names. consolidate folds a group of them into one name,
prune drops the ones holding no memories at all:
leteo projects list
leteo projects consolidate --project leteo --apply
leteo projects prune --applyConflicts. When a new memory looks like it contradicts an older one the two
are paired and the agent settles the pair. These read the same pairs from
outside: list and show for what is there, scan to look for pairs nobody
has recorded yet, stats to count them by verdict:
leteo conflicts list --project leteo --status pending
leteo conflicts show 7
leteo conflicts scan --project leteo --apply
leteo conflicts stats --project leteoDeleting. Without --hard a memory is marked deleted and stops coming back
in answers; with it, the row is gone and its relations are cut. A project takes
the same flag. A session takes none, and while it still holds memories deleting
it is refused outright and says how many — a session goes when it is empty, not
by taking its memories with it:
leteo delete observation 42 --hard
leteo delete session session-1
leteo delete project leteo --hardprojects consolidate, projects prune and conflicts scan change nothing
until --apply: without it each one reports exactly what it would do.
Languages
Three settings, because they answer three different questions.
interface is Leteo's own screens: the panels, the menus, the help. Thirteen
languages — English, español, português, français, Deutsch, italiano, română,
català, galego, euskara, Nederlands, polski, svenska — deliberately the same
thirteen offered for memories, from the same table. Left unset it follows the
machine's locale, so a Spanish computer gets a Spanish dashboard without being
asked. Romanian was translated by a machine rather than written by somebody who
speaks it, and the menu that offers it says so; the other twelve were not.
voice_language is what Sardi speaks, and it is separate because those
lines are written into your agent's conversation rather than onto Leteo's
screens. Working in English on a Spanish machine is an ordinary thing to do.
Left unset it follows interface. It is the same thirteen languages.
language is what memories are written in. It is handed to a model rather
than parsed, so it is free text and not limited to the thirteen above: español,
Spanish, português do Brasil and 日本語 all work. Left unset, each memory
is written in the language of the conversation that produced it.
A memory keeps the language it was written under, so changing this affects only
what is written from then on: the memories already saved are not converted, and a
search over them still stems them the old way. Setup warns you with the count
before the change is confirmed, and leteo doctor reports the split — how many
memories are in each language — without calling it a fault.
Settings
Those three and three more are kept in settings.json, in the data directory —
~/.leteo/settings.json unless you moved it. The Setup screen writes the file,
and it is also meant to be opened by hand: a value it cannot read costs that one
setting rather than the whole file. Nothing says so at the time, though, because
a hook must not fail while you are mid-edit — leteo doctor is what names a
setting being read past.
Key | Values | Unset means |
| one of the thirteen above | follow the machine's locale |
| one of the thirteen above | follow |
| free text | the language of each conversation |
|
|
|
|
|
|
|
|
|
The two languages are written as the language's own name — español, not es —
and read back forgivingly, because this is a file people type into: the English
name, the ISO code and the spelling without the accent all work.
voice is how much of its own work Sardi says out loud — everything, the save
reminder alone, or nothing. It is reminders unless you say otherwise: a report
line on every prompt is noise nobody asked for, and the reminder is the line that
does the work, so all is what brings the reports back. context_size is how
many memories a session opens with: twenty, thirty or eighty, for a small context
window or for a store that
matters more than the budget. semantic_search is whether a search the words
cannot answer goes on to look by meaning, in all thirteen languages, with a model
that ships beside the binary and never touches a network when you search; results
found that way are marked semantic. Turn it off if the memory it holds while it
runs is too much, or if you
want only answers that contain your words.
Two of the six are flags as well, because changing them should not mean reconfiguring an agent. Either one on its own is a whole command:
leteo setup --language "español"
leteo setup --context slimCloud
Optional, off by default, and per project. Your machine is the client; the cloud never connects back.
Turning it on takes two answers: where the server is, and which projects go
to it. config set writes the first into the data directory — into a file with
restricted permissions, because it holds a token — and enroll names a project.
Nothing replicates until both are done, and the commands below say so rather
than starting quietly:
leteo cloud config set --server https://memory.example.com --token YOUR-TOKEN
leteo cloud enroll --project leteo
leteo cloud config showconfig show reads the configuration back with the token replaced by a presence
flag, so it is safe to paste.
Once it is on, health asks the server whether it is there and answering.
status contacts nothing at all: it reports this machine's own view — what is
enrolled, how many changes are waiting and since when, and whether the last
attempt failed and with what. sync runs one cycle now, and leteo serve keeps
running them in the background — beside the HTTP API — until interrupted:
leteo cloud health
leteo cloud status
leteo cloud sync
leteo serveNot to be confused with leteo cloud serve, which is the other end — the server
itself, which you only run if you are hosting one.
Coming From Engram
Leteo is an independent Rust product derived from the workflow and MIT-licensed implementation of Gentleman Programming's Engram. It is not affiliated with or endorsed by that project, and promises no drop-in CLI compatibility.
A measured comparison — the same synthetic corpus saved into each engine through
its own write path, the same queries asked of its own search — is in
docs/comparison.md.
It reads an Engram database directly, so moving across is a copy. The first reports what it would adopt and writes nothing; the second carries it out, and refuses a second time rather than importing everything twice:
leteo import --from-engram --dry-run
leteo import --from-engramIt defaults to ~/.engram/engram.db; pass --source for another path. The copy
folds in the write-ahead log, so a running Engram's most recent memories come
across and its own file is never written to.
The second command writes into the target in one transaction. A store that already holds memories — sessions, prompts or observations — is refused rather than replaced, and the refusal names what is there; nothing is ever deleted. If the copy fails half way, the target is left exactly as it was, and running the command again is all that is needed to retry it.
Build
Leteo requires Rust 1.97 or newer.
cargo fmt --all
cargo test
cargo clippy --all-targets -- -D warnings
cargo build --releaseCI runs the tests. The formatting and the lints are on you before you commit,
which is why they are listed here and in AGENTS.md rather than
only in a workflow.
The cloud tests need a real PostgreSQL and are skipped without one. Point
TEST_DATABASE_URL at a throwaway database and run cargo test -- --ignored;
they create and drop their own schema, and are not written to share one.
Contributors — human or agent — should read AGENTS.md first.
Documentation
Longer write-ups of individual measurements live in docs/. The first
is there was nothing worth tuning: the third
search stage answers questions belonging to another project 90.2% of the time,
which is more often than it answers its own, and four rules swept across their
whole range say that is not a threshold anybody can fix.
The second is does memory save tokens?, which asks the question this project was launched with and answers it against itself: on questions the repository already answers the saving is inside the noise, on questions it cannot answer the agent without memory spends more and still gets it wrong three times out of three, and six of eleven sampled memories from the opening block turn out to be recoverable from the repository anyway — code, specs, tests and the git history together.
The third is Leteo against other memory engines, measured: the same synthetic corpus saved into Engram and two other engines as well as Leteo, the same queries asked of each engine's own search, and the numbers one run produced — generated from the harness so they cannot drift, with each engine's measured version beside it.
Environment
None of these has to be set. This is an inventory of every variable the
binary reads — a test fails the build when the binary honours one this table
leaves out — and not a list of things to configure. leteo setup writes what an
installation needs into each agent's own configuration file, and the choices you
make in the interface are kept in settings.json. Neither of them
sets a variable in your environment.
Most are a command-line flag as well, and the flag wins: the variable is read only when the command line does not answer the same question.
Variable | Flag | Purpose |
|
| Local data directory; defaults to |
|
| Explicit local SQLite path |
|
|
|
| — | A directory holding the semantic search model, looked in before anywhere else |
|
| Where |
|
| Project the MCP server trusts for the whole process; without it, the working directory decides |
|
| Agent CLI that judges conflict candidates: |
| — | Language this machine works in, when |
Two are worth a sentence more, because they are where the flag winning bites:
LETEO_TOOLSis already answered for every agent Leteo sets up: the MCP entry it writes runsleteo mcp --tools=agent. Exporting the variable afterwards changes nothing for that agent — edit the profile in its configuration file, or run the setup again.LETEO_DATA_DIRis the one with a real reason to be exported. The MCP server is started by the agent rather than by you, so a database somewhere other than~/.leteohas to reach it either through that agent's environment or as a--data-dirin the command its configuration runs.
Cloud, on your machine
leteo cloud config set persists the server and the token in the data directory
and is how this is configured. These two are read only where that file leaves
the field empty, so a setup that predates it keeps working unchanged.
Variable | Purpose |
| Cloud base URL for |
| Sync bearer token, at least 32 bytes |
leteo cloud serve reads LETEO_CLOUD_TOKEN too, as its own legacy static
token — on a machine that is both client and server, one name means two things.
Cloud, on the server
These belong to whoever runs leteo cloud serve, and they are set where that
service is defined — see
docker/docker-compose.yml. There is no wizard for
them on purpose: they are deployment secrets rather than preferences, and none
of this applies to a normal installation.
Variable | Purpose |
| PostgreSQL URL for cloud serve |
| Dashboard signing secret, at least 32 bytes |
| Managed-token HMAC pepper, at least 32 bytes |
| Optional legacy admin bearer token, at least 32 bytes |
| Required allowlist for legacy cloud tokens |
| Cloud bind host; defaults to |
| Cloud port; defaults to |
| PostgreSQL connection-pool limit |
| Maximum cloud push body size |
Browsing what the server holds. Sign in at /dashboard/login with an admin
token and /dashboard links to a read-only browse: the projects, each
project's sessions and memories, and a search over memory titles, content and
topic keys. It is server-rendered, edits nothing, and shows the current state of
the log, so a deleted memory does not appear. Being an admin lets you sign in
but is not a pass to every project: the browse shows only the projects your
token holds a grant on (all of them for a * grant), and asking for any other
project by name is refused with 403. Lists are cut at 200 projects, 100
sessions, 100 memories or 50 search matches, and the page says when it was cut.
Administering users, grants and tokens is not part of it.
License And Attribution
Leteo is distributed under the MIT License. See LICENSE and
NOTICE for upstream attribution and the exact reference revision.
Tagged binary archives also include a generated THIRD_PARTY_LICENSES.html
covering their Rust dependencies.
Available Tools
22 toolsmem_capture_passiveA
Extract and save the Key Learnings items a subagent ended with, in any of the twelve languages Leteo writes memories in. Each becomes a memory of its own, filed under the tool that produced it.
Inputs: content (required), source, session_id. Returns: hint, saved, dropped, project, +4 more
mem_compareAIdempotent
Persist a semantic verdict between two observation IDs. Semantic not_conflict is a successful no-op.
Inputs: relation (required), memory_id_a (required), memory_id_b (required), model, +2 more. Returns: sync_id
mem_contextARead-onlyIdempotent
Get pinned and recent observations plus the recent sessions and user prompts of a project. Answers about the current project unless you pass a project or all_projects. Long bodies come back as a 400-character preview marked content_truncated; read one in full with mem_get_observation.
Inputs: limit, scope, project, all_projects, +2 more. Returns: hint, count, project, prompts, +7 more
mem_current_projectARead-onlyIdempotent
Detect the current project without failing on ambiguous or invalid project context.
Returns: cwd, project, warning, error_hint, +3 more
mem_deleteADestructive
Delete an observation by ID. Soft-delete by default; hard_delete permanently removes it.
Inputs: id (required), hard_delete. Returns: id, status, hard_delete
mem_doctorARead-onlyIdempotent
Run read-only SQLite, FTS, foreign-key, and mutation-journal diagnostics.
Inputs: check, project. Returns: check, checks, issues, healthy, +16 more
mem_get_observationARead-onlyIdempotent
Get one complete observation by its numeric identifier, with its full body — unlike mem_search and mem_context, which preview it. Reads state: a memory that has been deleted is still returned here and says so.
Inputs: id (required). Returns: observation
mem_judgeAIdempotent
Record a manual verdict on a pending relation surfaced by mem_save. Manual not_conflict verdicts are persisted. Reason and evidence each come back as a 400-character preview marked reason_truncated or evidence_truncated.
Inputs: relation (required), judgment_id (required), reason, evidence, +2 more. Returns: relation
mem_merge_projectsADestructive
Merge comma-separated project name variants into one canonical project.
Inputs: to (required), from (required). Returns: canonical, sources_merged, enrolment_moved, prompts_updated, +4 more
mem_pinAIdempotent
Pin a local observation so it appears before recent observations in memory context.
Inputs: id (required). Returns: id, pinned, sync_id
mem_reviewA
List observations due for review or mark one reviewed. Actions: list, mark_reviewed. Bodies come back as a 400-character preview marked content_truncated; read one in full with mem_get_observation.
Inputs: action (required), id, limit, project, +1 more. Returns: count, action, due_omitted, observation, +1 more
mem_saveBDestructive
Save an observation to persistent memory. Without session_id, uses a stable manual-save session for the detected project.
Inputs: title (required), type, scope, content, +8 more. Returns: hint, status, project, candidates, +6 more
mem_save_promptA
Save a user prompt in an existing session. The prompt comes back as a 400-character preview marked content_truncated; keep the sync_id to link a later save to it.
Inputs: content (required), project, session_id, recovery_token, +1 more. Returns: prompt, project, project_path, project_source
mem_searchARead-onlyIdempotent
Search persistent observations by full-text query and optional filters. Answers about the current project unless you pass a project or all_projects. Long bodies come back as a 400-character preview marked content_truncated; read one in full with mem_get_observation.
Inputs: query (required), type, limit, scope, +3 more. Returns: hint, count, project, results, +2 more
mem_session_endAIdempotent
End an existing memory session and optionally attach a summary. The session comes back with its summary as a 400-character preview marked summary_truncated.
Inputs: id (required), summary. Returns: session
mem_session_startAIdempotent
Create a memory session, or return it unchanged if its identifier exists.
Inputs: id (required), project, directory. Returns: session
mem_session_summaryB
Save a structured end-of-session summary as persistent memory.
Inputs: content (required), project, session_id, recovery_token, +1 more. Returns: hint, status, project, candidates, +6 more
mem_statsARead-onlyIdempotent
Get aggregate memory store statistics. Takes no arguments and counts the whole store; for one project's counts call mem_doctor with that project.
Returns: projects, total_prompts, total_sessions, total_observations
mem_suggest_topic_keyARead-onlyIdempotent
Suggest a stable topic_key for observation upserts.
Inputs: type, title, content. Returns: topic_key
mem_timelineARead-onlyIdempotent
Show chronological context around a specific observation. Bodies come back as a 400-character preview marked content_truncated; read one in full with mem_get_observation.
Inputs: observation_id (required), after, before, project. Returns: after, focus, before, after_total, +2 more
mem_unpinAIdempotent
Unpin a local observation so it returns to normal recency order.
Inputs: id (required). Returns: id, pinned, sync_id
mem_updateADestructive
Revise a stored memory. Fields left out keep their current value. The memory comes back as a 400-character preview marked content_truncated; read one in full with mem_get_observation.
Inputs: id (required), type, scope, title, +3 more. Returns: observation
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 22 tools
Each tool targets a distinct operation: saving, retrieving, updating, deleting, session management, project handling, pinning, review, relations, and diagnostics. Even similar retrieval tools (search, context, timeline, get_observation) are clearly differentiated by their specific purpose and return format.
All tools share the 'mem_' prefix, which provides cohesion, but the action part mixes imperative verbs (save, search, delete), nouns (context, stats, timeline), and noun-verb constructs (session_start, session_end). This inconsistent pattern makes it less predictable than a uniform verb_noun convention.
With 22 tools, the set is on the heavy side but each tool covers a distinct aspect of memory management—sessions, projects, observations, reviews, and diagnostics. The breadth justifies the count, and it remains navigable due to the consistent prefix and clear individual purposes.
The toolset provides comprehensive coverage of the memory domain: full lifecycle for observations (create, read, update, delete), session management (start, end, prompts, summaries), project handling (detect, merge), plus pinning, review, relation verdicts, statistics, and diagnostics. No obvious gaps prevent core workflows.
Maintenance
Related MCP Connectors
Persistent memory for AI agents across Claude, ChatGPT and any MCP client.
Persistent AI memory shared across Claude, ChatGPT, coding agents, and compatible MCP clients.
Private memory for AI agents: save and recall facts across Claude Code, Codex, Cursor and ChatGPT.
Persistent memory for AI assistants. Save once; recall from Claude, ChatGPT, or any MCP client.
Related MCP Servers
- AlicenseCqualityBmaintenanceThis project is based on the Knowledge Graph Memory Server from the MCP servers repository and retains its core functionality.44354MIT
- AlicenseAqualityBmaintenanceBasic Memory is a knowledge management system that allows you to build a persistent semantic graph from conversations with AI assistants. All knowledge is stored in standard Markdown files on your computer, giving you full control and ownership of your data. Integrates directly with Obsidan.md21219,662 PyPI4,543AGPL 3.0
- FlicenseNot gradedqualityCmaintenanceLocal-first cross-agent memory for AI coding agents. Persistent, shared memory over MCP — what you tell one agent can be recalled by another — with all data stored in a single local SQLite file, no cloud and no API keys.-
- AlicenseNot gradedqualityBmaintenancePersistent memory for AI coding agents that stores and recalls preferences, decisions, and conventions via semantic similarity, with zero cloud dependencies and plug-and-play MCP integration for Claude Code.Apache 2.0