echo-mem 0.3.0__tar.gz → 0.5.0__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 (107) hide show
  1. echo_mem-0.5.0/LICENSE +96 -0
  2. echo_mem-0.5.0/PKG-INFO +351 -0
  3. echo_mem-0.5.0/README.md +328 -0
  4. {echo_mem-0.3.0 → echo_mem-0.5.0}/pyproject.toml +9 -2
  5. echo_mem-0.5.0/src/echo_mem.egg-info/PKG-INFO +351 -0
  6. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_mem.egg-info/SOURCES.txt +11 -0
  7. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/audit/get_audit_log.py +16 -2
  8. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/benchmark.py +67 -6
  9. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/health.py +185 -0
  10. echo_mem-0.5.0/src/echo_memory/cli/judge.py +250 -0
  11. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/main.py +134 -4
  12. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/queue.py +2 -1
  13. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/quickstart.py +125 -5
  14. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/recall.py +93 -6
  15. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/reindex.py +26 -8
  16. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/stop_gate.py +21 -5
  17. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/trial.py +44 -3
  18. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/client.py +26 -1
  19. echo_mem-0.5.0/src/echo_memory/eval/independent.py +683 -0
  20. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/eval/retrieval.py +251 -15
  21. echo_mem-0.5.0/src/echo_memory/eval/sweep.py +227 -0
  22. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/capture.py +11 -3
  23. echo_mem-0.5.0/src/echo_memory/ingestion/embeddings.py +173 -0
  24. echo_mem-0.5.0/src/echo_memory/ingestion/neighbourhood.py +194 -0
  25. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/resolution.py +107 -28
  26. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/write_episode.py +231 -16
  27. echo_mem-0.5.0/src/echo_memory/migrations/versions/0020_who_closed_a_queued_document.py +44 -0
  28. echo_mem-0.5.0/src/echo_memory/migrations/versions/0021_delivery_evidence.py +56 -0
  29. echo_mem-0.5.0/src/echo_memory/migrations/versions/0022_independent_evaluation.py +100 -0
  30. echo_mem-0.5.0/src/echo_memory/migrations/versions/0023_second_judge.py +61 -0
  31. echo_mem-0.5.0/src/echo_memory/migrations/versions/0024_node_by_id.py +46 -0
  32. echo_mem-0.5.0/src/echo_memory/retrieval/causality.py +337 -0
  33. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/retrieval/query_memory.py +234 -17
  34. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/server.py +126 -22
  35. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/skill/SKILL.md +32 -3
  36. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/trial/check.py +54 -3
  37. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/trial/observations.py +19 -3
  38. echo_mem-0.5.0/src/echo_memory/trial/reads.py +240 -0
  39. echo_mem-0.3.0/PKG-INFO +0 -253
  40. echo_mem-0.3.0/README.md +0 -231
  41. echo_mem-0.3.0/src/echo_mem.egg-info/PKG-INFO +0 -253
  42. echo_mem-0.3.0/src/echo_memory/ingestion/embeddings.py +0 -65
  43. echo_mem-0.3.0/src/echo_memory/trial/reads.py +0 -121
  44. /echo_mem-0.3.0/LICENSE → /echo_mem-0.5.0/LICENSE-APACHE-2.0 +0 -0
  45. {echo_mem-0.3.0 → echo_mem-0.5.0}/setup.cfg +0 -0
  46. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_mem.egg-info/dependency_links.txt +0 -0
  47. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_mem.egg-info/entry_points.txt +0 -0
  48. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_mem.egg-info/requires.txt +0 -0
  49. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_mem.egg-info/top_level.txt +0 -0
  50. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/__init__.py +0 -0
  51. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/audit/__init__.py +0 -0
  52. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/__init__.py +0 -0
  53. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/adopt.py +0 -0
  54. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/analyse.py +0 -0
  55. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/calibrate.py +0 -0
  56. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/connect.py +0 -0
  57. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/dashboard.py +0 -0
  58. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/dashboard_html.py +0 -0
  59. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/export.py +0 -0
  60. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/graph.py +0 -0
  61. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/hooks.py +0 -0
  62. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/initdb.py +0 -0
  63. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/install.py +0 -0
  64. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/merge.py +0 -0
  65. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/reattribute.py +0 -0
  66. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/reattribute_cmd.py +0 -0
  67. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/reconcile.py +0 -0
  68. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/session_start.py +0 -0
  69. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/skill.py +0 -0
  70. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/status.py +0 -0
  71. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/unmerge.py +0 -0
  72. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/cli/why.py +0 -0
  73. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/eval/__init__.py +0 -0
  74. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/graph/__init__.py +0 -0
  75. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/graph/communities.py +0 -0
  76. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/__init__.py +0 -0
  77. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/config.py +0 -0
  78. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/db.py +0 -0
  79. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/logging.py +0 -0
  80. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/pool.py +0 -0
  81. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/infra/project.py +0 -0
  82. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/__init__.py +0 -0
  83. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/activity.py +0 -0
  84. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/ingestion/bootstrap.py +0 -0
  85. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/env.py +0 -0
  86. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0001_initial_schema.py +0 -0
  87. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0002_trial_observations.py +0 -0
  88. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0003_project_and_agent_provenance.py +0 -0
  89. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0004_bootstrap_state.py +0 -0
  90. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0005_recall_save_guards.py +0 -0
  91. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0006_project_analysis.py +0 -0
  92. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0007_backfill_unknown_agent.py +0 -0
  93. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0008_read_events.py +0 -0
  94. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0009_read_event_attribution.py +0 -0
  95. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0010_stop_gate_fired.py +0 -0
  96. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0011_backfill_absent_agent.py +0 -0
  97. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0012_read_event_agent.py +0 -0
  98. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0013_active_fact_indexes.py +0 -0
  99. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0014_session_activity.py +0 -0
  100. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0015_retracted_observations.py +0 -0
  101. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0016_one_node_per_name.py +0 -0
  102. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0017_writer_version.py +0 -0
  103. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0018_trial_runs_are_history.py +0 -0
  104. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/migrations/versions/0019_versions_expected_since.py +0 -0
  105. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/retrieval/__init__.py +0 -0
  106. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/retrieval/fusion.py +0 -0
  107. {echo_mem-0.3.0 → echo_mem-0.5.0}/src/echo_memory/trial/__init__.py +0 -0
echo_mem-0.5.0/LICENSE ADDED
@@ -0,0 +1,96 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Ateleir Tech
6
+ Licensed Work: Echo Memory
7
+ The Licensed Work is (c) 2026 Ateleir Tech.
8
+ Additional Use Grant: You may use the Licensed Work in production, free of
9
+ charge, if the organisation using it has fewer than 50
10
+ employees AND less than US$5,000,000 in annual revenue,
11
+ counting any parent, subsidiary or affiliated entity.
12
+
13
+ You may always use the Licensed Work free of charge for
14
+ development, testing, evaluation, research, teaching and
15
+ any other non-production purpose, whatever your size.
16
+
17
+ "Production use" means using the Licensed Work to store
18
+ or serve memory for software that real users depend on.
19
+ Running it on a laptop to decide whether you want it is
20
+ not production use.
21
+
22
+ Change Date: Four years from the date each version is first published.
23
+
24
+ Change License: Apache License, Version 2.0
25
+
26
+ For information about alternative licensing arrangements for the Licensed
27
+ Work, please contact hello@echo-mem.com.
28
+
29
+ -----------------------------------------------------------------------------
30
+
31
+ Terms
32
+
33
+ The Licensor hereby grants you the right to copy, modify, create derivative
34
+ works, redistribute, and make non-production use of the Licensed Work. The
35
+ Licensor may make an Additional Use Grant, above, permitting limited
36
+ production use.
37
+
38
+ Effective on the Change Date, or the fourth anniversary of the first publicly
39
+ available distribution of a specific version of the Licensed Work under this
40
+ License, whichever comes first, the Licensor hereby grants you rights under
41
+ the terms of the Change License, and the rights granted in the paragraph
42
+ above terminate.
43
+
44
+ If your use of the Licensed Work does not comply with the requirements
45
+ currently in effect as described in this License, you must purchase a
46
+ commercial license from the Licensor, its affiliated entities, or authorized
47
+ resellers, or you must refrain from using the Licensed Work.
48
+
49
+ All copies of the original and modified Licensed Work, and derivative works
50
+ of the Licensed Work, are subject to this License. This License applies
51
+ separately for each version of the Licensed Work and the Change Date may vary
52
+ for each version of the Licensed Work released by Licensor.
53
+
54
+ You must conspicuously display this License on each original or modified copy
55
+ of the Licensed Work. If you receive the Licensed Work in original or
56
+ modified form from a third party, the terms and conditions set forth in this
57
+ License apply to your use of that work.
58
+
59
+ Any use of the Licensed Work in violation of this License will automatically
60
+ terminate your rights under this License for the current and all other
61
+ versions of the Licensed Work.
62
+
63
+ This License does not grant you any right in any trademark or logo of
64
+ Licensor or its affiliates (provided that you may use a trademark or logo of
65
+ Licensor as expressly required by this License).
66
+
67
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
68
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
69
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
71
+ TITLE.
72
+
73
+ MariaDB hereby grants you permission to use this License's text to license
74
+ your works, and to refer to it using the trademark "Business Source License",
75
+ as long as you comply with the Covenants of Licensor below.
76
+
77
+ Covenants of Licensor
78
+
79
+ In consideration of the right to use this License's text and the "Business
80
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
81
+ other recipients of the licensed work to be provided by Licensor:
82
+
83
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
84
+ or a license that is compatible with GPL Version 2.0 or a later version,
85
+ where "compatible" means that software provided under the Change License
86
+ can be included in a program with software provided under GPL Version 2.0
87
+ or a later version. Licensor may specify additional Change Licenses
88
+ without limitation.
89
+
90
+ 2. To either: (a) specify an additional grant of rights to use that does not
91
+ impose any additional restriction on the right granted in this License, as
92
+ the Additional Use Grant; or (b) insert the text "None".
93
+
94
+ 3. To specify a Change Date.
95
+
96
+ 4. Not to modify this License in any other way.
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.4
2
+ Name: echo-mem
3
+ Version: 0.5.0
4
+ Summary: A long-horizon memory architecture for AI agents
5
+ License-Expression: BUSL-1.1
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ License-File: LICENSE-APACHE-2.0
10
+ Requires-Dist: psycopg[binary]<4,>=3.2
11
+ Requires-Dist: psycopg-pool<4,>=3.2
12
+ Requires-Dist: pgvector<1,>=0.3
13
+ Requires-Dist: alembic<2,>=1.13
14
+ Requires-Dist: sqlalchemy<3,>=2
15
+ Requires-Dist: mcp<3,>=2.0
16
+ Requires-Dist: sentence-transformers<4,>=3
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest<9,>=8; extra == "dev"
19
+ Requires-Dist: ruff<1,>=0.6; extra == "dev"
20
+ Requires-Dist: build<2,>=1; extra == "dev"
21
+ Requires-Dist: twine<7,>=5; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Echo Memory
25
+
26
+ [![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)
27
+ [![PyPI](https://img.shields.io/pypi/v/echo-mem)](https://pypi.org/project/echo-mem/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/echo-mem)](https://pypi.org/project/echo-mem/)
29
+ [![License](https://img.shields.io/badge/license-BSL%201.1-blue)](LICENSE)
30
+
31
+ **Shared memory for AI agents, as a graph in your own database.** What Claude Code learns,
32
+ Cursor and Codex can recall. Every fact records who wrote it and when, and the server
33
+ never calls a model to store one.
34
+
35
+ > Your agents start every session from zero. The usual fix is a notes file you paste into
36
+ > context, which grows until it is mostly irrelevant to whatever you are asking. Echo
37
+ > Memory is the other shape: facts connected to each other, and a query that returns the
38
+ > few that matter. On the author's own store that is **96.7% less context** for the same
39
+ > answer, with the answer still present 87.2% of the time across 1,190 questions.
40
+
41
+ ```
42
+ write_episode query_memory
43
+ billing ──uses──▸ Razorpay "how do we take payments"
44
+ │ written by claude-code ▸ billing uses Razorpay, not Stripe
45
+ │ supersedes ──▸ Stripe written by claude-code, 3 days ago
46
+ └ no model invoked ▸ 1,372 tokens, not 41,838
47
+ ```
48
+
49
+ ## Install
50
+
51
+ **Requires:** Python 3.11+ and Docker (for the database).
52
+
53
+ ```bash
54
+ pipx install echo-mem
55
+ echo-memory quickstart
56
+ ```
57
+
58
+ `quickstart` starts the database, applies the schema, and prints the `claude mcp add`
59
+ line that registers it, filled in with the port it actually used. The Postgres image is
60
+ published, so nothing compiles.
61
+
62
+ Or use the hosted service and run no database at all:
63
+
64
+ ```bash
65
+ pipx install echo-mem
66
+ echo-memory connect <key> # a key from https://app.echo-mem.com
67
+ ```
68
+
69
+ Then once per machine, so an agent knows *when* to record and recall rather than only
70
+ that the tools exist:
71
+
72
+ ```bash
73
+ echo-memory install --global
74
+ ```
75
+
76
+ > **Restart your client afterwards.** An MCP server is a long lived process that holds
77
+ > the code and config it started with, and an editable install does not change that.
78
+
79
+ > **The PyPI name is `echo-mem`, not `echo-memory`.** That name belongs to an unrelated
80
+ > hosted product. The import package and the CLI are both `echo_memory` / `echo-memory`;
81
+ > only the distribution name differs.
82
+
83
+ ## Usage
84
+
85
+ ```bash
86
+ echo-memory status # what each scope holds, and which agents have written
87
+ echo-memory health # a score, what is weak, and what to do about it
88
+ echo-memory dashboard --serve # the graph, in a browser, localhost only
89
+
90
+ echo-memory why <fact_id> # the full audit trail for one fact
91
+ echo-memory recall "<question>" # query the store from a terminal
92
+ echo-memory export # everything, as JSON
93
+
94
+ echo-memory install --for cursor # wire one client, project scoped
95
+ echo-memory adopt # wire every MCP client on the machine, each with its own id
96
+
97
+ echo-memory eval # retrieval quality against your own store
98
+ echo-memory eval --context # what a recall costs against injecting everything
99
+ echo-memory eval --context --sweep # the same, as a curve across corpus size
100
+ echo-memory calibrate # is entity resolution trustworthy on your data
101
+ echo-memory benchmark # write, query and digest latency
102
+ ```
103
+
104
+ ### The seven MCP tools
105
+
106
+ | Tool | What it does |
107
+ |---|---|
108
+ | `write_episode` | Store entities and the facts connecting them. No model call. |
109
+ | `query_memory` | Hybrid vector and full text retrieval, fused by reciprocal rank. |
110
+ | `trace_cause` | Causal chains through the entities a subject matches, not a ranked list. |
111
+ | `record_recall_save` | Mark that a recalled fact saved re explaining something. Refuses a fact no read returned. |
112
+ | `get_audit_log` | Every change to memory, with a plain language reason. |
113
+ | `pending_documents` | Memory files this project wrote that the graph has not heard about. |
114
+ | `mark_ingested` | Close one of those out. |
115
+
116
+ ## What you get
117
+
118
+ **A graph, not a list.** Entities are nodes and a fact is an edge between two of them.
119
+ Two sessions that never knew about each other resolve onto the same entity by name, so
120
+ the second inherits what the first learned.
121
+
122
+ **Bounded retrieval, designed but not built.** The plan is that old, rarely read memory
123
+ demotes into higher level summaries over time, with nothing discarded and every summary
124
+ still edged back to the facts it came from. None of it exists yet: there is no tiering,
125
+ no summarisation, and retrieval today walks every active fact in the scope. It is
126
+ described in [`docs/designs/`](docs/designs/) and listed below as v1c, and this paragraph
127
+ used to claim it in the present tense.
128
+
129
+ **Provenance on every fact.** Who wrote it, which tool, which project, when, and which
130
+ reads returned it. A superseded fact is never deleted. It stops being drawn and stays
131
+ reachable with its history.
132
+
133
+ **Causal typing, and a walk over it.** A fact can carry `causal_hint`: one of
134
+ `caused_by`, `led_to`, `enabled_by`, `blocked_by` or `contradicts`, set by the agent's
135
+ own read of what the session said and never inferred statistically. `trace_cause` walks
136
+ those links and returns chains rather than a ranked list, because the answer to "why did
137
+ this happen" is an ordered chain and similarity cannot produce one. "A led_to B" and "B
138
+ caused_by A" are one claim written from opposite ends, and both assemble into the same
139
+ chain.
140
+
141
+ A fact with no hint is associative, which is the default and usually correct. Facts
142
+ written before 0.5.0 have no hint and are not a gap to fix. An empty answer from
143
+ `trace_cause` says which kind of empty it is: nobody recorded a cause here is a
144
+ different fact about a store than there are no chains.
145
+
146
+ **No inference on the write path.** Extraction happens in the calling agent, so storing
147
+ a memory invokes no model on the server. The cost moved rather than vanished: the agent
148
+ has to arrive with entities and facts already extracted, which is what the
149
+ [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
150
+ Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
151
+ "every episode triggers multiple LLM calls" and that "write cost scales with volume".
152
+
153
+ The obvious reply is that cheap writes are cheap because they do less, and that reply is
154
+ correct on the mechanism. [`docs/WRITE-COST.md`](docs/WRITE-COST.md) answers it properly,
155
+ including the two measured costs of the choice: the Stop gate fired seven times and
156
+ produced one fact, and a write touching an ambiguous entity is deferred while the call
157
+ returns as though it succeeded.
158
+
159
+ **Any MCP client.** A coding assistant, a chatbot, an ops agent, or something built in
160
+ house. Coding agents are where this is proven, not what it is limited to.
161
+
162
+ ## Numbers, and how they were taken
163
+
164
+ Every figure comes from this repository or a live store, on a date, with the command that
165
+ reproduces it on yours. The corpus is small and the noise floor is stated, because a
166
+ difference nobody sized is not a result.
167
+
168
+ | Measure | Value | Reproduce |
169
+ |---|---|---|
170
+ | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
171
+ | 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` |
172
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460 | `scripts/locomo-bench.py` |
173
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.940**, turn@10 0.727 | `scripts/longmemeval-bench.py --per-type 15` |
174
+ | Server side model calls per write | **0** | `echo-memory benchmark` |
175
+ | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
176
+ | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
177
+
178
+ That last row is the one that went the wrong way, and it is here on purpose. The interval
179
+ includes chance, so the unattended merge is switched off: at the automatic bar precision
180
+ was 50% over two reviewed pairs, and the audit log showed that path had fired once in the
181
+ system's entire history. A near match is now offered for confirmation instead.
182
+
183
+ The LoCoMo row is retrieval, not QA accuracy. Published LoCoMo results have a model write
184
+ an answer and a second model judge it; this asks only whether the turn holding the answer
185
+ came back, which is a ceiling on QA accuracy rather than a substitute for it, and is not
186
+ comparable to anybody's published QA figure. It also feeds raw dialogue turns, which skips
187
+ the extraction step this design pushes to the calling agent, so it is a floor as well as a
188
+ ceiling. The worst row, multi hop at recall@1 0.099, is in
189
+ [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) with the rest.
190
+
191
+ The context saving is measured against a specific baseline, stated so it cannot be read
192
+ as more than it is. Not "no memory at all", which is however long a human spends re
193
+ explaining and is unmeasurable. It is the thing people do instead: keep the project's
194
+ notes in one file and paste the whole file. On that store the file is 325 facts, about
195
+ 41,838 tokens; a recall returned 1,372 on average. The hit rate belongs beside it, because
196
+ a recall that returned nothing would score 100%.
197
+
198
+ ## The graph
199
+
200
+ Memory is a graph, not a list of notes. Entities are nodes; a fact is an **edge** between
201
+ two of them. That is the whole data model, and everything else follows from it.
202
+
203
+ ![The memory graph](docs/images/graph-overview.png)
204
+
205
+ Three projects here. `checkout-api`, `mobile-app` and `data-pipeline` were recorded in
206
+ separate sessions and never told about each other, yet the picture already separates them,
207
+ because separation is a property of the edges rather than a label anyone applied.
208
+
209
+ **Clusters come from structure.** Densely connected facts are grouped by label propagation
210
+ over the edges, and each cluster is named after its most connected node. That is why
211
+ `data-pipeline` sits apart: nothing it knows touches payments. It is also why
212
+ `checkout-api` and `mobile-app` share a cluster despite being different codebases. They
213
+ genuinely share an idea, and the graph found it rather than being told.
214
+
215
+ **Components are the stronger claim.** Two nodes in different components have no path
216
+ between them at all, which is the strongest statement this graph can make that two
217
+ memories are unrelated.
218
+
219
+ ### Click a node: everything it takes part in
220
+
221
+ ![A node selected](docs/images/graph-node-selected.png)
222
+
223
+ `idempotency keys` is the largest node here and nobody made it large: seventeen facts from
224
+ several services resolved onto one entity by name. The panel lists every one, with which
225
+ agent wrote it and when.
226
+
227
+ ### Click a link: why memory believes it
228
+
229
+ ![A fact selected](docs/images/graph-fact-selected.png)
230
+
231
+ Not a tooltip. Who wrote the fact, in which project, when, and how each of its entities
232
+ resolved. `echo-memory why <fact_id>` prints the same trail in a terminal.
233
+
234
+ ### Seeing your own
235
+
236
+ ```bash
237
+ echo-memory dashboard --serve --open
238
+ ```
239
+
240
+ The images above come from a synthetic dataset (`scripts/demo-seed.py`) rather than a real
241
+ store, for the obvious reason: a real memory graph is full of hostnames, account numbers
242
+ and client names.
243
+
244
+ ## Wiring more than one tool
245
+
246
+ **Give each client its own `ECHO_MEMORY_AGENT_ID`.** Cursor should say `cursor`, Claude
247
+ Desktop `claude-desktop`. Memory is shared either way, but a fact records which tool
248
+ learned it, and two tools claiming the same id makes cross tool recall impossible to see
249
+ afterwards.
250
+
251
+ ```bash
252
+ echo-memory adopt # every MCP client on the machine, each with its own id
253
+ echo-memory install [path] # one project: MCP config plus a skill, committed with the code
254
+ ```
255
+
256
+ `adopt` shows the diff before writing anything. For an agent that does not speak MCP, see
257
+ [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
258
+
259
+ ## Is the graph in good shape?
260
+
261
+ ```bash
262
+ echo-memory health
263
+ ```
264
+
265
+ A score, what is strong, what needs attention, and what to do about each, including what
266
+ recall has cost: how often memory was read, how often a read returned anything, roughly
267
+ how many tokens were injected, and how many saves those reads produced. Writes were
268
+ counted from the start; reads were not counted at all, so nothing could answer whether
269
+ recall earns what it costs. It exists to be run when you have no question, because a store
270
+ can look healthy by every other number while most of its facts came from a bulk import,
271
+ the last real write was a week ago, and only one of several wired agents has ever written
272
+ anything. `--json` for machine readable output.
273
+
274
+ Nothing in it is gated. The paid plan sells hosting; diagnostics about your own data are
275
+ not a thing to withhold from the person whose data it is.
276
+
277
+ ## Architecture
278
+
279
+ **Storage** PostgreSQL with `pgvector` and Apache AGE, from a single local agent up to an
280
+ organisation wide shared graph, with no forced migration later. The novel work is the
281
+ memory structure and the read/write algorithm on top of it, not a new database engine.
282
+
283
+ **Retrieval** Hybrid vector and full text search fused by reciprocal rank in v1a.
284
+ Personalised PageRank via `networkx` lands in v1b for multi hop associative retrieval.
285
+
286
+ **Interface** [Model Context Protocol](https://modelcontextprotocol.io), so any compliant
287
+ agent reads and writes the same graph.
288
+
289
+ ## Status
290
+
291
+ Early and staged, on purpose. See [`docs/designs/`](docs/designs/) for the architecture
292
+ and the v1a to v1b plan.
293
+
294
+ | | |
295
+ |---|---|
296
+ | **v1a, built** | Basic recall. Seven MCP tools, thirty CLI commands, on PyPI and in the MCP registry. |
297
+ | **v1b, part built** | Causal typing and `trace_cause` shipped in 0.5.0. Multi hop associative retrieval has not: 187 questions no single fact answers score MRR 0.212 today, and that number is what the rest of v1b has to beat. |
298
+ | **v1c, designed** | Consolidation: hot, consolidated and archived tiers, so retrieval cost stops tracking total facts written. Nothing implemented. |
299
+ | **v1.1, planned** | Organisation wide tenancy: per agent, per team, or org wide graphs. |
300
+
301
+ The validated wedge driving v1a is memory shared across coding agents, which is the
302
+ author's own daily pain and the case with the most evidence behind it. Everything else is
303
+ the target this architecture is built toward.
304
+
305
+ ## Hosted
306
+
307
+ Running it yourself is free under the Business Source License for any non production use,
308
+ and free in production for organisations under 50 people and under $5M revenue, with no
309
+ account and no feature held
310
+ back. [app.echo-mem.com](https://app.echo-mem.com) runs the database for you at $99 a
311
+ month if you would rather not.
312
+
313
+ <details>
314
+ <summary>Contributing</summary>
315
+
316
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md). Issues and pull requests welcome; please read the
317
+ design docs first so proposals fit the staged build plan. A first pull request is asked to
318
+ sign the [Contributor License Agreement](CLA.md), once, in the PR thread.
319
+
320
+ The most useful contribution is a measurement that disagrees with one of the numbers
321
+ above. Run `echo-memory eval`, `calibrate` or `benchmark` on your own store and open an
322
+ issue with the output.
323
+
324
+ </details>
325
+
326
+ ## License
327
+
328
+ Business Source License 1.1. See [`LICENSE`](LICENSE).
329
+
330
+ The source is public and stays public. What changed on 25 September 2026 is who may run it
331
+ in production without an agreement:
332
+
333
+ | | |
334
+ |---|---|
335
+ | Development, testing, evaluation, research, teaching | free, any size |
336
+ | Production, under 50 employees and under $5M revenue | free |
337
+ | Production, above that | [talk to us](mailto:hello@echo-mem.com) |
338
+ | Offering it to third parties as a hosted service | [talk to us](mailto:hello@echo-mem.com) |
339
+
340
+ Each released version converts to **Apache 2.0 four years after it is published**, and that
341
+ conversion is automatic and irrevocable.
342
+
343
+ Versions published before this change remain under Apache 2.0 permanently. That includes
344
+ everything up to and including 0.4.1 on PyPI. Relicensing cannot reach back, and this note
345
+ exists so nobody has to work that out from a git history. The Apache text those versions
346
+ were released under is kept at [`LICENSE-APACHE-2.0`](LICENSE-APACHE-2.0).
347
+
348
+ <!-- The MCP registry proves you own a PyPI package by finding this line in the
349
+ package's own description. It has to survive into the built distribution,
350
+ which is why it lives in the README rather than in a workflow. -->
351
+ mcp-name: io.github.ayushcodes10/echo-mem