echo-mem 0.5.2__tar.gz → 0.5.3__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 (110) hide show
  1. {echo_mem-0.5.2/src/echo_mem.egg-info → echo_mem-0.5.3}/PKG-INFO +27 -8
  2. {echo_mem-0.5.2 → echo_mem-0.5.3}/README.md +26 -7
  3. {echo_mem-0.5.2 → echo_mem-0.5.3}/pyproject.toml +1 -1
  4. {echo_mem-0.5.2 → echo_mem-0.5.3/src/echo_mem.egg-info}/PKG-INFO +27 -8
  5. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_mem.egg-info/SOURCES.txt +8 -0
  6. echo_mem-0.5.3/src/echo_memory/cli/infer_causal.py +737 -0
  7. echo_mem-0.5.3/src/echo_memory/cli/llm.py +132 -0
  8. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/main.py +160 -1
  9. echo_mem-0.5.3/src/echo_memory/eval/external.py +713 -0
  10. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/write_episode.py +7 -0
  11. echo_mem-0.5.3/src/echo_memory/migrations/versions/0025_lexical_statistics.py +72 -0
  12. echo_mem-0.5.3/src/echo_memory/migrations/versions/0026_late_causal_extraction.py +111 -0
  13. echo_mem-0.5.3/src/echo_memory/migrations/versions/0027_lexical_staleness_without_a_scan.py +47 -0
  14. echo_mem-0.5.3/src/echo_memory/retrieval/bm25.py +304 -0
  15. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/retrieval/query_memory.py +229 -20
  16. echo_mem-0.5.3/src/echo_memory/retrieval/salience.py +98 -0
  17. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/server.py +13 -2
  18. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/skill/SKILL.md +6 -0
  19. {echo_mem-0.5.2 → echo_mem-0.5.3}/LICENSE +0 -0
  20. {echo_mem-0.5.2 → echo_mem-0.5.3}/LICENSE-APACHE-2.0 +0 -0
  21. {echo_mem-0.5.2 → echo_mem-0.5.3}/setup.cfg +0 -0
  22. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_mem.egg-info/dependency_links.txt +0 -0
  23. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_mem.egg-info/entry_points.txt +0 -0
  24. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_mem.egg-info/requires.txt +0 -0
  25. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_mem.egg-info/top_level.txt +0 -0
  26. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/__init__.py +0 -0
  27. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/audit/__init__.py +0 -0
  28. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/audit/get_audit_log.py +0 -0
  29. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/__init__.py +0 -0
  30. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/adopt.py +0 -0
  31. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/analyse.py +0 -0
  32. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/benchmark.py +0 -0
  33. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/calibrate.py +0 -0
  34. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/connect.py +0 -0
  35. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/dashboard.py +0 -0
  36. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/dashboard_html.py +0 -0
  37. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/export.py +0 -0
  38. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/graph.py +0 -0
  39. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/health.py +0 -0
  40. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/hooks.py +0 -0
  41. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/initdb.py +0 -0
  42. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/install.py +0 -0
  43. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/judge.py +0 -0
  44. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/merge.py +0 -0
  45. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/queue.py +0 -0
  46. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/quickstart.py +0 -0
  47. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/reattribute.py +0 -0
  48. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/reattribute_cmd.py +0 -0
  49. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/recall.py +0 -0
  50. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/reconcile.py +0 -0
  51. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/reindex.py +0 -0
  52. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/session_start.py +0 -0
  53. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/skill.py +0 -0
  54. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/status.py +0 -0
  55. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/stop_gate.py +0 -0
  56. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/trial.py +0 -0
  57. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/unmerge.py +0 -0
  58. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/cli/why.py +0 -0
  59. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/client.py +0 -0
  60. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/eval/__init__.py +0 -0
  61. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/eval/independent.py +0 -0
  62. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/eval/retrieval.py +0 -0
  63. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/eval/sweep.py +0 -0
  64. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/graph/__init__.py +0 -0
  65. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/graph/communities.py +0 -0
  66. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/__init__.py +0 -0
  67. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/config.py +0 -0
  68. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/db.py +0 -0
  69. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/logging.py +0 -0
  70. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/pool.py +0 -0
  71. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/infra/project.py +0 -0
  72. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/__init__.py +0 -0
  73. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/activity.py +0 -0
  74. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/bootstrap.py +0 -0
  75. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/capture.py +0 -0
  76. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/embeddings.py +0 -0
  77. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/neighbourhood.py +0 -0
  78. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/ingestion/resolution.py +0 -0
  79. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/env.py +0 -0
  80. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0001_initial_schema.py +0 -0
  81. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0002_trial_observations.py +0 -0
  82. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0003_project_and_agent_provenance.py +0 -0
  83. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0004_bootstrap_state.py +0 -0
  84. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0005_recall_save_guards.py +0 -0
  85. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0006_project_analysis.py +0 -0
  86. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0007_backfill_unknown_agent.py +0 -0
  87. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0008_read_events.py +0 -0
  88. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0009_read_event_attribution.py +0 -0
  89. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0010_stop_gate_fired.py +0 -0
  90. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0011_backfill_absent_agent.py +0 -0
  91. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0012_read_event_agent.py +0 -0
  92. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0013_active_fact_indexes.py +0 -0
  93. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0014_session_activity.py +0 -0
  94. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0015_retracted_observations.py +0 -0
  95. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0016_one_node_per_name.py +0 -0
  96. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0017_writer_version.py +0 -0
  97. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0018_trial_runs_are_history.py +0 -0
  98. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0019_versions_expected_since.py +0 -0
  99. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0020_who_closed_a_queued_document.py +0 -0
  100. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0021_delivery_evidence.py +0 -0
  101. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0022_independent_evaluation.py +0 -0
  102. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0023_second_judge.py +0 -0
  103. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/migrations/versions/0024_node_by_id.py +0 -0
  104. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/retrieval/__init__.py +0 -0
  105. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/retrieval/causality.py +0 -0
  106. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/retrieval/fusion.py +0 -0
  107. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/trial/__init__.py +0 -0
  108. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/trial/check.py +0 -0
  109. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/trial/observations.py +0 -0
  110. {echo_mem-0.5.2 → echo_mem-0.5.3}/src/echo_memory/trial/reads.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echo-mem
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A long-horizon memory architecture for AI agents
5
5
  License-Expression: BUSL-1.1
6
6
  Requires-Python: >=3.11
@@ -94,6 +94,8 @@ echo-memory export # everything, as JSON
94
94
  echo-memory install --for cursor # wire one client, project scoped
95
95
  echo-memory adopt # wire every MCP client on the machine, each with its own id
96
96
 
97
+ echo-memory infer-causal-hints # type the facts whose own sentence states a cause (dry run)
98
+
97
99
  echo-memory eval # retrieval quality against your own store
98
100
  echo-memory eval --context # what a recall costs against injecting everything
99
101
  echo-memory eval --context --sweep # the same, as a curve across corpus size
@@ -138,13 +140,30 @@ this happen" is an ordered chain and similarity cannot produce one. "A led_to B"
138
140
  caused_by A" are one claim written from opposite ends, and both assemble into the same
139
141
  chain.
140
142
 
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.
143
+ A fact with no hint is associative, which is the default and usually correct. An empty
144
+ answer from `trace_cause` says which kind of empty it is: nobody recorded a cause here is
145
+ a different fact about a store than there are no chains.
146
+
147
+ **Filling it in, for a store that predates it.** Facts written before 0.5.0 carry no
148
+ hint, so on an existing store `trace_cause` has nothing to walk.
149
+ `echo-memory infer-causal-hints` re-reads the fact text already stored with your own
150
+ model (`ECHO_MEMORY_LLM_API_KEY`, `ECHO_MEMORY_LLM_MODEL`) and types the edges whose own
151
+ sentence states the relation. Dry run by default; `--write` applies what it printed and
152
+ `--clear --write` takes it back.
153
+
154
+ This is extraction done late, not causal discovery, and the difference is enforced rather
155
+ than promised. A sentence with no causal connective is never sent to a model, so
156
+ co-occurrence is refused before it costs anything. Every proposal has to quote the words
157
+ that state the relation, and a quote that is not literally in the fact is dropped, so a
158
+ model reasoning from the world instead of reading the sentence gets nothing stored. Each
159
+ hint written is audited and marked as extracted late, which is what makes `--clear`
160
+ able to remove exactly these and never one you wrote at write time.
145
161
 
146
162
  **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
163
+ a memory invokes no model on the server. `infer-causal-hints` above is not an exception
164
+ to that and is worth being precise about: it is a command a person types against facts
165
+ already stored, it never runs as part of a write, a migration or a hook, and a store that
166
+ never runs it never causes a model call. The cost moved rather than vanished: the agent
148
167
  has to arrive with entities and facts already extracted, which is what the
149
168
  [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
150
169
  Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
@@ -169,8 +188,8 @@ difference nobody sized is not a result.
169
188
  |---|---|---|
170
189
  | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
171
190
  | 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` |
191
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460, session@10 0.850 | `echo-memory eval-external locomo` |
192
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.937**, recall@10 0.727, MRR 0.428 | `echo-memory eval-external longmemeval --per-type 15` |
174
193
  | Server side model calls per write | **0** | `echo-memory benchmark` |
175
194
  | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
176
195
  | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
@@ -71,6 +71,8 @@ echo-memory export # everything, as JSON
71
71
  echo-memory install --for cursor # wire one client, project scoped
72
72
  echo-memory adopt # wire every MCP client on the machine, each with its own id
73
73
 
74
+ echo-memory infer-causal-hints # type the facts whose own sentence states a cause (dry run)
75
+
74
76
  echo-memory eval # retrieval quality against your own store
75
77
  echo-memory eval --context # what a recall costs against injecting everything
76
78
  echo-memory eval --context --sweep # the same, as a curve across corpus size
@@ -115,13 +117,30 @@ this happen" is an ordered chain and similarity cannot produce one. "A led_to B"
115
117
  caused_by A" are one claim written from opposite ends, and both assemble into the same
116
118
  chain.
117
119
 
118
- A fact with no hint is associative, which is the default and usually correct. Facts
119
- written before 0.5.0 have no hint and are not a gap to fix. An empty answer from
120
- `trace_cause` says which kind of empty it is: nobody recorded a cause here is a
121
- different fact about a store than there are no chains.
120
+ A fact with no hint is associative, which is the default and usually correct. An empty
121
+ answer from `trace_cause` says which kind of empty it is: nobody recorded a cause here is
122
+ a different fact about a store than there are no chains.
123
+
124
+ **Filling it in, for a store that predates it.** Facts written before 0.5.0 carry no
125
+ hint, so on an existing store `trace_cause` has nothing to walk.
126
+ `echo-memory infer-causal-hints` re-reads the fact text already stored with your own
127
+ model (`ECHO_MEMORY_LLM_API_KEY`, `ECHO_MEMORY_LLM_MODEL`) and types the edges whose own
128
+ sentence states the relation. Dry run by default; `--write` applies what it printed and
129
+ `--clear --write` takes it back.
130
+
131
+ This is extraction done late, not causal discovery, and the difference is enforced rather
132
+ than promised. A sentence with no causal connective is never sent to a model, so
133
+ co-occurrence is refused before it costs anything. Every proposal has to quote the words
134
+ that state the relation, and a quote that is not literally in the fact is dropped, so a
135
+ model reasoning from the world instead of reading the sentence gets nothing stored. Each
136
+ hint written is audited and marked as extracted late, which is what makes `--clear`
137
+ able to remove exactly these and never one you wrote at write time.
122
138
 
123
139
  **No inference on the write path.** Extraction happens in the calling agent, so storing
124
- a memory invokes no model on the server. The cost moved rather than vanished: the agent
140
+ a memory invokes no model on the server. `infer-causal-hints` above is not an exception
141
+ to that and is worth being precise about: it is a command a person types against facts
142
+ already stored, it never runs as part of a write, a migration or a hook, and a store that
143
+ never runs it never causes a model call. The cost moved rather than vanished: the agent
125
144
  has to arrive with entities and facts already extracted, which is what the
126
145
  [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
127
146
  Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
@@ -146,8 +165,8 @@ difference nobody sized is not a result.
146
165
  |---|---|---|
147
166
  | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
148
167
  | 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` |
149
- | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460 | `scripts/locomo-bench.py` |
150
- | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.940**, turn@10 0.727 | `scripts/longmemeval-bench.py --per-type 15` |
168
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460, session@10 0.850 | `echo-memory eval-external locomo` |
169
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.937**, recall@10 0.727, MRR 0.428 | `echo-memory eval-external longmemeval --per-type 15` |
151
170
  | Server side model calls per write | **0** | `echo-memory benchmark` |
152
171
  | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
153
172
  | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
@@ -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.5.2"
9
+ version = "0.5.3"
10
10
  description = "A long-horizon memory architecture for AI agents"
11
11
  readme = "README.md"
12
12
  # BUSL-1.1 is the SPDX identifier for the Business Source License 1.1, so
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echo-mem
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A long-horizon memory architecture for AI agents
5
5
  License-Expression: BUSL-1.1
6
6
  Requires-Python: >=3.11
@@ -94,6 +94,8 @@ echo-memory export # everything, as JSON
94
94
  echo-memory install --for cursor # wire one client, project scoped
95
95
  echo-memory adopt # wire every MCP client on the machine, each with its own id
96
96
 
97
+ echo-memory infer-causal-hints # type the facts whose own sentence states a cause (dry run)
98
+
97
99
  echo-memory eval # retrieval quality against your own store
98
100
  echo-memory eval --context # what a recall costs against injecting everything
99
101
  echo-memory eval --context --sweep # the same, as a curve across corpus size
@@ -138,13 +140,30 @@ this happen" is an ordered chain and similarity cannot produce one. "A led_to B"
138
140
  caused_by A" are one claim written from opposite ends, and both assemble into the same
139
141
  chain.
140
142
 
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.
143
+ A fact with no hint is associative, which is the default and usually correct. An empty
144
+ answer from `trace_cause` says which kind of empty it is: nobody recorded a cause here is
145
+ a different fact about a store than there are no chains.
146
+
147
+ **Filling it in, for a store that predates it.** Facts written before 0.5.0 carry no
148
+ hint, so on an existing store `trace_cause` has nothing to walk.
149
+ `echo-memory infer-causal-hints` re-reads the fact text already stored with your own
150
+ model (`ECHO_MEMORY_LLM_API_KEY`, `ECHO_MEMORY_LLM_MODEL`) and types the edges whose own
151
+ sentence states the relation. Dry run by default; `--write` applies what it printed and
152
+ `--clear --write` takes it back.
153
+
154
+ This is extraction done late, not causal discovery, and the difference is enforced rather
155
+ than promised. A sentence with no causal connective is never sent to a model, so
156
+ co-occurrence is refused before it costs anything. Every proposal has to quote the words
157
+ that state the relation, and a quote that is not literally in the fact is dropped, so a
158
+ model reasoning from the world instead of reading the sentence gets nothing stored. Each
159
+ hint written is audited and marked as extracted late, which is what makes `--clear`
160
+ able to remove exactly these and never one you wrote at write time.
145
161
 
146
162
  **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
163
+ a memory invokes no model on the server. `infer-causal-hints` above is not an exception
164
+ to that and is worth being precise about: it is a command a person types against facts
165
+ already stored, it never runs as part of a write, a migration or a hook, and a store that
166
+ never runs it never causes a model call. The cost moved rather than vanished: the agent
148
167
  has to arrive with entities and facts already extracted, which is what the
149
168
  [tool contract](docs/DEVELOPMENT.md) spells out. The comparison that makes this matter is
150
169
  Zep/Graphiti, the closest architectural match, whose own description of ingestion is that
@@ -169,8 +188,8 @@ difference nobody sized is not a result.
169
188
  |---|---|---|
170
189
  | Context per recall vs injecting everything | **96.7% less**, hit@10 0.872 over 1,190 questions | `echo-memory eval --context` |
171
190
  | 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` |
191
+ | LoCoMo retrieval, 1,982 questions, 5,882 turns | recall@10 **0.601**, hit@10 0.658, MRR 0.460, session@10 0.850 | `echo-memory eval-external locomo` |
192
+ | LongMemEval retrieval, 90 questions, 15 per type | session@10 **0.937**, recall@10 0.727, MRR 0.428 | `echo-memory eval-external longmemeval --per-type 15` |
174
193
  | Server side model calls per write | **0** | `echo-memory benchmark` |
175
194
  | Write, query, digest latency (median) | 15ms, 8ms, 1ms | `echo-memory benchmark` |
176
195
  | Entity resolution AUC | 0.666, 95% CI [0.421, 0.881] | `echo-memory calibrate` |
@@ -25,9 +25,11 @@ src/echo_memory/cli/export.py
25
25
  src/echo_memory/cli/graph.py
26
26
  src/echo_memory/cli/health.py
27
27
  src/echo_memory/cli/hooks.py
28
+ src/echo_memory/cli/infer_causal.py
28
29
  src/echo_memory/cli/initdb.py
29
30
  src/echo_memory/cli/install.py
30
31
  src/echo_memory/cli/judge.py
32
+ src/echo_memory/cli/llm.py
31
33
  src/echo_memory/cli/main.py
32
34
  src/echo_memory/cli/merge.py
33
35
  src/echo_memory/cli/queue.py
@@ -45,6 +47,7 @@ src/echo_memory/cli/trial.py
45
47
  src/echo_memory/cli/unmerge.py
46
48
  src/echo_memory/cli/why.py
47
49
  src/echo_memory/eval/__init__.py
50
+ src/echo_memory/eval/external.py
48
51
  src/echo_memory/eval/independent.py
49
52
  src/echo_memory/eval/retrieval.py
50
53
  src/echo_memory/eval/sweep.py
@@ -89,10 +92,15 @@ src/echo_memory/migrations/versions/0021_delivery_evidence.py
89
92
  src/echo_memory/migrations/versions/0022_independent_evaluation.py
90
93
  src/echo_memory/migrations/versions/0023_second_judge.py
91
94
  src/echo_memory/migrations/versions/0024_node_by_id.py
95
+ src/echo_memory/migrations/versions/0025_lexical_statistics.py
96
+ src/echo_memory/migrations/versions/0026_late_causal_extraction.py
97
+ src/echo_memory/migrations/versions/0027_lexical_staleness_without_a_scan.py
92
98
  src/echo_memory/retrieval/__init__.py
99
+ src/echo_memory/retrieval/bm25.py
93
100
  src/echo_memory/retrieval/causality.py
94
101
  src/echo_memory/retrieval/fusion.py
95
102
  src/echo_memory/retrieval/query_memory.py
103
+ src/echo_memory/retrieval/salience.py
96
104
  src/echo_memory/skill/SKILL.md
97
105
  src/echo_memory/trial/__init__.py
98
106
  src/echo_memory/trial/check.py