Skip to main content
Glama

lean-memory

test PyPI Wuesteon/lean-memory MCP server

Embedded, local-first agent memory. No server, no daemon, no mandatory cloud key.

Status (2026-07): first public release line (0.2.1) is live on PyPI and the MCP Registry (MCP-first launch); the Claude Code plugin ships in this repo (marketplace listing pending). Roadmap and rationale: docs/superpowers/specs/2026-07-08-strategic-direction-design.md. Public benchmark runs (LongMemEval/LoCoMo) are deliberately deferred until after launch; the harness is complete (bench/phase2_*.py) and the engine flaws it exposed are fixed — see docs/phase2-learnings.md.

from lean_memory import Memory

mem = Memory(root="./data")

mem.add("user-42", "I work at Acme Corp.")
mem.add("user-42", "I now work at Globex.")          # supersedes Acme automatically

mem.search("user-42", "where does the user work?")   # → "I now work at Globex."

lean-memory quickstart

Facts are extracted from natural language, stored in a per-namespace SQLite file, and retrieved with hybrid dense+sparse search. Old facts are never deleted — they're superseded and queryable at any past point in time.

Install

pip install lean-memory

Runs fully offline out of the box. Optional extras unlock real model quality:

Extra

What it adds

lean-memory[models]

Real embedder + reranker (Qwen3-0.6B + Ettin-32M)

lean-memory[extract]

GLiNER2 candidate generation for richer extraction

lean-memory[llm]

Ollama-backed LLM typing pass

lean-memory[mcp]

MCP server bridge for Claude Desktop / Claude Code

lean-memory[examples]

Terminal demo agent (requires anthropic SDK)

Related MCP server: engram-mcp

Quickstart

from lean_memory import Memory

mem = Memory(root="./data")   # one SQLite file per namespace, stored under ./data/

# Store facts in natural language
mem.add("alice", "I work at Stripe.")
mem.add("alice", "I now work at Vercel.")   # supersedes Stripe automatically

# Retrieve — the superseded Stripe fact drops out; only the current one is returned
results = mem.search("alice", "what does Alice do for work?", k=3)
for hit in results:
    print(hit.fact.fact_text, hit.final_score)
# → I now work at Vercel. 0.89

# Point-in-time query — what was true at a specific moment?
mem.search("alice", "employer", as_of=1_700_000_000_000, is_latest_only=False)  # epoch ms

# Always close when done (flushes WAL)
mem.close()

Demo Agent

A terminal chatbot showing the full memory loop — add, retrieve, supersede, restart. The demo script lives in the repo (it is not installed with the package):

git clone https://github.com/Wuesteon/lean-memory && cd lean-memory
pip install -e '.[examples]'
export ANTHROPIC_API_KEY=sk-ant-...
python examples/chat.py                  # uses offline stubs by default
python examples/chat.py --namespace bob  # separate memory tenant, persists across restarts

No API key? The demo still runs — it echoes the retrieved memory context instead of calling Claude, so you can watch the engine work offline.

MCP Server — memory for Claude Code / Claude Desktop

Give any MCP agent persistent local memory: three tools (memory_add, memory_search, memory_clear), one SQLite file per namespace, nothing leaves your machine.

pip install 'lean-memory[mcp,models,extract]'

First run downloads three open models (~2.0 GB total: Qwen3-Embedding-0.6B

  • Ettin-32M reranker for retrieval, plus GLiNER2-base (~0.8 GB) for real extraction — all ungated). Pre-warm once so your MCP client never waits on a download:

python -c "from lean_memory.embed.sentence_transformer import SentenceTransformerEmbedder; \
from lean_memory.retrieve.rerank import CrossEncoderReranker; \
SentenceTransformerEmbedder().embed_one('warm'); CrossEncoderReranker().score('warm', ['up']); \
from lean_memory.extract.gliner_extractor import Gliner2Generator; from lean_memory.types import Episode; \
Gliner2Generator().generate(Episode(namespace='w', raw='I work at Acme.', t_ref=0, source='user'))"

Claude Code:

claude mcp add lean-memory -- lean-memory-mcp

Claude Desktop — add to mcpServers (or copy examples/mcp_config.json):

{ "lean-memory": { "command": "lean-memory-mcp", "env": { "LM_DATA_ROOT": "~/.lean_memory" } } }

Data root: LM_DATA_ROOT (default ~/.lean_memory). Works offline-only too — the server opportunistically upgrades each backend that its extra is installed for ([models] → real embedder + reranker, [extract] → GLiNER2 extraction) and otherwise falls back to deterministic stub backends (fine for CI, semantically meaningless for real use — install [mcp,models,extract]).

What the optional [llm] extra buys. The canonical [mcp,models,extract] install has no LLM typing pass, so the ~15% of candidates that escalate — almost all of them inferential (derives) facts — are typed by a deterministic stub instead of a model. Assertional facts are unaffected; inference-type facts are effectively second-class on the default path. Adding [llm] (a local Ollama model) upgrades that escalated tier to real constrained typing. See ARCHITECTURE.md → Known Limitations.

Sleep-time maintenance & review

Memory accumulates cruft: the same fact restated a dozen ways, old records that never come up, clusters begging to be summarized. lean-memory cleans it up the way sleep consolidates memory — an offline job you run off-hours that dedupes, summarizes, and demotes low-value records, then hands you the judgment calls to click through the next morning, in the web console or conversationally in Claude Code.

The CLI (lean-memory-maintain) is the primary trigger. It is dry-run by default — it reports what it would do and writes nothing:

lean-memory-maintain --root ~/.lean_memory              # dry-run: report only, zero writes
lean-memory-maintain --root ~/.lean_memory --apply      # auto-apply safe transforms + stage the rest
lean-memory-maintain --root ~/.lean_memory --auto-only   # with --apply: ONLY the provably-safe band, stage nothing
lean-memory-maintain --root ~/.lean_memory --json        # one machine-readable object, stable keys

--root defaults to $LM_DATA_ROOT; add --namespace NS to run a single namespace instead of every *.db under the root. Overnight, on a schedule — one crontab line runs the safe band nightly at 3am and stages everything else for you:

0 3 * * *  lean-memory-maintain --root ~/.lean_memory --apply >> ~/.lean_memory/maintain.log 2>&1

Next-morning review in Claude Code. Judgment calls (near-duplicate merges, summaries, evictions) are staged as proposals — nothing changes in stored memory until you approve. Run the /review-memory plugin command (or invoke the review-memory-maintenance MCP prompt on the console server) and Claude walks you through the queue, grouped by entity with before/after evidence, recording only the verdicts you give. Four MCP tools back it — memory_maintenance_run (dry-run by default, like the CLI), memory_maintenance_status, memory_review_queue, and memory_review_decide — available on the core lean-memory-mcp server and both console MCP surfaces. Set LM_MAINT_AUTO=1 to opt into a background auto-run (safe band only) on the first tool call of a stale namespace; it is off by default.

Or click through it in the console. The memory console ships a Review page: the same queue grouped by entity, before/after evidence per proposal (both texts + cosine for near-duplicates, sources + proposed text for summaries, score evidence for evictions), with Approve / Keep / Edit-then-approve / Promote verbs, batch-approve per entity, and a run-maintenance button (dry-run by default; apply sits behind a confirm). Both frontends drive the same proposal store with compare-and-set decisions, so deciding in one place shows up as "already decided" in the other instead of double-applying.

The safety story in one paragraph. Nothing is ever deleted — maintenance only appends, retires (the same superseded_by flip ordinary supersession uses), or demotes to a cold tier, so your full history stays queryable as-of any past point in time, bit-for-bit identical at the store predicate and pinned by executable tests. Only two transforms auto-apply: exact-duplicate retirement and a strict eviction band; everything judgmental is staged for a human, and an unreviewed proposal expires after 30 days rather than auto-applying — silence is never consent. Cold-demoted facts stay reachable via as_of queries and search(..., include_cold=True), and promotion back to the hot tier is explicit-only, so a read never durably changes what your agent sees.

Real Model Quality

The default backends are offline stubs — deterministic and dependency-free, but semantically meaningless. Swap in real models for production-quality retrieval:

pip install 'lean-memory[models]'

With Qwen3-Embedding-0.6B + Ettin-32M reranker, retrieval jumps from 1/5 to 4/5 on the internal benchmark with zero code changes.

For benchmark results, architecture decisions, and implementation status see ARCHITECTURE.md.

How It Works

Each mem.add() call runs a 4-pass hybrid extraction pipeline:

  1. Rules — regex + dateparser for common predicates (works_at, lives_in, …)

  2. GLiNER2 — open-vocabulary NER candidate generation (offline stub by default)

  3. Router — recall-biased escalation: low-confidence, coreference, and inferential (derives) facts escalate to the LLM pass

  4. LLM typing — constrained relation typing via a local Ollama model (stub by default)

Contradiction detection runs cheap-first (slot match → cosine → token subsumption → LLM). Conflicting facts are superseded, not deleted — the old fact stays with is_latest=False and a superseded_by pointer.

Entity identity resolves on a normalized name key — NFC + Unicode case-fold + whitespace collapse — so Acme, ACME and acme are one subject and dedupe/supersession actually apply to them. It is a full Unicode fold, not SQLite's ASCII-only NOCASE: Café/CAFÉ and ЖУК/жук collate too. Punctuation and diacritics are deliberately not folded (Yahoo!Yahoo, CaféCafe), and the display name keeps the first spelling you used. The trade-off: two genuinely distinct subjects differing only by case (Mercury the planet vs mercury the metal) collate into one — nothing is deleted, and the retired fact stays readable via search(as_of=…, is_latest_only=False).

Retrieval fuses two-stage Matryoshka dense search (256-dim coarse KNN → full-dim (1024 for the default embedder) re-score) with BM25 sparse, applies RRF fusion, reranks with a cross-encoder, and scores with salience-decay (0.6·relevance + 0.2·recency + 0.2·importance).

Develop

git clone https://github.com/Wuesteon/lean-memory
cd lean-memory
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
pytest -q    # full offline suite, no downloads

Project Layout

src/lean_memory/
  memory.py                   Memory facade — the public API
  types.py                    Episode / Fact / RetrievedFact types
  store/                      Store interface + SqliteStore (vec0 + FTS5)
  embed/                      Embedder interface, FakeEmbedder, SentenceTransformer
  extract/                    4-pass extraction pipeline
  retrieve/                   Reranker interface, retrieval pipeline
examples/
  chat.py                     Terminal demo agent
  mcp_config.json             Drop-in MCP client config
tests/                        offline test suite
bench/                        Retrieval quality + BET-2 ablation harnesses

License

Apache-2.0

Available Tools

7 tools
memory_addA

Distill durable facts from text and write them to a namespace's memory.

The raw text is NOT stored verbatim: an extraction pass distills it into discrete facts, which are embedded and indexed for memory_search. Returns how many facts were written (possibly 0 if nothing extractable). Additive only — never overwrites or deletes existing facts; contradictions are handled by supersession, with full history retained. Creates the namespace on first write.

Use it after learning durable information worth recalling in later sessions (preferences, decisions, biographical facts) — not for transient chatter, and not to re-state facts already in memory (use memory_search to check what is already known; use memory_clear to delete a namespace). With the [extract]/[models] extras installed, the first call in a fresh environment downloads model weights (one-time, can take minutes); the call blocks until done.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesNatural-language text to remember (a message, note, or observation). It is distilled into discrete facts, not stored verbatim.
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations, the description states 'Additive only — never overwrites or deletes existing facts; contradictions are handled by supersession, with full history retained.' It also notes namespace creation on first write and the one-time model download that blocks, providing substantial behavioral context that annotations do not cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is organized into three short paragraphs: purpose, behavior, and usage context. Every sentence contributes unique value (e.g., return count, supersession, model download) and the core action is front-loaded. No waste or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even with an output schema present, the description explains the return value's meaning (count of facts written) and covers key side effects such as namespace creation, supersession, and blocking model download. This is sufficient for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and both text and namespace already have detailed descriptions in the schema. The description restates the distillation behavior already present in the schema without adding new parameter-specific semantics, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Distill durable facts from text and write them to a namespace's memory,' which clearly states a specific action and resource. It contrasts with siblings by focusing on adding facts, and later mentions memory_search and memory_clear as distinct alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Use it after learning durable information worth recalling in later sessions (preferences, decisions, biographical facts) — not for transient chatter, and not to re-state facts already in memory,' and names alternatives with 'use memory_search to check what is already known; use memory_clear to delete a namespace.' This provides clear when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memory_clearA
DestructiveIdempotent

Delete ALL memory for a namespace by removing its SQLite file. Irreversible.

Use only when the namespace's entire history should be forgotten; there is no per-fact deletion. Idempotent — clearing a missing namespace succeeds as a no-op. Other namespaces are untouched.

Refuses (returns an explanatory message, changing nothing) while a LIVE maintenance lease with a fresh heartbeat is held for the namespace (spec §7.3): a POSIX unlink cannot safely interrupt an in-flight maintenance run — the run's open handle would keep committing to the unlinked (ghost) inode, silently losing that work. So clear waits for the run to finish or its lease to go stale. A stale or absent lease clears normally; the maintenance runner itself independently skips a namespace cleared mid-run at its next batch boundary.

Residual race (spec §7.3, known limitation): a clear that lands in the sliver BETWEEN this lease-check and the unlink is not prevented — full cross-process file locking is deliberately out of scope for v1. The two guards (this refusal + the runner's batch-boundary skip) shrink the window; they do not close it.

ParametersJSON Schema
NameRequiredDescriptionDefault
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (destructiveHint, idempotentHint), the description discloses the file-removal mechanism, irreversibility, the lease-refusal behavior, and the known residual race condition. This is substantial behavioral context that helps an agent predict edge-case behavior and safety trade-offs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and irreversibility in the first sentence. The later sections on lease handling and race conditions are verbose but each sentence adds meaningful safety context. It is not maximally concise but well-organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's destructive and concurrency-sensitive nature, the description fully covers normal operation, edge cases (missing namespace, live lease), isolation guarantees, and known limitations. The output schema can handle return-value details, so the description is complete enough for safe invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already fully documents the 'namespace' parameter with 100% coverage, including its meaning as an isolation key and file path. The description adds no further parameter-specific detail, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific action—deleting ALL memory for a namespace by removing its SQLite file—and clearly marks it as irreversible, distinguishing it from per-fact deletion (explicitly disclaimed) and sibling tools like memory_add or memory_search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use ('Use only when the namespace's entire history should be forgotten') and when-not-to-use ('there is no per-fact deletion'), but does not name an alternative sibling tool. It also clarifies idempotency and namespace isolation, but stops short of referencing a specific replacement tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memory_maintenance_runA

Run one sleep-time maintenance pass on a namespace (§6.3).

DRY-RUN by default (apply=False): computes the full would-do report with ZERO writes — no ledger row, no proposals. apply=True claims the lease, runs the provably-safe auto band (exact-dup retirement + auto-band eviction) AND stages the judgment-call proposals for human review. Symmetric with lean-memory-maintain. NOTE the asymmetry with the LM_MAINT_AUTO auto-spawn path: that fires --apply --auto-only (auto band only, never stages proposals), so unattended runs cannot grow the review queue — only interactive apply=True stages.

Returns the run summary: mode, staged/merged/demoted counts, and threshold stats.

ParametersJSON Schema
NameRequiredDescriptionDefault
applyNoFalse (default): dry-run — compute the full would-do report with zero writes. True: claim the maintenance lease, apply the provably-safe auto band, and stage judgment-call proposals for review via memory_review_queue.
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

It discloses key behavioral details: dry-run performs zero writes, apply=True claims a lease, runs a provably-safe auto band, and stages proposals. It also notes the important asymmetry that only interactive apply=True stages proposals, which is valuable context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is logically structured with clear paragraphs and a note section. It is slightly verbose but every sentence carries meaningful operational detail, so the length is justified.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description covers all essential aspects: modes, lease behavior, proposal staging, comparison to auto-spawn, and return summary. The output schema is present, and the description still summarizes return values, making it complete for an AI agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already provides 100% parameter coverage with detailed descriptions. The tool description adds extra context by explaining the dry-run/apply distinction and the maintenance lease behavior, but it mostly reinforces rather than substantially extends the schema's parameter semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Run one sleep-time maintenance pass on a namespace', which clearly defines the tool's function. It further distinguishes behavior by explaining dry-run vs. apply modes and notes symmetry with `lean-memory-maintain`, separating it from sibling status tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly explains when to use dry-run (default) versus apply=True, and warns about the LM_MAINT_AUTO auto-spawn path, noting that unattended runs never stage proposals. This gives clear when/when-not guidance and references an alternative invocation path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memory_maintenance_statusA
Read-only

Report a namespace's maintenance ledger — runs + pending proposals (§6.3).

MODEL-FREE by contract: this reads the namespace DB directly and NEVER builds the embedder/reranker (it does not call _mem()). Answering "when did maintenance last run?" must never trigger the ~2 GB first-run model download.

ParametersJSON Schema
NameRequiredDescriptionDefault
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint and openWorldHint annotations, it discloses that the tool reads the namespace DB directly, never builds the embedder/reranker, and does not call _mem(). This explains the performance and safety characteristics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first front-loads the core purpose, the second adds a critical behavioral guarantee. It is dense but every sentence contributes value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With one well-documented parameter, a detailed schema, and an output schema present, the description covers the essential usage context and an important behavioral constraint. No gaps are apparent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides a rich description of the namespace parameter (100% coverage), and the description adds no additional parameter-specific meaning beyond referencing 'namespace' in the main text. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Report') and names the resource ('maintenance ledger') with details ('runs + pending proposals'), clearly distinguishing it from siblings like memory_maintenance_run. It references section §6.3, adding precision.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly positions the tool as MODEL-FREE by contract, explaining that checking maintenance status should never trigger a ~2 GB model download. This implies when to prefer this tool, though it doesn't name alternative tools explicitly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memory_review_decideA

Decide a maintenance proposal: approve | reject | edit | promote (§6.3).

approve applies the proposal's verbs at decide-time (with apply-time target re-validation); reject leaves the spine byte-identical; edit (summarize only) approves the human-edited text; promote (evict only) rejects the eviction and lifts the fact back to the hot tier. Returns a JSON result string.

ParametersJSON Schema
NameRequiredDescriptionDefault
decisionYesOne of 'approve' | 'reject' | 'edit' | 'promote'. 'edit' is valid only for summarize proposals; 'promote' only for evict proposals.
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.
edited_textNoHuman-edited replacement summary text. Required when decision='edit'; ignored otherwise.
proposal_idYesID of a pending proposal, as listed by memory_review_queue.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description provides detailed behavioral semantics for each decision: 'approve applies the proposal's verbs at decide-time (with apply-time target re-validation)', 'reject leaves the spine byte-identical', and 'edit'/'promote' specifics. This goes well beyond the sparse annotations (all false) and clarifies that while not read-only, reject is non-destructive to the spine. It also discloses the return type.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences: the first states the core purpose and options, the second details each option's effect, and the third states the return type. There is no filler or redundancy, and the structure front-loads the most important information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers all decision types and their effects, and the output schema covers return details. Parameters are well documented in the schema (100% coverage). The only minor gap is not explicitly stating the need for edited_text with edit, but that is in the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description's explanation of each decision adds nuance (e.g., 'edit (summarize only)') beyond the schema's enum constraints, but it doesn't add syntax or required-field details; these are fully covered by the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Decide a maintenance proposal: approve | reject | edit | promote (§6.3)', clearly identifying the verb and resource and distinguishing it from queue/run siblings by listing the four decision actions. It also specifies the effect of each decision, making the tool's purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the semantics of each decision option but does not explicitly state when to use this tool versus alternatives like memory_review_queue or memory_maintenance_run. The schema's proposal_id reference to memory_review_queue provides indirect context, but the description itself lacks explicit when-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memory_review_queueA

List pending maintenance proposals, grouped by entity, with evidence (§6.3).

Each group carries the subject entity and its proposals; each proposal includes its parsed payload (the evidence) so a reviewer sees what would change. Not fully read-only: overdue proposals lazily expire (are marked expired) as a side effect of listing. Returns a JSON string (the grouped list). Use memory_review_decide to act on a listed proposal.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindNoFilter to one proposal kind: 'dedup_near' | 'summarize' | 'evict'. Omit (null) for all kinds.
limitNoMaximum number of proposals returned.
namespaceYesIsolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description proactively discloses the non-read-only nature: 'Not fully read-only: overdue proposals lazily expire (are marked expired) as a side effect of listing.' It also clarifies the return type (JSON string). This goes beyond the annotations, which only note readOnlyHint=false, and provides essential context without contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, front-loaded with the primary purpose, then adds critical side-effect information and a pointer to the next tool. Every sentence contributes value and there is no redundancy or filler, making it highly efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the presence of an output schema and well-documented parameters, the description covers all necessary aspects: what it lists, how the data is structured, the side effect, the output type, and the relationship to sibling tools. It is complete for this list-with-side-effect utility.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and each parameter (kind, limit, namespace) already has a clear description in the schema. The tool description adds no additional parameter-level detail beyond the context of what the listing returns, so it meets the baseline but does not exceed it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'List pending maintenance proposals, grouped by entity, with evidence.' It clearly distinguishes from siblings by mentioning the follow-up tool memory_review_decide for acting on proposals, making the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides context for use (reviewing pending proposals before deciding) and explicitly directs to memory_review_decide for the next step, which acts as an alternative. However, it does not explicitly state when not to use this tool or mention other alternatives like memory_maintenance_status, so it falls short of full explicit usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 7 tool updatesv0.3.0
    • Changedmemory_add2 fields changed
      • addedInput schema / properties / namespace / description
        Added value: +"Isolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access."
      • addedInput schema / properties / text / description
        Added value: +"Natural-language text to remember (a message, note, or observation). It is distilled into discrete facts, not stored verbatim."
    • Changedmemory_clear1 field changed
      • addedInput schema / properties / namespace / description
        Added value: +"Isolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access."
    • Addedmemory_maintenance_run
    • Addedmemory_maintenance_status
    • Addedmemory_review_decide
    • Addedmemory_review_queue
    • Changedmemory_search4 fields changed
      • addedInput schema / properties / k / description
        Added value: +"Maximum number of facts to return (top-k after reranking)."
      • addedInput schema / properties / k / minimum
        Added value: +1
      • addedInput schema / properties / namespace / description
        Added value: +"Isolation key for one memory store. Each namespace is a separate local SQLite file under LM_DATA_ROOT (default ~/.lean_memory); namespaces never see each other's facts. Use one per agent, project, or user whose memory must stay separate. Created on first access."
      • addedInput schema / properties / query / description
        Added value: +"Natural-language search query; matched against stored facts by hybrid vector + full-text retrieval, then reranked."
  2. 3 tool updatesv0.1.0
    • First observedmemory_add
    • First observedmemory_clear
    • First observedmemory_search

TDQS

A4.5/5.0
Disambiguation5/5

Each tool has a clearly distinct purpose: add, search, clear, maintenance status, maintenance run, review queue, and review decide. There is no overlap or ambiguity between them.

Naming Consistency4/5

All tools use the 'memory_' prefix and lowercase with underscores. Most follow a verb-noun pattern (memory_add, memory_search, memory_clear, memory_maintenance_run, memory_review_decide), though memory_maintenance_status uses a noun phrase and memory_review_queue is ambiguous. Overall the pattern is predictable.

Tool Count5/5

Seven tools is well-scoped for a memory management server, covering ingestion, retrieval, deletion, and maintenance workflows without redundancy. Each tool earns its place.

Completeness4/5

The set covers the main CRUD-like operations (add, search, clear) plus a full maintenance lifecycle (status, run, review queue, decide). Minor gaps exist: no direct list-all-facts operation and no per-fact deletion, but these are noted as design limitations and do not severely hinder agent workflows.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Wuesteon/lean-memory'

If you have feedback or need assistance with the MCP directory API, please join our Discord server