echo-mem 0.3.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. echo_mem-0.4.1/PKG-INFO +315 -0
  2. echo_mem-0.4.1/README.md +293 -0
  3. {echo_mem-0.3.0 → echo_mem-0.4.1}/pyproject.toml +1 -1
  4. echo_mem-0.4.1/src/echo_mem.egg-info/PKG-INFO +315 -0
  5. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_mem.egg-info/SOURCES.txt +9 -0
  6. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/audit/get_audit_log.py +16 -2
  7. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/benchmark.py +67 -6
  8. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/health.py +185 -0
  9. echo_mem-0.4.1/src/echo_memory/cli/judge.py +250 -0
  10. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/main.py +134 -4
  11. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/queue.py +2 -1
  12. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/quickstart.py +125 -5
  13. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/recall.py +93 -6
  14. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/reindex.py +26 -8
  15. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/stop_gate.py +21 -5
  16. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/trial.py +44 -3
  17. echo_mem-0.4.1/src/echo_memory/eval/independent.py +683 -0
  18. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/eval/retrieval.py +251 -15
  19. echo_mem-0.4.1/src/echo_memory/eval/sweep.py +227 -0
  20. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/capture.py +11 -3
  21. echo_mem-0.4.1/src/echo_memory/ingestion/embeddings.py +173 -0
  22. echo_mem-0.4.1/src/echo_memory/ingestion/neighbourhood.py +194 -0
  23. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/resolution.py +87 -26
  24. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/write_episode.py +80 -13
  25. echo_mem-0.4.1/src/echo_memory/migrations/versions/0020_who_closed_a_queued_document.py +44 -0
  26. echo_mem-0.4.1/src/echo_memory/migrations/versions/0021_delivery_evidence.py +56 -0
  27. echo_mem-0.4.1/src/echo_memory/migrations/versions/0022_independent_evaluation.py +100 -0
  28. echo_mem-0.4.1/src/echo_memory/migrations/versions/0023_second_judge.py +61 -0
  29. echo_mem-0.4.1/src/echo_memory/migrations/versions/0024_node_by_id.py +46 -0
  30. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/retrieval/query_memory.py +193 -17
  31. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/server.py +52 -11
  32. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/trial/check.py +54 -3
  33. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/trial/observations.py +19 -3
  34. echo_mem-0.4.1/src/echo_memory/trial/reads.py +240 -0
  35. echo_mem-0.3.0/PKG-INFO +0 -253
  36. echo_mem-0.3.0/README.md +0 -231
  37. echo_mem-0.3.0/src/echo_mem.egg-info/PKG-INFO +0 -253
  38. echo_mem-0.3.0/src/echo_memory/ingestion/embeddings.py +0 -65
  39. echo_mem-0.3.0/src/echo_memory/trial/reads.py +0 -121
  40. {echo_mem-0.3.0 → echo_mem-0.4.1}/LICENSE +0 -0
  41. {echo_mem-0.3.0 → echo_mem-0.4.1}/setup.cfg +0 -0
  42. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_mem.egg-info/dependency_links.txt +0 -0
  43. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_mem.egg-info/entry_points.txt +0 -0
  44. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_mem.egg-info/requires.txt +0 -0
  45. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_mem.egg-info/top_level.txt +0 -0
  46. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/__init__.py +0 -0
  47. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/audit/__init__.py +0 -0
  48. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/__init__.py +0 -0
  49. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/adopt.py +0 -0
  50. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/analyse.py +0 -0
  51. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/calibrate.py +0 -0
  52. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/connect.py +0 -0
  53. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/dashboard.py +0 -0
  54. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/dashboard_html.py +0 -0
  55. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/export.py +0 -0
  56. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/graph.py +0 -0
  57. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/hooks.py +0 -0
  58. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/initdb.py +0 -0
  59. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/install.py +0 -0
  60. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/merge.py +0 -0
  61. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/reattribute.py +0 -0
  62. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/reattribute_cmd.py +0 -0
  63. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/reconcile.py +0 -0
  64. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/session_start.py +0 -0
  65. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/skill.py +0 -0
  66. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/status.py +0 -0
  67. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/unmerge.py +0 -0
  68. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/cli/why.py +0 -0
  69. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/client.py +0 -0
  70. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/eval/__init__.py +0 -0
  71. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/graph/__init__.py +0 -0
  72. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/graph/communities.py +0 -0
  73. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/__init__.py +0 -0
  74. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/config.py +0 -0
  75. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/db.py +0 -0
  76. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/logging.py +0 -0
  77. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/pool.py +0 -0
  78. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/infra/project.py +0 -0
  79. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/__init__.py +0 -0
  80. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/activity.py +0 -0
  81. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/ingestion/bootstrap.py +0 -0
  82. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/env.py +0 -0
  83. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0001_initial_schema.py +0 -0
  84. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0002_trial_observations.py +0 -0
  85. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0003_project_and_agent_provenance.py +0 -0
  86. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0004_bootstrap_state.py +0 -0
  87. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0005_recall_save_guards.py +0 -0
  88. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0006_project_analysis.py +0 -0
  89. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0007_backfill_unknown_agent.py +0 -0
  90. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0008_read_events.py +0 -0
  91. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0009_read_event_attribution.py +0 -0
  92. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0010_stop_gate_fired.py +0 -0
  93. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0011_backfill_absent_agent.py +0 -0
  94. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0012_read_event_agent.py +0 -0
  95. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0013_active_fact_indexes.py +0 -0
  96. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0014_session_activity.py +0 -0
  97. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0015_retracted_observations.py +0 -0
  98. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0016_one_node_per_name.py +0 -0
  99. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0017_writer_version.py +0 -0
  100. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0018_trial_runs_are_history.py +0 -0
  101. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/migrations/versions/0019_versions_expected_since.py +0 -0
  102. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/retrieval/__init__.py +0 -0
  103. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/retrieval/fusion.py +0 -0
  104. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/skill/SKILL.md +0 -0
  105. {echo_mem-0.3.0 → echo_mem-0.4.1}/src/echo_memory/trial/__init__.py +0 -0
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: echo-mem
3
+ Version: 0.4.1
4
+ Summary: A long-horizon memory architecture for AI agents
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: psycopg[binary]<4,>=3.2
10
+ Requires-Dist: psycopg-pool<4,>=3.2
11
+ Requires-Dist: pgvector<1,>=0.3
12
+ Requires-Dist: alembic<2,>=1.13
13
+ Requires-Dist: sqlalchemy<3,>=2
14
+ Requires-Dist: mcp<3,>=2.0
15
+ Requires-Dist: sentence-transformers<4,>=3
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest<9,>=8; extra == "dev"
18
+ Requires-Dist: ruff<1,>=0.6; extra == "dev"
19
+ Requires-Dist: build<2,>=1; extra == "dev"
20
+ Requires-Dist: twine<7,>=5; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # Echo Memory
24
+
25
+ [![CI](https://github.com/echo-mem/echo-mem/actions/workflows/ci.yml/badge.svg)](https://github.com/echo-mem/echo-mem/actions/workflows/ci.yml)
26
+ [![PyPI](https://img.shields.io/pypi/v/echo-mem)](https://pypi.org/project/echo-mem/)
27
+ [![Python](https://img.shields.io/pypi/pyversions/echo-mem)](https://pypi.org/project/echo-mem/)
28
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
29
+
30
+ **Shared memory for AI agents, as a graph in your own database.** What Claude Code learns,
31
+ Cursor and Codex can recall. Every fact records who wrote it and when, and the server
32
+ never calls a model to store one.
33
+
34
+ > Your agents start every session from zero. The usual fix is a notes file you paste into
35
+ > context, which grows until it is mostly irrelevant to whatever you are asking. Echo
36
+ > Memory is the other shape: facts connected to each other, and a query that returns the
37
+ > few that matter. On the author's own store that is **96.7% less context** for the same
38
+ > answer, with the answer still present 87.2% of the time across 1,190 questions.
39
+
40
+ ```
41
+ write_episode query_memory
42
+ billing ──uses──▸ Razorpay "how do we take payments"
43
+ │ written by claude-code ▸ billing uses Razorpay, not Stripe
44
+ │ supersedes ──▸ Stripe written by claude-code, 3 days ago
45
+ └ no model invoked ▸ 1,372 tokens, not 41,838
46
+ ```
47
+
48
+ ## Install
49
+
50
+ **Requires:** Python 3.11+ and Docker (for the database).
51
+
52
+ ```bash
53
+ pipx install echo-mem
54
+ echo-memory quickstart
55
+ ```
56
+
57
+ `quickstart` starts the database, applies the schema, and prints the `claude mcp add`
58
+ line that registers it, filled in with the port it actually used. The Postgres image is
59
+ published, so nothing compiles.
60
+
61
+ Or use the hosted service and run no database at all:
62
+
63
+ ```bash
64
+ pipx install echo-mem
65
+ echo-memory connect <key> # a key from https://app.echo-mem.com
66
+ ```
67
+
68
+ Then once per machine, so an agent knows *when* to record and recall rather than only
69
+ that the tools exist:
70
+
71
+ ```bash
72
+ echo-memory install --global
73
+ ```
74
+
75
+ > **Restart your client afterwards.** An MCP server is a long lived process that holds
76
+ > the code and config it started with, and an editable install does not change that.
77
+
78
+ > **The PyPI name is `echo-mem`, not `echo-memory`.** That name belongs to an unrelated
79
+ > hosted product. The import package and the CLI are both `echo_memory` / `echo-memory`;
80
+ > only the distribution name differs.
81
+
82
+ ## Usage
83
+
84
+ ```bash
85
+ echo-memory status # what each scope holds, and which agents have written
86
+ echo-memory health # a score, what is weak, and what to do about it
87
+ echo-memory dashboard --serve # the graph, in a browser, localhost only
88
+
89
+ echo-memory why <fact_id> # the full audit trail for one fact
90
+ echo-memory recall "<question>" # query the store from a terminal
91
+ echo-memory export # everything, as JSON
92
+
93
+ echo-memory install --for cursor # wire one client, project scoped
94
+ echo-memory adopt # wire every MCP client on the machine, each with its own id
95
+
96
+ echo-memory eval # retrieval quality against your own store
97
+ echo-memory eval --context # what a recall costs against injecting everything
98
+ echo-memory eval --context --sweep # the same, as a curve across corpus size
99
+ echo-memory calibrate # is entity resolution trustworthy on your data
100
+ echo-memory benchmark # write, query and digest latency
101
+ ```
102
+
103
+ ### The six MCP tools
104
+
105
+ | Tool | What it does |
106
+ |---|---|
107
+ | `write_episode` | Store entities and the facts connecting them. No model call. |
108
+ | `query_memory` | Hybrid vector and full text retrieval, fused by reciprocal rank. |
109
+ | `record_recall_save` | Mark that a recalled fact saved re explaining something. Refuses a fact no read returned. |
110
+ | `get_audit_log` | Every change to memory, with a plain language reason. |
111
+ | `pending_documents` | Memory files this project wrote that the graph has not heard about. |
112
+ | `mark_ingested` | Close one of those out. |
113
+
114
+ ## What you get
115
+
116
+ **A graph, not a list.** Entities are nodes and a fact is an edge between two of them.
117
+ Two sessions that never knew about each other resolve onto the same entity by name, so
118
+ the second inherits what the first learned.
119
+
120
+ **Bounded retrieval.** Old, rarely read memory consolidates into higher level summaries
121
+ over time. Nothing is discarded; what changes is how much a query has to walk.
122
+
123
+ **Provenance on every fact.** Who wrote it, which tool, which project, when, and which
124
+ reads returned it. A superseded fact is never deleted. It stops being drawn and stays
125
+ reachable with its history.
126
+
127
+ **Causal typing.** Edges can be tagged `caused_by`, `led_to`, `blocked_by`,
128
+ `contradicts`, set by the agent's own read of the conversation rather than inferred
129
+ statistically.
130
+
131
+ **No inference on the write path.** Extraction happens in the calling agent, so storing
132
+ a memory invokes no model on the server. The cost moved rather than vanished: the agent
133
+ has to arrive with entities and facts already extracted, which is what the
134
+ [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
135
+ Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
136
+ "every episode triggers multiple LLM calls" and that "write cost scales with volume".
137
+
138
+ The obvious reply is that cheap writes are cheap because they do less, and that reply is
139
+ correct on the mechanism. [`docs/WRITE-COST.md`](docs/WRITE-COST.md) answers it properly,
140
+ including the two measured costs of the choice: the Stop gate fired seven times and
141
+ produced one fact, and a write touching an ambiguous entity is deferred while the call
142
+ returns as though it succeeded.
143
+
144
+ **Any MCP client.** A coding assistant, a chatbot, an ops agent, or something built in
145
+ house. Coding agents are where this is proven, not what it is limited to.
146
+
147
+ ## Numbers, and how they were taken
148
+
149
+ Every figure comes from this repository or a live store, on a date, with the command that
150
+ reproduces it on yours. The corpus is small and the noise floor is stated, because a
151
+ difference nobody sized is not a result.
152
+
153
+ | Measure | Value | Reproduce |
154
+ |---|---|---|
155
+ | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
156
+ | 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 | `echo-memory eval --context --sweep` |
157
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460 | `scripts/locomo-bench.py` |
158
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.940**, turn@10 0.727 | `scripts/longmemeval-bench.py --per-type 15` |
159
+ | Server side model calls per write | **0** | `echo-memory benchmark` |
160
+ | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
161
+ | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
162
+
163
+ That last row is the one that went the wrong way, and it is here on purpose. The interval
164
+ includes chance, so the unattended merge is switched off: at the automatic bar precision
165
+ was 50% over two reviewed pairs, and the audit log showed that path had fired once in the
166
+ system's entire history. A near match is now offered for confirmation instead.
167
+
168
+ The LoCoMo row is retrieval, not QA accuracy. Published LoCoMo results have a model write
169
+ an answer and a second model judge it; this asks only whether the turn holding the answer
170
+ came back, which is a ceiling on QA accuracy rather than a substitute for it, and is not
171
+ comparable to anybody's published QA figure. It also feeds raw dialogue turns, which skips
172
+ the extraction step this design pushes to the calling agent, so it is a floor as well as a
173
+ ceiling. The worst row, multi hop at recall@1 0.099, is in
174
+ [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) with the rest.
175
+
176
+ The context saving is measured against a specific baseline, stated so it cannot be read
177
+ as more than it is. Not "no memory at all", which is however long a human spends re
178
+ explaining and is unmeasurable. It is the thing people do instead: keep the project's
179
+ notes in one file and paste the whole file. On that store the file is 325 facts, about
180
+ 41,838 tokens; a recall returned 1,372 on average. The hit rate belongs beside it, because
181
+ a recall that returned nothing would score 100%.
182
+
183
+ ## The graph
184
+
185
+ Memory is a graph, not a list of notes. Entities are nodes; a fact is an **edge** between
186
+ two of them. That is the whole data model, and everything else follows from it.
187
+
188
+ ![The memory graph](docs/images/graph-overview.png)
189
+
190
+ Three projects here. `checkout-api`, `mobile-app` and `data-pipeline` were recorded in
191
+ separate sessions and never told about each other, yet the picture already separates them,
192
+ because separation is a property of the edges rather than a label anyone applied.
193
+
194
+ **Clusters come from structure.** Densely connected facts are grouped by label propagation
195
+ over the edges, and each cluster is named after its most connected node. That is why
196
+ `data-pipeline` sits apart: nothing it knows touches payments. It is also why
197
+ `checkout-api` and `mobile-app` share a cluster despite being different codebases. They
198
+ genuinely share an idea, and the graph found it rather than being told.
199
+
200
+ **Components are the stronger claim.** Two nodes in different components have no path
201
+ between them at all, which is the strongest statement this graph can make that two
202
+ memories are unrelated.
203
+
204
+ ### Click a node: everything it takes part in
205
+
206
+ ![A node selected](docs/images/graph-node-selected.png)
207
+
208
+ `idempotency keys` is the largest node here and nobody made it large: seventeen facts from
209
+ several services resolved onto one entity by name. The panel lists every one, with which
210
+ agent wrote it and when.
211
+
212
+ ### Click a link: why memory believes it
213
+
214
+ ![A fact selected](docs/images/graph-fact-selected.png)
215
+
216
+ Not a tooltip. Who wrote the fact, in which project, when, and how each of its entities
217
+ resolved. `echo-memory why <fact_id>` prints the same trail in a terminal.
218
+
219
+ ### Seeing your own
220
+
221
+ ```bash
222
+ echo-memory dashboard --serve --open
223
+ ```
224
+
225
+ The images above come from a synthetic dataset (`scripts/demo-seed.py`) rather than a real
226
+ store, for the obvious reason: a real memory graph is full of hostnames, account numbers
227
+ and client names.
228
+
229
+ ## Wiring more than one tool
230
+
231
+ **Give each client its own `ECHO_MEMORY_AGENT_ID`.** Cursor should say `cursor`, Claude
232
+ Desktop `claude-desktop`. Memory is shared either way, but a fact records which tool
233
+ learned it, and two tools claiming the same id makes cross tool recall impossible to see
234
+ afterwards.
235
+
236
+ ```bash
237
+ echo-memory adopt # every MCP client on the machine, each with its own id
238
+ echo-memory install [path] # one project: MCP config plus a skill, committed with the code
239
+ ```
240
+
241
+ `adopt` shows the diff before writing anything. For an agent that does not speak MCP, see
242
+ [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
243
+
244
+ ## Is the graph in good shape?
245
+
246
+ ```bash
247
+ echo-memory health
248
+ ```
249
+
250
+ A score, what is strong, what needs attention, and what to do about each, including what
251
+ recall has cost: how often memory was read, how often a read returned anything, roughly
252
+ how many tokens were injected, and how many saves those reads produced. Writes were
253
+ counted from the start; reads were not counted at all, so nothing could answer whether
254
+ recall earns what it costs. It exists to be run when you have no question, because a store
255
+ can look healthy by every other number while most of its facts came from a bulk import,
256
+ the last real write was a week ago, and only one of several wired agents has ever written
257
+ anything. `--json` for machine readable output.
258
+
259
+ Nothing in it is gated. The paid plan sells hosting; diagnostics about your own data are
260
+ not a thing to withhold from the person whose data it is.
261
+
262
+ ## Architecture
263
+
264
+ **Storage** PostgreSQL with `pgvector` and Apache AGE, from a single local agent up to an
265
+ organisation wide shared graph, with no forced migration later. The novel work is the
266
+ memory structure and the read/write algorithm on top of it, not a new database engine.
267
+
268
+ **Retrieval** Hybrid vector and full text search fused by reciprocal rank in v1a.
269
+ Personalised PageRank via `networkx` lands in v1b for multi hop associative retrieval.
270
+
271
+ **Interface** [Model Context Protocol](https://modelcontextprotocol.io), so any compliant
272
+ agent reads and writes the same graph.
273
+
274
+ ## Status
275
+
276
+ Early and staged, on purpose. See [`docs/designs/`](docs/designs/) for the architecture
277
+ and the v1a to v1b plan.
278
+
279
+ | | |
280
+ |---|---|
281
+ | **v1a, built** | Basic recall. Six MCP tools, thirty CLI commands, on PyPI and in the MCP registry. |
282
+ | **v1b, gated** | Causal typing and multi hop retrieval. 187 questions no single fact answers score MRR 0.212 today; the number to beat exists before the feature does. |
283
+ | **v1.1, planned** | Organisation wide tenancy: per agent, per team, or org wide graphs. |
284
+
285
+ The validated wedge driving v1a is memory shared across coding agents, which is the
286
+ author's own daily pain and the case with the most evidence behind it. Everything else is
287
+ the target this architecture is built toward.
288
+
289
+ ## Hosted
290
+
291
+ Running it yourself is free forever under Apache 2.0, with no account and no feature held
292
+ back. [app.echo-mem.com](https://app.echo-mem.com) runs the database for you at $99 a
293
+ month if you would rather not.
294
+
295
+ <details>
296
+ <summary>Contributing</summary>
297
+
298
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md). Issues and pull requests welcome; please read the
299
+ design docs first so proposals fit the staged build plan. A first pull request is asked to
300
+ sign the [Contributor License Agreement](CLA.md), once, in the PR thread.
301
+
302
+ The most useful contribution is a measurement that disagrees with one of the numbers
303
+ above. Run `echo-memory eval`, `calibrate` or `benchmark` on your own store and open an
304
+ issue with the output.
305
+
306
+ </details>
307
+
308
+ ## License
309
+
310
+ Apache License 2.0. See [`LICENSE`](LICENSE).
311
+
312
+ <!-- The MCP registry proves you own a PyPI package by finding this line in the
313
+ package's own description. It has to survive into the built distribution,
314
+ which is why it lives in the README rather than in a workflow. -->
315
+ mcp-name: io.github.ayushcodes10/echo-mem
@@ -0,0 +1,293 @@
1
+ # Echo Memory
2
+
3
+ [![CI](https://github.com/echo-mem/echo-mem/actions/workflows/ci.yml/badge.svg)](https://github.com/echo-mem/echo-mem/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/echo-mem)](https://pypi.org/project/echo-mem/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/echo-mem)](https://pypi.org/project/echo-mem/)
6
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
7
+
8
+ **Shared memory for AI agents, as a graph in your own database.** What Claude Code learns,
9
+ Cursor and Codex can recall. Every fact records who wrote it and when, and the server
10
+ never calls a model to store one.
11
+
12
+ > Your agents start every session from zero. The usual fix is a notes file you paste into
13
+ > context, which grows until it is mostly irrelevant to whatever you are asking. Echo
14
+ > Memory is the other shape: facts connected to each other, and a query that returns the
15
+ > few that matter. On the author's own store that is **96.7% less context** for the same
16
+ > answer, with the answer still present 87.2% of the time across 1,190 questions.
17
+
18
+ ```
19
+ write_episode query_memory
20
+ billing ──uses──▸ Razorpay "how do we take payments"
21
+ │ written by claude-code ▸ billing uses Razorpay, not Stripe
22
+ │ supersedes ──▸ Stripe written by claude-code, 3 days ago
23
+ └ no model invoked ▸ 1,372 tokens, not 41,838
24
+ ```
25
+
26
+ ## Install
27
+
28
+ **Requires:** Python 3.11+ and Docker (for the database).
29
+
30
+ ```bash
31
+ pipx install echo-mem
32
+ echo-memory quickstart
33
+ ```
34
+
35
+ `quickstart` starts the database, applies the schema, and prints the `claude mcp add`
36
+ line that registers it, filled in with the port it actually used. The Postgres image is
37
+ published, so nothing compiles.
38
+
39
+ Or use the hosted service and run no database at all:
40
+
41
+ ```bash
42
+ pipx install echo-mem
43
+ echo-memory connect <key> # a key from https://app.echo-mem.com
44
+ ```
45
+
46
+ Then once per machine, so an agent knows *when* to record and recall rather than only
47
+ that the tools exist:
48
+
49
+ ```bash
50
+ echo-memory install --global
51
+ ```
52
+
53
+ > **Restart your client afterwards.** An MCP server is a long lived process that holds
54
+ > the code and config it started with, and an editable install does not change that.
55
+
56
+ > **The PyPI name is `echo-mem`, not `echo-memory`.** That name belongs to an unrelated
57
+ > hosted product. The import package and the CLI are both `echo_memory` / `echo-memory`;
58
+ > only the distribution name differs.
59
+
60
+ ## Usage
61
+
62
+ ```bash
63
+ echo-memory status # what each scope holds, and which agents have written
64
+ echo-memory health # a score, what is weak, and what to do about it
65
+ echo-memory dashboard --serve # the graph, in a browser, localhost only
66
+
67
+ echo-memory why <fact_id> # the full audit trail for one fact
68
+ echo-memory recall "<question>" # query the store from a terminal
69
+ echo-memory export # everything, as JSON
70
+
71
+ echo-memory install --for cursor # wire one client, project scoped
72
+ echo-memory adopt # wire every MCP client on the machine, each with its own id
73
+
74
+ echo-memory eval # retrieval quality against your own store
75
+ echo-memory eval --context # what a recall costs against injecting everything
76
+ echo-memory eval --context --sweep # the same, as a curve across corpus size
77
+ echo-memory calibrate # is entity resolution trustworthy on your data
78
+ echo-memory benchmark # write, query and digest latency
79
+ ```
80
+
81
+ ### The six MCP tools
82
+
83
+ | Tool | What it does |
84
+ |---|---|
85
+ | `write_episode` | Store entities and the facts connecting them. No model call. |
86
+ | `query_memory` | Hybrid vector and full text retrieval, fused by reciprocal rank. |
87
+ | `record_recall_save` | Mark that a recalled fact saved re explaining something. Refuses a fact no read returned. |
88
+ | `get_audit_log` | Every change to memory, with a plain language reason. |
89
+ | `pending_documents` | Memory files this project wrote that the graph has not heard about. |
90
+ | `mark_ingested` | Close one of those out. |
91
+
92
+ ## What you get
93
+
94
+ **A graph, not a list.** Entities are nodes and a fact is an edge between two of them.
95
+ Two sessions that never knew about each other resolve onto the same entity by name, so
96
+ the second inherits what the first learned.
97
+
98
+ **Bounded retrieval.** Old, rarely read memory consolidates into higher level summaries
99
+ over time. Nothing is discarded; what changes is how much a query has to walk.
100
+
101
+ **Provenance on every fact.** Who wrote it, which tool, which project, when, and which
102
+ reads returned it. A superseded fact is never deleted. It stops being drawn and stays
103
+ reachable with its history.
104
+
105
+ **Causal typing.** Edges can be tagged `caused_by`, `led_to`, `blocked_by`,
106
+ `contradicts`, set by the agent's own read of the conversation rather than inferred
107
+ statistically.
108
+
109
+ **No inference on the write path.** Extraction happens in the calling agent, so storing
110
+ a memory invokes no model on the server. The cost moved rather than vanished: the agent
111
+ has to arrive with entities and facts already extracted, which is what the
112
+ [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
113
+ Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
114
+ "every episode triggers multiple LLM calls" and that "write cost scales with volume".
115
+
116
+ The obvious reply is that cheap writes are cheap because they do less, and that reply is
117
+ correct on the mechanism. [`docs/WRITE-COST.md`](docs/WRITE-COST.md) answers it properly,
118
+ including the two measured costs of the choice: the Stop gate fired seven times and
119
+ produced one fact, and a write touching an ambiguous entity is deferred while the call
120
+ returns as though it succeeded.
121
+
122
+ **Any MCP client.** A coding assistant, a chatbot, an ops agent, or something built in
123
+ house. Coding agents are where this is proven, not what it is limited to.
124
+
125
+ ## Numbers, and how they were taken
126
+
127
+ Every figure comes from this repository or a live store, on a date, with the command that
128
+ reproduces it on yours. The corpus is small and the noise floor is stated, because a
129
+ difference nobody sized is not a result.
130
+
131
+ | Measure | Value | Reproduce |
132
+ |---|---|---|
133
+ | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
134
+ | 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 | `echo-memory eval --context --sweep` |
135
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460 | `scripts/locomo-bench.py` |
136
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.940**, turn@10 0.727 | `scripts/longmemeval-bench.py --per-type 15` |
137
+ | Server side model calls per write | **0** | `echo-memory benchmark` |
138
+ | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
139
+ | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
140
+
141
+ That last row is the one that went the wrong way, and it is here on purpose. The interval
142
+ includes chance, so the unattended merge is switched off: at the automatic bar precision
143
+ was 50% over two reviewed pairs, and the audit log showed that path had fired once in the
144
+ system's entire history. A near match is now offered for confirmation instead.
145
+
146
+ The LoCoMo row is retrieval, not QA accuracy. Published LoCoMo results have a model write
147
+ an answer and a second model judge it; this asks only whether the turn holding the answer
148
+ came back, which is a ceiling on QA accuracy rather than a substitute for it, and is not
149
+ comparable to anybody's published QA figure. It also feeds raw dialogue turns, which skips
150
+ the extraction step this design pushes to the calling agent, so it is a floor as well as a
151
+ ceiling. The worst row, multi hop at recall@1 0.099, is in
152
+ [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) with the rest.
153
+
154
+ The context saving is measured against a specific baseline, stated so it cannot be read
155
+ as more than it is. Not "no memory at all", which is however long a human spends re
156
+ explaining and is unmeasurable. It is the thing people do instead: keep the project's
157
+ notes in one file and paste the whole file. On that store the file is 325 facts, about
158
+ 41,838 tokens; a recall returned 1,372 on average. The hit rate belongs beside it, because
159
+ a recall that returned nothing would score 100%.
160
+
161
+ ## The graph
162
+
163
+ Memory is a graph, not a list of notes. Entities are nodes; a fact is an **edge** between
164
+ two of them. That is the whole data model, and everything else follows from it.
165
+
166
+ ![The memory graph](docs/images/graph-overview.png)
167
+
168
+ Three projects here. `checkout-api`, `mobile-app` and `data-pipeline` were recorded in
169
+ separate sessions and never told about each other, yet the picture already separates them,
170
+ because separation is a property of the edges rather than a label anyone applied.
171
+
172
+ **Clusters come from structure.** Densely connected facts are grouped by label propagation
173
+ over the edges, and each cluster is named after its most connected node. That is why
174
+ `data-pipeline` sits apart: nothing it knows touches payments. It is also why
175
+ `checkout-api` and `mobile-app` share a cluster despite being different codebases. They
176
+ genuinely share an idea, and the graph found it rather than being told.
177
+
178
+ **Components are the stronger claim.** Two nodes in different components have no path
179
+ between them at all, which is the strongest statement this graph can make that two
180
+ memories are unrelated.
181
+
182
+ ### Click a node: everything it takes part in
183
+
184
+ ![A node selected](docs/images/graph-node-selected.png)
185
+
186
+ `idempotency keys` is the largest node here and nobody made it large: seventeen facts from
187
+ several services resolved onto one entity by name. The panel lists every one, with which
188
+ agent wrote it and when.
189
+
190
+ ### Click a link: why memory believes it
191
+
192
+ ![A fact selected](docs/images/graph-fact-selected.png)
193
+
194
+ Not a tooltip. Who wrote the fact, in which project, when, and how each of its entities
195
+ resolved. `echo-memory why <fact_id>` prints the same trail in a terminal.
196
+
197
+ ### Seeing your own
198
+
199
+ ```bash
200
+ echo-memory dashboard --serve --open
201
+ ```
202
+
203
+ The images above come from a synthetic dataset (`scripts/demo-seed.py`) rather than a real
204
+ store, for the obvious reason: a real memory graph is full of hostnames, account numbers
205
+ and client names.
206
+
207
+ ## Wiring more than one tool
208
+
209
+ **Give each client its own `ECHO_MEMORY_AGENT_ID`.** Cursor should say `cursor`, Claude
210
+ Desktop `claude-desktop`. Memory is shared either way, but a fact records which tool
211
+ learned it, and two tools claiming the same id makes cross tool recall impossible to see
212
+ afterwards.
213
+
214
+ ```bash
215
+ echo-memory adopt # every MCP client on the machine, each with its own id
216
+ echo-memory install [path] # one project: MCP config plus a skill, committed with the code
217
+ ```
218
+
219
+ `adopt` shows the diff before writing anything. For an agent that does not speak MCP, see
220
+ [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
221
+
222
+ ## Is the graph in good shape?
223
+
224
+ ```bash
225
+ echo-memory health
226
+ ```
227
+
228
+ A score, what is strong, what needs attention, and what to do about each, including what
229
+ recall has cost: how often memory was read, how often a read returned anything, roughly
230
+ how many tokens were injected, and how many saves those reads produced. Writes were
231
+ counted from the start; reads were not counted at all, so nothing could answer whether
232
+ recall earns what it costs. It exists to be run when you have no question, because a store
233
+ can look healthy by every other number while most of its facts came from a bulk import,
234
+ the last real write was a week ago, and only one of several wired agents has ever written
235
+ anything. `--json` for machine readable output.
236
+
237
+ Nothing in it is gated. The paid plan sells hosting; diagnostics about your own data are
238
+ not a thing to withhold from the person whose data it is.
239
+
240
+ ## Architecture
241
+
242
+ **Storage** PostgreSQL with `pgvector` and Apache AGE, from a single local agent up to an
243
+ organisation wide shared graph, with no forced migration later. The novel work is the
244
+ memory structure and the read/write algorithm on top of it, not a new database engine.
245
+
246
+ **Retrieval** Hybrid vector and full text search fused by reciprocal rank in v1a.
247
+ Personalised PageRank via `networkx` lands in v1b for multi hop associative retrieval.
248
+
249
+ **Interface** [Model Context Protocol](https://modelcontextprotocol.io), so any compliant
250
+ agent reads and writes the same graph.
251
+
252
+ ## Status
253
+
254
+ Early and staged, on purpose. See [`docs/designs/`](docs/designs/) for the architecture
255
+ and the v1a to v1b plan.
256
+
257
+ | | |
258
+ |---|---|
259
+ | **v1a, built** | Basic recall. Six MCP tools, thirty CLI commands, on PyPI and in the MCP registry. |
260
+ | **v1b, gated** | Causal typing and multi hop retrieval. 187 questions no single fact answers score MRR 0.212 today; the number to beat exists before the feature does. |
261
+ | **v1.1, planned** | Organisation wide tenancy: per agent, per team, or org wide graphs. |
262
+
263
+ The validated wedge driving v1a is memory shared across coding agents, which is the
264
+ author's own daily pain and the case with the most evidence behind it. Everything else is
265
+ the target this architecture is built toward.
266
+
267
+ ## Hosted
268
+
269
+ Running it yourself is free forever under Apache 2.0, with no account and no feature held
270
+ back. [app.echo-mem.com](https://app.echo-mem.com) runs the database for you at $99 a
271
+ month if you would rather not.
272
+
273
+ <details>
274
+ <summary>Contributing</summary>
275
+
276
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md). Issues and pull requests welcome; please read the
277
+ design docs first so proposals fit the staged build plan. A first pull request is asked to
278
+ sign the [Contributor License Agreement](CLA.md), once, in the PR thread.
279
+
280
+ The most useful contribution is a measurement that disagrees with one of the numbers
281
+ above. Run `echo-memory eval`, `calibrate` or `benchmark` on your own store and open an
282
+ issue with the output.
283
+
284
+ </details>
285
+
286
+ ## License
287
+
288
+ Apache License 2.0. See [`LICENSE`](LICENSE).
289
+
290
+ <!-- The MCP registry proves you own a PyPI package by finding this line in the
291
+ package's own description. It has to survive into the built distribution,
292
+ which is why it lives in the README rather than in a workflow. -->
293
+ mcp-name: io.github.ayushcodes10/echo-mem
@@ -6,7 +6,7 @@
6
6
  # (`-m echo_memory.server`) and every existing install reference it, and
7
7
  # renaming the module would break live configuration to fix a cosmetic mismatch.
8
8
  name = "echo-mem"
9
- version = "0.3.0"
9
+ version = "0.4.1"
10
10
  description = "A long-horizon memory architecture for AI agents"
11
11
  readme = "README.md"
12
12
  license = "Apache-2.0"