Echo Memory
Echo Memory lets agents write, recall, validate, and audit a shared graph of facts without the server calling an LLM.
Write memories with
write_episode: supply entities (name, type) and facts (source, target, relation_type, fact, confidence).Resolve ambiguous mentions by passing
entity_resolutionsmapping mentions to existing node IDs or "new".Extract entities and facts yourself; the server never invokes a model on writes.
Set each fact’s
confidenceto"extracted","inferred", or"ambiguous".Query memory with
query_memorybyscope(soloorshared), optionalquery,top_k, ordigest=Truefor recent active facts.See
pending_ingestwhen memory files have been written that the graph hasn’t ingested yet.Record that a recalled fact saved re-explaining with
record_recall_save, using itsfact_idand a cold-readablenote; the server reads the fact’s original author.Read the audit trail with
get_audit_log, optionally since an ISO8601 timestamp, to see writes, invalidations, supersessions, and resolutions.
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., "@Echo MemoryEcho Memory, remember that the API rate limit is now 100 req/min."
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.
Echo Memory
Shared memory for AI agents, as a graph in your own database. What Claude Code learns, Cursor and Codex can recall. Every fact records who wrote it and when, and the server never calls a model to store one.
Your agents start every session from zero. The usual fix is a notes file you paste into context, which grows until it is mostly irrelevant to whatever you are asking. Echo Memory is the other shape: facts connected to each other, and a query that returns the few that matter. On the author's own store that is 96.7% less context for the same answer, with the answer still present 87.2% of the time across 1,190 questions.
write_episode query_memory
billing ──uses──▸ Razorpay "how do we take payments"
│ written by claude-code ▸ billing uses Razorpay, not Stripe
│ supersedes ──▸ Stripe written by claude-code, 3 days ago
└ no model invoked ▸ 1,372 tokens, not 41,838Install
Requires: Python 3.11+ and Docker (for the database).
pipx install echo-mem
echo-memory quickstartquickstart starts the database, applies the schema, and prints the claude mcp add
line that registers it, filled in with the port it actually used. The Postgres image is
published, so nothing compiles.
Or use the hosted service and run no database at all:
pipx install echo-mem
echo-memory connect <key> # a key from https://app.echo-mem.comThen once per machine, so an agent knows when to record and recall rather than only that the tools exist:
echo-memory install --globalRestart your client afterwards. An MCP server is a long lived process that holds the code and config it started with, and an editable install does not change that.
The PyPI name is
echo-mem, notecho-memory. That name belongs to an unrelated hosted product. The import package and the CLI are bothecho_memory/echo-memory; only the distribution name differs.
Related MCP server: smriti-memcore
Upgrading
pipx upgrade echo-mem
echo-memory init-db # apply any new migrations
echo-memory reindex # rebuild what the new code derivesBoth steps, in that order, and neither is optional on a store that predates the version you just installed.
Skipping init-db fails loudly. A read against a schema older than the code raises an
undefined column, which is the good case: it stops rather than answering from something it
half understands.
Skipping reindex fails silently, which is worse. Derived data that the new code reads
differently is still the old data, and nothing errors. Concretely, on an upgrade to 0.5.3
the lexical corpus statistics are treated as stale until rebuilt, so BM25 stays off and
your queries keep ranking by ts_rank while the setting reports as on. reindex rebuilds
the embeddings and those statistics together. echo-memory health tells you afterwards.
Usage
echo-memory status # what each scope holds, and which agents have written
echo-memory health # a score, what is weak, and what to do about it
echo-memory dashboard --serve # the graph, in a browser, localhost only
echo-memory why <fact_id> # the full audit trail for one fact
echo-memory recall "<question>" # query the store from a terminal
echo-memory export # everything, as JSON
echo-memory install --for cursor # wire one client, project scoped
echo-memory adopt # wire every MCP client on the machine, each with its own id
echo-memory infer-causal-hints # type the facts whose own sentence states a cause (dry run)
echo-memory eval # retrieval quality against your own store
echo-memory eval --context # what a recall costs against injecting everything
echo-memory eval --context --sweep # the same, as a curve across corpus size
echo-memory eval-external locomo <path> # LoCoMo, the corpus the published figures use
echo-memory eval-external longmemeval <path> # LongMemEval, the same
echo-memory calibrate # is entity resolution trustworthy on your data
echo-memory benchmark # write, query and digest latencyThe seven MCP tools
Tool | What it does |
| Store entities and the facts connecting them. No model call. |
| Hybrid vector and full text retrieval, fused by reciprocal rank. Full text ranks by BM25. Takes |
| Causal chains through the entities a subject matches, not a ranked list. |
| Mark that a recalled fact saved re explaining something. Refuses a fact no read returned. |
| Every change to memory, with a plain language reason. |
| Memory files this project wrote that the graph has not heard about. |
| Close one of those out. |
What you get
A graph, not a list. Entities are nodes and a fact is an edge between two of them. Two sessions that never knew about each other resolve onto the same entity by name, so the second inherits what the first learned.
Bounded retrieval, designed but not built. The plan is that old, rarely read memory
demotes into higher level summaries over time, with nothing discarded and every summary
still edged back to the facts it came from. None of it exists yet: there is no tiering,
no summarisation, and retrieval today walks every active fact in the scope. It is
described in docs/designs/ and listed below as v1c, and this paragraph
used to claim it in the present tense.
A ranked answer that says why each fact is in it. Every returned fact carries
score, its cosine similarity to the query, rank, its position, and matched, the
channels that found it. score is deliberately not the fusion number: reciprocal rank
values are sums of 1/(k+rank) and mean nothing from one query to the next, while a
similarity means the same thing every time, which is what a caller thresholding on it
needs. It is computed for every fact returned, including the ones only full text search
found, because which channel retrieved a fact is an implementation detail and has no
business reaching a field callers read as relevance.
The full text channel ranks by BM25. Postgres's ts_rank counts term occurrences and
nothing else: no inverse document frequency, so a word in every fact of a scope counts as
much as one in three; no saturation, so repetition scales linearly; no length
normalisation, so a long fact is punished for being long. On 324 real prompts the rank 3
and rank 4 scores were identical in 59% of them, and reciprocal rank fusion reads rank
position and never the score underneath it, so a channel ordered by scan order handed the
fusion a coin flip.
BM25 is computed in SQL rather than installed, so no extension is added to your database.
Corpus statistics are rebuilt amortised on write inside the transaction that already holds
the scope's lock, and by echo-memory reindex; a scope that has none falls back to
ts_rank rather than ranking on statistics it does not have. ECHO_MEMORY_LEXICAL_BM25=0
returns to ts_rank everywhere.
Facts about an entity, which resemblance cannot express. query_memory takes
about, one or two entity names, and returns only the facts recorded against them. One
name gives the edges incident to that node; two give the edges between them in either
direction, which is "both names in the same fact" without going near the fact text.
This is not a ranking improvement and it is not a substitute for one. A query about one team returns a semantically close fact about a different team because the wrong fact genuinely does resemble the question, and no amount of better ranking removes it. Identity is not similarity, and a fact is an edge between two nodes, so asking which entity a fact is about is structural and exact.
Name matching is exact and case insensitive, never a substring, because a short name is a substring of longer unrelated words. A name nothing is recorded under returns no facts rather than the nearest thing, and so does a pair with no fact joining them: that is real absence and it means nobody recorded this, not here is something adjacent. More than two names is refused rather than answered empty, because an edge has two endpoints and a fact about three entities is a question this model cannot express.
The history the store has always kept is readable. Every fact has carried t_valid
and t_invalid since the first migration, and the read path had only ever asked whether a
fact is current, so the store paid to keep the whole record of what a scope believed and
could not answer the first question a post mortem asks. query_memory takes as_of in
unix seconds and every channel honours it: vector, full text, the graph hop, the digest.
Supersession is what makes that more than a curiosity. Writing the same source, target and
relation again does not edit the old fact, it ends it: the old edge takes a t_invalid and
stays queryable. Nothing is ever rewritten, so the history is real rather than
reconstructed, which is the property the memory reconsolidation literature gives up and
the reason this project refuses that idea.
Provenance on every fact. Who wrote it, which tool, which project, when, and which reads returned it. A superseded fact is never deleted. It stops being drawn and stays reachable with its history.
Causal typing, and a walk over it. A fact can carry causal_hint: one of
caused_by, led_to, enabled_by, blocked_by or contradicts, set by the agent's
own read of what the session said and never inferred statistically. trace_cause walks
those links and returns chains rather than a ranked list, because the answer to "why did
this happen" is an ordered chain and similarity cannot produce one. "A led_to B" and "B
caused_by A" are one claim written from opposite ends, and both assemble into the same
chain.
A fact with no hint is associative, which is the default and usually correct. An empty
answer from trace_cause says which kind of empty it is: nobody recorded a cause here is
a different fact about a store than there are no chains.
Filling it in, for a store that predates it. Facts written before 0.5.0 carry no
hint, so on an existing store trace_cause has nothing to walk.
echo-memory infer-causal-hints re-reads the fact text already stored with your own
model (ECHO_MEMORY_LLM_API_KEY, ECHO_MEMORY_LLM_MODEL) and types the edges whose own
sentence states the relation. Dry run by default; --write applies what it printed and
--clear --write takes it back.
This is extraction done late, not causal discovery, and the difference is enforced rather
than promised. A sentence with no causal connective is never sent to a model, so
co-occurrence is refused before it costs anything. Every proposal has to quote the words
that state the relation, and a quote that is not literally in the fact is dropped, so a
model reasoning from the world instead of reading the sentence gets nothing stored. Each
hint written is audited and marked as extracted late, which is what makes --clear
able to remove exactly these and never one you wrote at write time.
No inference on the write path. Extraction happens in the calling agent, so storing
a memory invokes no model on the server. infer-causal-hints above is not an exception
to that and is worth being precise about: it is a command a person types against facts
already stored, it never runs as part of a write, a migration or a hook, and a store that
never runs it never causes a model call. The cost moved rather than vanished: the agent
has to arrive with entities and facts already extracted, which is what the
tool contract spells out. The comparison that makes this matter is
Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
"every episode triggers multiple LLM calls" and that "write cost scales with volume".
The obvious reply is that cheap writes are cheap because they do less, and that reply is
correct on the mechanism. docs/WRITE-COST.md answers it properly,
including the two measured costs of the choice: the Stop gate fired seven times and
produced one fact, and a write touching an ambiguous entity is deferred while the call
returns as though it succeeded.
Any MCP client. A coding assistant, a chatbot, an ops agent, or something built in house. Coding agents are where this is proven, not what it is limited to.
Numbers, and how they were taken
Every figure comes from this repository or a live store, on a date, with the command that reproduces it on yours. The corpus is small and the noise floor is stated, because a difference nobody sized is not a result.
Measure | Value | Reproduce |
Context per recall vs injecting everything | 96.7% less, hit@10 0.872 over 1,190 questions |
|
The same saving across 8x of corpus growth | 75.5% at 32 facts rising to 96.4% at 261, hit@10 0.900 to 0.946 |
|
LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 0.601, hit@10 0.658, MRR 0.460, session@10 0.850 |
|
LongMemEval retrieval, 90 questions, 15 per type | session@10 0.937, recall@10 0.727, MRR 0.428 |
|
Lexical BM25 against | +0.0271 MRR on entity_pair [+0.0083, +0.0481] and on multihop [+0.0166, +0.0381], the other two shapes inside noise, none worse |
|
Server side model calls per write | 0 |
|
Write, query, digest latency (median) | 15ms, 8ms, 1ms |
|
Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] |
|
That last row is the one that went the wrong way, and it is here on purpose. The interval includes chance, so the unattended merge is switched off: at the automatic bar precision was 50% over two reviewed pairs, and the audit log showed that path had fired once in the system's entire history. A near match is now offered for confirmation instead.
The LoCoMo row is retrieval, not QA accuracy. Published LoCoMo results have a model write
an answer and a second model judge it; this asks only whether the turn holding the answer
came back, which is a ceiling on QA accuracy rather than a substitute for it, and is not
comparable to anybody's published QA figure. It also feeds raw dialogue turns, which skips
the extraction step this design pushes to the calling agent, so it is a floor as well as a
ceiling. The worst row, multi hop at recall@1 0.099, is in
docs/BENCHMARKS.md with the rest.
The context saving is measured against a specific baseline, stated so it cannot be read as more than it is. Not "no memory at all", which is however long a human spends re explaining and is unmeasurable. It is the thing people do instead: keep the project's notes in one file and paste the whole file. On that store the file is 325 facts, about 41,838 tokens; a recall returned 1,372 on average. The hit rate belongs beside it, because a recall that returned nothing would score 100%.
The graph
Memory is a graph, not a list of notes. Entities are nodes; a fact is an edge between two of them. That is the whole data model, and everything else follows from it.

Three projects here. checkout-api, mobile-app and data-pipeline were recorded in
separate sessions and never told about each other, yet the picture already separates them,
because separation is a property of the edges rather than a label anyone applied.
Clusters come from structure. Densely connected facts are grouped by label propagation
over the edges, and each cluster is named after its most connected node. That is why
data-pipeline sits apart: nothing it knows touches payments. It is also why
checkout-api and mobile-app share a cluster despite being different codebases. They
genuinely share an idea, and the graph found it rather than being told.
Components are the stronger claim. Two nodes in different components have no path between them at all, which is the strongest statement this graph can make that two memories are unrelated.
Click a node: everything it takes part in

idempotency keys is the largest node here and nobody made it large: seventeen facts from
several services resolved onto one entity by name. The panel lists every one, with which
agent wrote it and when.
Click a link: why memory believes it

Not a tooltip. Who wrote the fact, in which project, when, and how each of its entities
resolved. echo-memory why <fact_id> prints the same trail in a terminal.
Seeing your own
echo-memory dashboard --serve --openThe images above come from a synthetic dataset (scripts/demo-seed.py) rather than a real
store, for the obvious reason: a real memory graph is full of hostnames, account numbers
and client names.
Wiring more than one tool
Give each client its own ECHO_MEMORY_AGENT_ID. Cursor should say cursor, Claude
Desktop claude-desktop. Memory is shared either way, but a fact records which tool
learned it, and two tools claiming the same id makes cross tool recall impossible to see
afterwards.
echo-memory adopt # every MCP client on the machine, each with its own id
echo-memory install [path] # one project: MCP config plus a skill, committed with the codeadopt shows the diff before writing anything. For an agent that does not speak MCP, see
docs/INTEGRATIONS.md.
Is the graph in good shape?
echo-memory healthA score, what is strong, what needs attention, and what to do about each, including what
recall has cost: how often memory was read, how often a read returned anything, roughly
how many tokens were injected, and how many saves those reads produced. Writes were
counted from the start; reads were not counted at all, so nothing could answer whether
recall earns what it costs. It exists to be run when you have no question, because a store
can look healthy by every other number while most of its facts came from a bulk import,
the last real write was a week ago, and only one of several wired agents has ever written
anything. --json for machine readable output.
Nothing in it is gated. The paid plan sells hosting; diagnostics about your own data are not a thing to withhold from the person whose data it is.
Architecture
Storage PostgreSQL with pgvector and Apache AGE, from a single local agent up to an
organisation wide shared graph, with no forced migration later. The novel work is the
memory structure and the read/write algorithm on top of it, not a new database engine.
Retrieval Hybrid vector and full text search fused by reciprocal rank in v1a, with
the full text side ranked by BM25 computed in SQL over per scope corpus statistics.
Personalised PageRank via networkx lands in v1b for multi hop associative retrieval.
Interface Model Context Protocol, so any compliant agent reads and writes the same graph.
Status
Early and staged, on purpose. See docs/designs/ for the architecture
and the v1a to v1b plan.
v1a, built | Basic recall. Seven MCP tools, thirty CLI commands, on PyPI and in the MCP registry. |
v1b, part built | Causal typing and |
v1c, designed | Consolidation: hot, consolidated and archived tiers, so retrieval cost stops tracking total facts written. Nothing implemented. |
v1.1, planned | Organisation wide tenancy: per agent, per team, or org wide graphs. |
The validated wedge driving v1a is memory shared across coding agents, which is the author's own daily pain and the case with the most evidence behind it. Everything else is the target this architecture is built toward.
Hosted
Running it yourself is free under the Business Source License for any non production use, and free in production for organisations under 50 people and under $5M revenue, with no account and no feature held back. app.echo-mem.com runs the database for you at $99 a month if you would rather not.
See CONTRIBUTING.md. Issues and pull requests welcome; please read the
design docs first so proposals fit the staged build plan. A first pull request is asked to
sign the Contributor License Agreement, once, in the PR thread.
The most useful contribution is a measurement that disagrees with one of the numbers
above. Run echo-memory eval, calibrate or benchmark on your own store and open an
issue with the output.
License
Business Source License 1.1. See LICENSE.
The source is public and stays public. What changed on 25 September 2026 is who may run it in production without an agreement:
Development, testing, evaluation, research, teaching | free, any size |
Production, under 50 employees and under $5M revenue | free |
Production, above that | |
Offering it to third parties as a hosted service |
Each released version converts to Apache 2.0 four years after it is published, and that conversion is automatic and irrevocable.
Versions published before this change remain under Apache 2.0 permanently. That includes
everything up to and including 0.4.1 on PyPI. Relicensing cannot reach back, and this note
exists so nobody has to work that out from a git history. The Apache text those versions
were released under is kept at LICENSE-APACHE-2.0.
mcp-name: io.github.ayushcodes10/echo-mem
Available Tools
7 toolsget_audit_logCInspect
Human-readable audit trail: what was written, invalidated, superseded, or resolved, and why. since is an ISO8601 timestamp; entries at or after it, chronologically ordered.
| Name | Required | Description | Default |
|---|---|---|---|
| scope | Yes | ||
| since | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden for behavioral disclosure. It usefully explains that results are chronological, filtered by 'since', and cover certain event types. However, it does not disclose pagination, response shape, or the behavior when 'since' is null.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tight and front-loaded, with the core purpose stated first and the 'since' behavior in the second sentence. It wastes no words, though the missing scope explanation prevents a top score.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotations and no output schema, the description leaves important gaps: it does not define the required 'scope' parameter, describe the return structure, or state how 'since' defaults behave. The event-type list is helpful but only partially completes the picture.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It clearly documents the 'since' parameter as an ISO8601 timestamp with ordering semantics, but it does not explain the meaning of the required 'scope' parameter at all, leaving a significant gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies the tool as a human-readable audit trail and lists the event categories it covers (written, invalidated, superseded, resolved) plus the rationale. This distinguishes it from the sibling memory operations, though it lacks an explicit retrieval verb like 'gets' or 'returns'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies this is for viewing historical audit information but gives no explicit guidance on when to choose this over write_episode, query_memory, or record_recall_save. There are no stated exclusions, prerequisites, or alternative routing conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark_ingestedBInspect
Close pending documents once their content is in the graph.
Pass the same session_id you pass write_episode. Closing a document is one of the two valid answers to a capture prompt - the other is writing the facts - so without it the gate that asked cannot tell a session that complied from one that ignored it.
The other half of pending_documents, and it exists because the queue could not be closed by the tool that had just drained it. On 2026-09-12 Codex read a pending note, wrote its facts correctly, and reported: "Its pending marker couldn't be cleared because the local echo-memory command wasn't available." It was right - marking done was CLI-only, so a tool that could do the hard half could not do the trivial one, and the file stayed queued for a tool that happened to have a shell.
Paths that are not in the queue are reported rather than silently ignored, because a mistyped path that returns success leaves a document queued forever while the caller believes it is done.
| Name | Required | Description | Default |
|---|---|---|---|
| paths | Yes | ||
| session_id | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the full burden. It usefully discloses one behavioral trait — paths not in the queue are reported rather than silently ignored, so a mistyped path cannot leave a document queued while the caller believes it is done. However it omits whether the close is reversible, what a successful response looks like, and any permission requirements, leaving real gaps for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The operational facts occupy the first two short sentences; the remaining two paragraphs are a narrative rationale including a dated anecdote about Codex on 2026-09-12. That story does not help an agent select or invoke the tool and makes the definition poorly front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with no annotations, no output schema, and 0% parameter coverage, the description covers the workflow relationship to write_episode and pending_documents well. It is still missing side-effect/reversibility disclosure and path format, which an agent needs to invoke it safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for 2 parameters, so the description must compensate. It gives real meaning to session_id (must match the one used with write_episode) and frames paths as queue entries whose mistyping causes silent failure, but it never states the expected path format (absolute file path vs queue key) or whether paths may be repeated.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a concrete verb+resource ('Close pending documents once their content is in the graph') and the narrative explicitly positions it as 'the other half of pending_documents' and the closing counterpart to write_episode. An agent can distinguish it from siblings, though the verb in the name ('mark_ingested') and the verb used ('close') differ slightly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the workflow condition clearly: closing a document is one of two valid answers to a capture prompt, the other being writing facts, so this is called after write_episode for a pending document. The session_id linkage ('pass the same session_id you pass write_episode') further anchors usage. No explicit when-not-to-use, keeping it from a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pending_documentsAInspect
Memory files this project has written that are not in the graph yet.
A hook notices a file the moment it changes; turning it into entities and facts needs a model, so it waits here for one. Read each path, call write_episode with what it states, then mark_ingested to close it.
Claude Code is told about this queue at session start by a hook. Nothing tells any other tool, so this is how they find out at all.
| Name | Required | Description | Default |
|---|---|---|---|
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it explains that a hook enqueues files on change, that a model is required to convert them to entities/facts, and that Claude Code is informed at session start while other tools are not. It stops short of stating permissions, rate limits, or whether reading has side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with what the queue is, followed by the operating procedure and system context. Slightly discursive ('turning it into entities and facts needs a model, so it waits here for one'), but every clause conveys usable information rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter queue-listing tool with no output schema, the description covers the lifecycle (enqueue by hook, process via write_episode, close via mark_ingested) and the discovery mechanism. The only real gap is the undocumented 'project' parameter and the exact shape of the returned list.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the single 'project' parameter (nullable, optional) is never mentioned in the description. There is no indication of what 'project' filters by, what its default scope is, or whether omitting it returns all projects — the description fails to compensate for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource ('memory files this project has written that are not in the graph yet') and a specific purpose (a pending queue awaiting a model). It explicitly routes to sibling tools write_episode and mark_ingested, so an agent can distinguish this queue-read from the write/mark operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit workflow: read each path, call write_episode with what it states, then mark_ingested to close it. This is clear sequencing with named alternatives. It lacks an explicit when-not-to-use clause, but the ordering constraint is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
query_memoryAInspect
Recall prior facts relevant to query, from this agent's own memory (scope="solo") or the pool shared across this user's agents (scope="shared"). Call this at session start, and any other time recalling prior context would save the user from re-explaining something.
digest=True ignores query and returns the most recently written active facts, an opt-in "catch me up"; never automatic.
as_of (optional): unix seconds. Answers with what this scope believed at that moment, including facts later superseded. Nothing here is ever rewritten, so use it for "what did we think was true when this was decided".
about (optional): one or two entity names. Returns only facts that ARE about them, not facts resembling your query. One name gives that entity's facts, two gives the facts between them, either direction: about=["Kohli"] or about=["Kohli", "Bumrah"]. At most two names, matched exactly and case-insensitively, never partially. A name nothing is recorded under returns no facts rather than the nearest thing, and so does a pair with no fact joining them: real absence, not something adjacent. Use it when you know whose fact you want, because resemblance cannot tell one entity from a similar one.
Each fact carries rank (1 first), score and matched. If you must drop facts to fit a budget, drop by rank: it is the fused result of every channel, and re-sorting by score throws that away. score is cosine similarity to your query, comparable across queries, diagnostic not a correctness test: a keyword match can be right at a low score. matched names the channels that found it; full text search is not the weaker one.
pending_ingest appears when memory files exist that the graph hasn't heard
about. Read each listed file, call write_episode with what it states, then
echo-memory pending --done <path>. The queue exists because extraction
needs a model and this server never calls one.
| Name | Required | Description | Default |
|---|---|---|---|
| about | No | ||
| as_of | No | ||
| query | No | ||
| scope | Yes | ||
| top_k | No | ||
| digest | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and largely meets it: it discloses immutability ('Nothing here is ever rewritten'), explains that digest=True ignores query, and surfaces a side-effect path via pending_ingest that requires reading files, calling write_episode, and running 'echo-memory pending --done'. That is behavioral context well beyond what any structured field provides.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but front-loaded and organized as discrete paragraph blocks, one per behavioral facet, with zero filler sentences. It is slightly over-sized for the guidance it delivers, but each paragraph earns its place given the absent schema descriptions and output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-parameter tool with no annotations, no output schema, and 0% schema coverage, the description supplies return-shape hints (rank, score, matched, pending_ingest) and explains how to consume them. It stops short of documenting top_k or the exact result envelope, which is a modest remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does for most parameters: scope values, digest semantics, as_of units and purpose, and about with exact arity/case-matching and no-partial-match rules. The only gap is top_k, which is never named — 'if you must drop facts to fit a budget' only implies it indirectly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Recall prior facts... from this agent's own memory') and immediately disambiguates the two scope modes ('solo' vs 'shared'). It is clearly distinguishable from write-oriented siblings like write_episode and mark_ingested without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use guidance ('Call this at session start, and any other time recalling prior context would save the user from re-explaining something'), notes digest is opt-in and 'never automatic', and routes to about/as_of with the conditions that select each. It even names write_episode as the follow-up for pending_ingest, so the agent knows the alternative rather than guessing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
record_recall_saveAInspect
Record that a fact you recalled from memory saved the user from re-explaining something to you.
Call this IN THE SAME TURN, the moment it happens. The trigger is concrete: you called query_memory (or read a memory-derived fact), it answered something the user would otherwise have had to tell you again, and the fact was originally written by a DIFFERENT tool or a past session.
That is why this takes only a fact_id. Pass the one carried by the
query_memory result that helped; the server reads that edge's own agent_id
for the writer, and uses its own configured agent id for the reader. You
assert neither. Both used to be caller-supplied, and each in turn let the
model being graded type its own evidence.
The server also checks its read log for the fact you cite, and refuses an id no read in this scope has ever returned. Pass the id you were given, not one you remember.
If the fact's author and you are the same tool, the save is recorded but does not count - recalling your own note from ten minutes ago is not the thing being measured.
note should be one sentence naming what it saved re-explaining, written so it still makes sense read cold in six months. Recording the identical note twice is a no-op, so a retry after an error is safe.
Do NOT call this speculatively, for a fact you wrote this session, or because a recall was merely interesting. It is evidence for a gate that decides real build work; an inflated count is worse than an empty one.
| Name | Required | Description | Default |
|---|---|---|---|
| note | Yes | ||
| scope | Yes | ||
| fact_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations this carries the full burden, and it does: server derives writer agent_id from the edge and reader from its own config, the read log is validated and unread ids are refused, self-authored saves are recorded but do not count, and duplicate notes are a safe no-op. This is unusually rich behavioral disclosure for a mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The operational content is front-loaded (trigger, then id semantics, then caveats), but a few sentences drift into rationale about graded evidence and prior caller-supplied ids that do not help an agent invoke the tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-param, no-output-schema mutation, the description supplies trigger conditions, idempotency, auth derivation, and validation behavior. The only real gap is the undefined `scope` parameter, which an agent must guess at.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It explains fact_id thoroughly (the id carried by the query_memory result, not one recalled) and note (one sentence, must read cold in six months), but `scope` is never defined beyond an oblique mention of 'no read in this scope' — one of three required params is left semantically undefined.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a precise verb and resource: recording that a recalled fact spared the user from re-explaining something. It is clearly differentiated from the read-side sibling query_memory, which it names as the source of the fact_id.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger (same turn, the moment it happens, after a query_memory result that answered something), explicit exclusions (not speculatively, not for facts you wrote this session, not merely because recall was interesting), and names the alternative query_memory.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
trace_causeAInspect
Why something happened, not what resembles it. Follows the causal links
recorded on facts about subject and returns them as chains, ordered from
the subject outwards.
Use this when the question is "why", "what did this break", "what was this a consequence of", or "what happens if we undo it". query_memory ranks facts by similarity and returns a flat list, which cannot answer those - the chain is the answer.
direction: "upstream" for what led here, "downstream" for what followed, "both" for both. max_hops: 1 to 6, default 3.
causes/effects are chains, each a list of facts. contradictions are facts explicitly recorded as contradicting one of the matched entities.
Only links a caller recorded are followed. Nothing here infers a cause, so an empty answer means nobody asserted one - not that none exists. Pass causal_hint on a fact in write_episode to make it traceable.
| Name | Required | Description | Default |
|---|---|---|---|
| scope | Yes | ||
| subject | Yes | ||
| max_hops | No | ||
| direction | No | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that only caller-recorded links are followed, that nothing infers a cause, and that an empty result means 'nobody asserted one - not that none exists'. It also describes the return shape (causes/effects as chains of facts, contradictions as facts). It does not address read-only status, sizing, or pagination, so it falls short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the discriminating idea, then usage triggers, then parameter guidance, then return shape and the key caveat. Every sentence carries information; nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must explain returns - and it does, defining causes/effects as chains of facts and contradictions as explicitly recorded conflicts. Combined with the empty-result caveat and the pointer to write_episode/causal_hint for making facts traceable, an agent has what it needs to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it partially does: it enumerates direction values ('upstream', 'downstream', 'both') and constrains max_hops ('1 to 6, default 3'). However, it never defines what `scope` means or what form `subject` takes, leaving two of four parameters undocumented. Useful but incomplete against a 0% coverage schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('follows the causal links recorded on facts about `subject`') and returns them as chains. It explicitly contrasts itself with the sibling query_memory, which 'ranks facts by similarity and returns a flat list, which cannot answer those'. An agent can distinguish the two without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use triggers are given verbatim ('why', 'what did this break', 'what was this a consequence of', 'what happens if we undo it'), and the alternative (query_memory) is named with the reason it fails for these questions. This is about as complete as usage routing gets.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
write_episodeAInspect
Record something worth remembering later: a decision, a correction, a stated preference, or context that would otherwise be re-explained to another tool or a later session. Call it the moment you notice one, not batched and not at the end - a missed memory costs more than an extra call. You extract the entities and facts; this server never infers.
entities: name non-empty, unique within this call type any short string ("tool", "person", "decision") - not an enum
facts: source/target must each exactly match a name in entities relation_type any short string ("uses", "decided") - not an enum fact the sentence to remember, plain text confidence exactly one of "extracted" (stated), "inferred" (deduced), "ambiguous" (uncertain). Anything else, or a number, is rejected. causal_hint optional, only when the session said so: caused_by, led_to, enabled_by, blocked_by, contradicts. What trace_cause walks. Omit when merely associative.
entity_resolutions (optional): only after a call returned ambiguous_entities, to say which candidate a mention meant: {"mention": {"resolved_to": "<node_id>" | "new"}}. Omit otherwise.
assume_new (optional): True when you know every entity here is new - a symbol just read, a title just coined. It answers "new" up front, so the call cannot come back asking. Explicit resolutions win.
related_entities in the reply: names this scope already uses for what you just wrote. Reuse them rather than coin a near-synonym. Advisory; no reply needed.
Example: write_episode(scope="solo", session_id="s1", entities=[{"name": "Postgres", "type": "tool"}, {"name": "Decision", "type": "decision"}], facts=[{"source": "Decision", "target": "Postgres", "relation_type": "uses", "confidence": "extracted", "fact": "decided to use Postgres for storage"}])
| Name | Required | Description | Default |
|---|---|---|---|
| facts | Yes | ||
| scope | Yes | ||
| entities | Yes | ||
| assume_new | No | ||
| session_id | Yes | ||
| entity_resolutions | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does so richly: it discloses that the server never infers, that confidence must be one of three exact strings and numbers are rejected, what causal_hint values mean, and that related_entities is advisory with no reply needed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the purpose and timing guidance, then organizes field semantics into clear sections with an inline example. The length is justified by the lack of schema descriptions, and every sentence adds distinct operational detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotations, no output schema, and 0% schema coverage, the description is highly complete: it covers nested objects, optional parameters, and even reply behavior. The main omission is any semantic explanation of the required scope and session_id fields beyond their appearance in the example.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it thoroughly documents entities, facts, entity_resolutions, and assume_new including nested field rules. However, the required scope and session_id parameters are only shown in an example and their valid values or meaning are never explained, leaving a notable gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Starts with a specific verb and resource: 'Record something worth remembering later,' then names the kinds of things to record. It also distinguishes itself from retrieval and causal-trace siblings by describing episode writing and referencing what trace_cause walks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to call ('the moment you notice one, not batched and not at the end') and when not to use optional parameters ('Omit otherwise'). It also gives precise conditions for entity_resolutions and assume_new, leaving little to inference.
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.
6 tool updates
v0.1.1- Added
mark_ingested - Added
pending_documents - Changed
query_memory2 fields changed- added
Input schema / properties / aboutAdded value: +{ + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "About" +} - added
Input schema / properties / as_ofAdded value: +{ + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "As Of" +}
- Changed
record_recall_save1 field changed- removed
Input schema / properties / recalled_byRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Recalled By" -}
- Added
trace_cause - Changed
write_episode1 field changed- added
Input schema / properties / assume_newAdded value: +{ + "default": false, + "title": "Assume New", + "type": "boolean" +}
4 tool updates
v0.1.0- First observed
get_audit_log - First observed
query_memory - First observed
record_recall_save - First observed
write_episode
TDQS
Scored across 7 tools
Each tool has a distinct purpose: write_episode creates memories, query_memory retrieves by similarity, trace_cause follows causal links, pending_documents/mark_ingested manage an ingestion queue, get_audit_log provides an audit trail, and record_recall_save captures evidence of memory utility. Overlap is minimal and descriptions clarify boundaries.
Most names follow a verb_noun snake_case pattern (write_episode, get_audit_log, trace_cause, query_memory, mark_ingested), but pending_documents is a noun phrase and record_recall_save is a compound verb phrase, creating minor inconsistency.
7 tools is well-scoped for an agent memory server, covering write, query, trace, audit, ingestion, and evidence recording without excessive granularity or missing core operations.
Core lifecycle for append-only agent memory is covered: write, query, causal trace, audit, pending document ingestion, and recall evidence. However, no explicit tool exists to invalidate or supersede facts despite the audit log referencing those events; agents must work around this by writing contradictory facts.
Maintenance
Related MCP Connectors
Long-term memory for AI agents: durable records, observable retrieval, governed context assembly.
Graph-native persistent memory for AI agents — 33 MCP tools, zero-LLM writes.
Long-term memory for AI agents: semantic facts, episodic events, and procedural workflows
- memoryOAuthcom.humaux
Persistent long-term memory for AI agents: semantic search, knowledge graph, and task canvas.
Related MCP Servers
- AlicenseBqualityBmaintenanceCausal graph memory engine for AI agents. Scores memories using relevance × connectivity × reactivation, connects them in a causal graph, and actively forgets irrelevant ones. 11 MCP tools including store, recall, search, traverse, and explain.1331AGPL 3.0
- AlicenseNot gradedqualityAmaintenanceA neuro-inspired long-term memory architecture for AI agents.135 PyPI3MIT
- AlicenseNot gradedqualityCmaintenancePersistent memory infrastructure for AI agents, enabling cross-session recall and autonomous memory evolution via an MCP server.1MIT
- AlicenseAqualityBmaintenanceAn MCP server providing long-term memory for AI agents with forgetting curves, consolidation, and graph-based retrieval.1014 npmMIT