loop-memory 0.4.15__tar.gz → 0.4.17__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 (170) hide show
  1. {loop_memory-0.4.15 → loop_memory-0.4.17}/PKG-INFO +47 -2
  2. {loop_memory-0.4.15 → loop_memory-0.4.17}/README.md +46 -1
  3. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/read.py +12 -2
  4. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/write.py +94 -1
  5. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/main.py +6 -1
  6. loop_memory-0.4.17/loop_memory/jobs/dream.py +549 -0
  7. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/sdk.py +33 -0
  8. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/handlers.py +5 -0
  9. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/cognitive.py +39 -0
  10. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/memories.py +22 -3
  11. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/system.py +13 -1
  12. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/storage/retrieval.py +11 -1
  13. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/storage/sqlite_store.py +417 -19
  14. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/PKG-INFO +47 -2
  15. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/SOURCES.txt +5 -0
  16. {loop_memory-0.4.15 → loop_memory-0.4.17}/pyproject.toml +1 -1
  17. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_agents_init.py +2 -2
  18. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_content_hash_dedup_2026_10_18.py +2 -1
  19. loop_memory-0.4.17/tests/test_dream_2026_10_25.py +347 -0
  20. loop_memory-0.4.17/tests/test_dream_cli_http_2026_10_25.py +287 -0
  21. loop_memory-0.4.17/tests/test_latest_only_2026_10_25.py +208 -0
  22. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_memory_stats.py +3 -0
  23. loop_memory-0.4.17/tests/test_subject_key_2026_10_25.py +248 -0
  24. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_supersession_chain.py +3 -2
  25. {loop_memory-0.4.15 → loop_memory-0.4.17}/LICENSE +0 -0
  26. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/__init__.py +0 -0
  27. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/backends/__init__.py +0 -0
  28. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/backends/embedding.py +0 -0
  29. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/backends/sentence_embedder.py +0 -0
  30. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/backends/vector_store.py +0 -0
  31. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/__init__.py +0 -0
  32. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/_common.py +0 -0
  33. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/__init__.py +0 -0
  34. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/cognitive.py +0 -0
  35. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/diag.py +0 -0
  36. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/doctor.py +0 -0
  37. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/graph.py +0 -0
  38. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/hooks.py +0 -0
  39. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/rules.py +0 -0
  40. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/cli/commands/serve.py +0 -0
  41. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/decorators.py +0 -0
  42. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/doctor.py +0 -0
  43. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/engine/__init__.py +0 -0
  44. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/engine/loop.py +0 -0
  45. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/engine/reflect.py +0 -0
  46. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/examples/__init__.py +0 -0
  47. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/examples/demo.py +0 -0
  48. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/export/__init__.py +0 -0
  49. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/export/memory_md.py +0 -0
  50. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/graph/__init__.py +0 -0
  51. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/graph/build.py +0 -0
  52. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/graph/extract.py +0 -0
  53. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/ingest/__init__.py +0 -0
  54. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/ingest/loader.py +0 -0
  55. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/ingest/pipeline.py +0 -0
  56. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/__init__.py +0 -0
  57. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/cognitive.py +0 -0
  58. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/compact.py +0 -0
  59. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/consolidate.py +0 -0
  60. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/contradiction.py +0 -0
  61. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/evolution.py +0 -0
  62. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/graph.py +0 -0
  63. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/llm_compact_pass.py +0 -0
  64. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/llm_consolidate.py +0 -0
  65. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/jobs/scheduler.py +0 -0
  66. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/llm/__init__.py +0 -0
  67. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/llm/base.py +0 -0
  68. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/llm/openai_adapter.py +0 -0
  69. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/llm/providers.py +0 -0
  70. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/mcp/__init__.py +0 -0
  71. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/memory/__init__.py +0 -0
  72. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/memory/types.py +0 -0
  73. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/privacy/__init__.py +0 -0
  74. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/privacy/private.py +0 -0
  75. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/privacy/redact.py +0 -0
  76. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/py.typed +0 -0
  77. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/sdk_extensions.py +0 -0
  78. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/security/__init__.py +0 -0
  79. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/security/secrets.py +0 -0
  80. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/__init__.py +0 -0
  81. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/app.py +0 -0
  82. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/_shared.py +0 -0
  83. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/admin.py +0 -0
  84. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/export.py +0 -0
  85. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/graph.py +0 -0
  86. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/insights.py +0 -0
  87. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/sessions.py +0 -0
  88. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/routes/wiki.py +0 -0
  89. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/static/__init__.py +0 -0
  90. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/static/index.html +0 -0
  91. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/serve/watcher.py +0 -0
  92. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/storage/__init__.py +0 -0
  93. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/storage/snapshot.py +0 -0
  94. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/wiki/__init__.py +0 -0
  95. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/wiki/backfill.py +0 -0
  96. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/wiki/classifier.py +0 -0
  97. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/wiki/prompts.py +0 -0
  98. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory/wiki/scope.py +0 -0
  99. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/dependency_links.txt +0 -0
  100. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/entry_points.txt +0 -0
  101. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/requires.txt +0 -0
  102. {loop_memory-0.4.15 → loop_memory-0.4.17}/loop_memory.egg-info/top_level.txt +0 -0
  103. {loop_memory-0.4.15 → loop_memory-0.4.17}/setup.cfg +0 -0
  104. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_admin_ingest_route.py +0 -0
  105. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_agent_memory_api.py +0 -0
  106. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_agent_memory_sdk.py +0 -0
  107. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_audit_triggers_2026_09_27.py +0 -0
  108. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_auth_token_rotate.py +0 -0
  109. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_busy_timeout_2026_09_24.py +0 -0
  110. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cache_cap_2026_09_23.py +0 -0
  111. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_audit_supersede.py +0 -0
  112. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_export_okf.py +0 -0
  113. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_init.py +0 -0
  114. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_memory_stats.py +0 -0
  115. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_recall_as_of.py +0 -0
  116. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_recall_paths.py +0 -0
  117. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_recall_verbose.py +0 -0
  118. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_rules.py +0 -0
  119. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_snapshot.py +0 -0
  120. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_v7.py +0 -0
  121. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_cli_version.py +0 -0
  122. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_contradictions.py +0 -0
  123. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_decorators_2026_10_18.py +0 -0
  124. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_doctor_2026_10_12.py +0 -0
  125. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_evolution.py +0 -0
  126. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_evolution_quality.py +0 -0
  127. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_expanduser.py +0 -0
  128. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_export_ask.py +0 -0
  129. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_export_okf.py +0 -0
  130. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_graph.py +0 -0
  131. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_healthz_readyz_2026_09_24.py +0 -0
  132. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_import_okf_2026_09_27.py +0 -0
  133. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_ingest.py +0 -0
  134. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_llm_consolidator.py +0 -0
  135. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_llm_fingerprint_not_persisted.py +0 -0
  136. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_llm_providers.py +0 -0
  137. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_llm_test_endpoint.py +0 -0
  138. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_loop.py +0 -0
  139. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_mcp.py +0 -0
  140. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_memories_pagination.py +0 -0
  141. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_openclaw_loader.py +0 -0
  142. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_package_version.py +0 -0
  143. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_path_safety_2026_09_23.py +0 -0
  144. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_recall_as_of.py +0 -0
  145. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_recall_budget_2026_10_05.py +0 -0
  146. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_recall_paths.py +0 -0
  147. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_recall_provenance.py +0 -0
  148. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_recall_validity_http_2026_10_18.py +0 -0
  149. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_reflection.py +0 -0
  150. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_repo_framing.py +0 -0
  151. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_retrieval_channels_2026_10_18.py +0 -0
  152. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_routes_audit_2026_09_13.py +0 -0
  153. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_routes_audit_2026_09_20.py +0 -0
  154. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_score_api.py +0 -0
  155. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_scoring_v2.py +0 -0
  156. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_secrets.py +0 -0
  157. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_serve_app.py +0 -0
  158. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_serve_handlers.py +0 -0
  159. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_session_order.py +0 -0
  160. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_short_query.py +0 -0
  161. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_snapshot.py +0 -0
  162. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_store.py +0 -0
  163. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_subgraph_hops_2026_09_23.py +0 -0
  164. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_summarization.py +0 -0
  165. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_universal_memory.py +0 -0
  166. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_vector_store.py +0 -0
  167. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_watcher.py +0 -0
  168. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_wiki_classifier.py +0 -0
  169. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_wiki_export_escape.py +0 -0
  170. {loop_memory-0.4.15 → loop_memory-0.4.17}/tests/test_wiki_prompts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loop-memory
3
- Version: 0.4.15
3
+ Version: 0.4.17
4
4
  Summary: A general-purpose, local memory system for every AI agent you run. Loop Memory auto-captures conversations from Codex / Claude / Hermes / OpenClaw, scores them by importance × recency × usage × feedback, distils them into a curated wiki, and serves everything from a single web UI.
5
5
  Author: Loop Memory contributors <loop-memory@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -66,6 +66,7 @@ Dynamic: license-file
66
66
 
67
67
  ---
68
68
 
69
+ > **What's new in 0.4.16** — **subject-key logical fact slots + `latest_only` recall + the "dream" sleep-time consolidation pass** (audit 2026-10-25, weekly ecosystem sync — Mem0 v2.2.1 "Dream", arXiv 2605.27366 "In Defense of Forgotten", Zep/Graphiti temporal edges, Letta memory blocks). New nullable `subject_key` column on `memories` names the *logical slot* a fact occupies (`"editor.preference"`); `upsert_memory(subject_key=...)` makes the new row the live version and soft-supersedes the previous one with a `superseded_by` pointer, so the version history survives and `recall_as_of()` can still answer "what did we believe last week?". The content-hash dedup path is now slot-aware — two different slots holding the same text stay two rows. `SCHEMA_VERSION` bumps 12 → 13 with an idempotent in-place migration. New `latest_only=False` flag on `recall()` / `recall_hybrid()` (Mem0 v2 `SearchMemoryOptions.latest_only` parity) collapses the answer in three layers: rows outside their validity window at *now* are dropped, a subject-key slot resolves to its newest **live** row (looked up in the store, so a stale version never comes back just because it matched the query better), and near-duplicate texts cluster by token Jaccard (default 0.85) with the newest row winning. Wired through `GET /api/recall?latest_only=1`, `GET /api/v1/recall?latest_only=1`, `loop-memory recall --latest-only` and `MemoryClient.recall(latest_only=True)`. New `loop_memory/jobs/dream.py` implements the sleep-time consolidation Mem0 v2 calls the "Dream" — deterministic, zero-dep and **non-destructive**: it decays scores, then judges every row past its deadline on **utility** (`0.45 × importance + 0.30 × usage + 0.25 × recency`) so a frequently-recalled old fact is *protected* while a never-used stale row is **soft-expired** (`invalid_at` stamped, row kept, history kept) instead of deleted, and finally merges near-duplicates into a supersession chain. Run it with `loop-memory dream [--apply] [--json]` or `POST /api/v1/dream` — it is a **dry run by default** and every decision lands in `cognitive_audit` so the dashboard can show and revert it. New store primitives `expire_memory()` / `revive_memory()`, and `memory_stats()` now reports `subject_key`, `ttl`, `valid_at`, `invalid_at` and a derived `ttl_state`. **Also fixed a real defect**: the hybrid recall path (BM25 / LIKE / semantic channels) did not filter `superseded_by`, so `recall_hybrid()` — the production path behind the CLI, MCP server and dashboard — returned both versions of a superseded fact. Source: Mem0 v2.2.1 Dream + ADD-only architecture + arXiv 2605.27366 utility-aware eviction + Zep/Graphiti bi-temporal edges (pattern only, no code copied). 77 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0416---2026-10-25)
69
70
  > **What's new in 0.4.15** — **content-hash dedup + bi-temporal validity window + evidence chain + `@agent_memory` decorator** (audit 2026-10-18, weekly ecosystem sync). Three new columns on `memories`: `content_hash` (SHA-256 of normalised text, indexed), `valid_at` (epoch the memory becomes active), `invalid_at` (epoch it expires — NULL means still open-ended). The schema migration bumps `SCHEMA_VERSION` 11 → 12 with an idempotent backfill so legacy DBs upgrade in-place. New `upsert_memory(valid_at=, invalid_at=, dedup=)` parameters let callers schedule future-dated or expiring memories and opt out of content-hash dedup for episodic event streams. `recall()` and `recall_hybrid()` now accept an optional `as_of_ts` (float epoch or ISO-8601) — when set, both methods drop memories whose `valid_at > as_of` or `invalid_at <= as_of`, complementing the existing `recall_as_of()` supersession-chain walk. Every recall hit now carries an `evidence: [{field, value, weight}]` array — BM25 / semantic / entity / rrf_total / high_importance channels are exposed individually so a UI can render "this hit because ... [bm25 1.0] [entity 0.7]" instead of a black-box score (Cognee 1.0 / Mem0 v2 evidence-citation pattern, zero-dep). New `loop_memory.decorators` exposes `@agent_memory(store, agent_id=, limit=)` — a one-line wrapper that recalls the top-k relevant memories before the body runs and writes a fresh `kind="interaction"` memory after. Strings returned from the wrapped function are stored verbatim; dict/list returns are JSON-serialised; the recall list is injected as a `memory_context` keyword when the function declares it, otherwise attached to dict results under `memories`. Sister `remember(store, text, ...)` helper for seeding. The BM25 `_like_fallback` now falls through to per-token OR-LIKE when the whole-string match returns nothing — natural-language questions like "What is the deploy process?" now hit stored facts even when FTS5 trigram can't match quoted exact phrases. Source: Cognee 1.0 ECL pipeline + Mem0 v2 hybrid fusion + Zep/Graphiti bi-temporal validity fields + Letta block-memory (pattern only, no code copied). 59 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0415---2026-10-18)
70
71
  > **What's new in 0.4.14** — **`loop-memory doctor` deep store-health check** (audit 2026-10-12). Ten diagnostic checks — `db_exists`, `db_integrity`, `schema_version`, `audit_triggers`, `audit_log_writable`, `audit_log_size`, `hooks_installed`, `version_consistent`, `wiki_no_orphans`, `recall_paths_works` — report `ok`/`info`/`warn`/`error` for the live `MemoryStore`. `--fix` auto-repairs the `audit_triggers` check. `--json` emits a stable shape for CI. `--list` prints the check function names without touching the store. `--verbose` shows `info`-level checks. Exit code `1` on any `error` status, `0` otherwise. Source: `cortexkit/magic-context doctor` auto-diagnostic pattern (MIT, 2026-10-12) + `dsh-memento` write-approval-gate pattern (Apache-2.0, 2026-10-12). Stdlib-only — we re-implement from scratch against our own `MemoryStore`. Also fixed a Python 3.14 bug: chained `store._conn().__enter__()` immediately triggers the contextmanager's `finally` and closes the connection mid-query — both call sites in `doctor.py` now use explicit `with` form. 27 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0414---2026-10-12)
71
72
 
@@ -246,6 +247,10 @@ loop-memory serve --port 7767 # open http://127.0.0.1:7767
246
247
  # 3. Make it run on a timer
247
248
  # (see docs/auto-capture.md for launchd / systemd / cron snippets)
248
249
  loop-memory consolidate # rescore + GC + dedupe
250
+
251
+ # 4. Optional: sleep-time consolidation (soft expiry, never deletes)
252
+ loop-memory dream # dry run -- what would expire / merge
253
+ loop-memory dream --apply # write it (soft-expire + supersede)
249
254
  ```
250
255
 
251
256
  ---
@@ -447,6 +452,46 @@ User signals close the loop:
447
452
  - Every `recall()` / search bumps `recall_count` on the returned
448
453
  rows so the next Stage-1 ranks them higher.
449
454
 
455
+ ### Dream: sleep-time consolidation (0.4.16)
456
+
457
+ `loop-memory dream [--apply]` is the **non-destructive** counterpart to
458
+ `loop-memory cognitive-sleep --apply`:
459
+
460
+ | | `cognitive-sleep --apply` | `dream --apply` |
461
+ |---|---|---|
462
+ | forgets a stale row | `DELETE` — the row is gone | **soft expiry** — `invalid_at` is stamped, the row and its history stay |
463
+ | decides by age alone | yes | no — **utility** decides |
464
+ | near-duplicates | merged | merged into a **supersession chain** |
465
+ | reversible | no | yes (`revive_memory()` / `recall --as-of`) |
466
+
467
+ The utility blend is `0.45 × importance + 0.30 × usage + 0.25 × recency`,
468
+ so a rarely-stated-but-constantly-recalled fact is *protected* while a
469
+ never-used stale row is expired. Age alone never decides — that is the
470
+ whole point of "In Defense of Forgotten" (arXiv 2605.27366).
471
+
472
+ The pass is a **dry run by default**: `--apply` (CLI) or `"apply": true`
473
+ (HTTP) is what actually writes. A dry run changes **no memory data** --
474
+ no expiry, no supersession, no rescore -- it only appends a
475
+ `cognitive_audit` row per proposed action (`action="suggest"`), which
476
+ is the reviewable plan the dashboard shows before you commit.
477
+
478
+ ```bash
479
+ loop-memory dream --json # machine-readable dry run
480
+ loop-memory dream --apply --ttl-days 60 # tighter horizon
481
+ loop-memory dream --apply --utility 0.2 # expire more aggressively
482
+ curl -X POST http://127.0.0.1:7767/api/v1/dream -d '{"apply": true}'
483
+ ```
484
+
485
+ After a dream pass, ask for the *current* truth explicitly:
486
+
487
+ ```bash
488
+ loop-memory recall "editor preference" --latest-only
489
+ ```
490
+
491
+ `--latest-only` (Mem0 v2 `latest_only` parity) drops rows outside their
492
+ validity window at *now*, collapses a subject-key slot to its newest live
493
+ row, and collapses near-duplicate paraphrases to the newest one.
494
+
450
495
  ### Cognitive sleep (v7)
451
496
 
452
497
  `loop-memory cognitive-sleep [--apply]` runs an **auditable cleanup
@@ -603,7 +648,7 @@ loop_memory/
603
648
  ingest/ # Codex / Claude / Hermes / OpenClaw / generic loaders
604
649
  wiki/ # distillation, classifier, scope auto-promotion
605
650
  graph/ # entity extraction + knowledge-graph build
606
- jobs/ # consolidate / evolve / cognitive-sleep / scheduler / contradiction / graph
651
+ jobs/ # consolidate / evolve / cognitive-sleep / dream / scheduler / contradiction / graph
607
652
  llm/ # provider protocol + OpenAI / Anthropic / Ollama / rule-based
608
653
  backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
609
654
  storage/ # SQLite-backed MemoryStore + migrations
@@ -22,6 +22,7 @@
22
22
 
23
23
  ---
24
24
 
25
+ > **What's new in 0.4.16** — **subject-key logical fact slots + `latest_only` recall + the "dream" sleep-time consolidation pass** (audit 2026-10-25, weekly ecosystem sync — Mem0 v2.2.1 "Dream", arXiv 2605.27366 "In Defense of Forgotten", Zep/Graphiti temporal edges, Letta memory blocks). New nullable `subject_key` column on `memories` names the *logical slot* a fact occupies (`"editor.preference"`); `upsert_memory(subject_key=...)` makes the new row the live version and soft-supersedes the previous one with a `superseded_by` pointer, so the version history survives and `recall_as_of()` can still answer "what did we believe last week?". The content-hash dedup path is now slot-aware — two different slots holding the same text stay two rows. `SCHEMA_VERSION` bumps 12 → 13 with an idempotent in-place migration. New `latest_only=False` flag on `recall()` / `recall_hybrid()` (Mem0 v2 `SearchMemoryOptions.latest_only` parity) collapses the answer in three layers: rows outside their validity window at *now* are dropped, a subject-key slot resolves to its newest **live** row (looked up in the store, so a stale version never comes back just because it matched the query better), and near-duplicate texts cluster by token Jaccard (default 0.85) with the newest row winning. Wired through `GET /api/recall?latest_only=1`, `GET /api/v1/recall?latest_only=1`, `loop-memory recall --latest-only` and `MemoryClient.recall(latest_only=True)`. New `loop_memory/jobs/dream.py` implements the sleep-time consolidation Mem0 v2 calls the "Dream" — deterministic, zero-dep and **non-destructive**: it decays scores, then judges every row past its deadline on **utility** (`0.45 × importance + 0.30 × usage + 0.25 × recency`) so a frequently-recalled old fact is *protected* while a never-used stale row is **soft-expired** (`invalid_at` stamped, row kept, history kept) instead of deleted, and finally merges near-duplicates into a supersession chain. Run it with `loop-memory dream [--apply] [--json]` or `POST /api/v1/dream` — it is a **dry run by default** and every decision lands in `cognitive_audit` so the dashboard can show and revert it. New store primitives `expire_memory()` / `revive_memory()`, and `memory_stats()` now reports `subject_key`, `ttl`, `valid_at`, `invalid_at` and a derived `ttl_state`. **Also fixed a real defect**: the hybrid recall path (BM25 / LIKE / semantic channels) did not filter `superseded_by`, so `recall_hybrid()` — the production path behind the CLI, MCP server and dashboard — returned both versions of a superseded fact. Source: Mem0 v2.2.1 Dream + ADD-only architecture + arXiv 2605.27366 utility-aware eviction + Zep/Graphiti bi-temporal edges (pattern only, no code copied). 77 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0416---2026-10-25)
25
26
  > **What's new in 0.4.15** — **content-hash dedup + bi-temporal validity window + evidence chain + `@agent_memory` decorator** (audit 2026-10-18, weekly ecosystem sync). Three new columns on `memories`: `content_hash` (SHA-256 of normalised text, indexed), `valid_at` (epoch the memory becomes active), `invalid_at` (epoch it expires — NULL means still open-ended). The schema migration bumps `SCHEMA_VERSION` 11 → 12 with an idempotent backfill so legacy DBs upgrade in-place. New `upsert_memory(valid_at=, invalid_at=, dedup=)` parameters let callers schedule future-dated or expiring memories and opt out of content-hash dedup for episodic event streams. `recall()` and `recall_hybrid()` now accept an optional `as_of_ts` (float epoch or ISO-8601) — when set, both methods drop memories whose `valid_at > as_of` or `invalid_at <= as_of`, complementing the existing `recall_as_of()` supersession-chain walk. Every recall hit now carries an `evidence: [{field, value, weight}]` array — BM25 / semantic / entity / rrf_total / high_importance channels are exposed individually so a UI can render "this hit because ... [bm25 1.0] [entity 0.7]" instead of a black-box score (Cognee 1.0 / Mem0 v2 evidence-citation pattern, zero-dep). New `loop_memory.decorators` exposes `@agent_memory(store, agent_id=, limit=)` — a one-line wrapper that recalls the top-k relevant memories before the body runs and writes a fresh `kind="interaction"` memory after. Strings returned from the wrapped function are stored verbatim; dict/list returns are JSON-serialised; the recall list is injected as a `memory_context` keyword when the function declares it, otherwise attached to dict results under `memories`. Sister `remember(store, text, ...)` helper for seeding. The BM25 `_like_fallback` now falls through to per-token OR-LIKE when the whole-string match returns nothing — natural-language questions like "What is the deploy process?" now hit stored facts even when FTS5 trigram can't match quoted exact phrases. Source: Cognee 1.0 ECL pipeline + Mem0 v2 hybrid fusion + Zep/Graphiti bi-temporal validity fields + Letta block-memory (pattern only, no code copied). 59 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0415---2026-10-18)
26
27
  > **What's new in 0.4.14** — **`loop-memory doctor` deep store-health check** (audit 2026-10-12). Ten diagnostic checks — `db_exists`, `db_integrity`, `schema_version`, `audit_triggers`, `audit_log_writable`, `audit_log_size`, `hooks_installed`, `version_consistent`, `wiki_no_orphans`, `recall_paths_works` — report `ok`/`info`/`warn`/`error` for the live `MemoryStore`. `--fix` auto-repairs the `audit_triggers` check. `--json` emits a stable shape for CI. `--list` prints the check function names without touching the store. `--verbose` shows `info`-level checks. Exit code `1` on any `error` status, `0` otherwise. Source: `cortexkit/magic-context doctor` auto-diagnostic pattern (MIT, 2026-10-12) + `dsh-memento` write-approval-gate pattern (Apache-2.0, 2026-10-12). Stdlib-only — we re-implement from scratch against our own `MemoryStore`. Also fixed a Python 3.14 bug: chained `store._conn().__enter__()` immediately triggers the contextmanager's `finally` and closes the connection mid-query — both call sites in `doctor.py` now use explicit `with` form. 27 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0414---2026-10-12)
27
28
 
@@ -202,6 +203,10 @@ loop-memory serve --port 7767 # open http://127.0.0.1:7767
202
203
  # 3. Make it run on a timer
203
204
  # (see docs/auto-capture.md for launchd / systemd / cron snippets)
204
205
  loop-memory consolidate # rescore + GC + dedupe
206
+
207
+ # 4. Optional: sleep-time consolidation (soft expiry, never deletes)
208
+ loop-memory dream # dry run -- what would expire / merge
209
+ loop-memory dream --apply # write it (soft-expire + supersede)
205
210
  ```
206
211
 
207
212
  ---
@@ -403,6 +408,46 @@ User signals close the loop:
403
408
  - Every `recall()` / search bumps `recall_count` on the returned
404
409
  rows so the next Stage-1 ranks them higher.
405
410
 
411
+ ### Dream: sleep-time consolidation (0.4.16)
412
+
413
+ `loop-memory dream [--apply]` is the **non-destructive** counterpart to
414
+ `loop-memory cognitive-sleep --apply`:
415
+
416
+ | | `cognitive-sleep --apply` | `dream --apply` |
417
+ |---|---|---|
418
+ | forgets a stale row | `DELETE` — the row is gone | **soft expiry** — `invalid_at` is stamped, the row and its history stay |
419
+ | decides by age alone | yes | no — **utility** decides |
420
+ | near-duplicates | merged | merged into a **supersession chain** |
421
+ | reversible | no | yes (`revive_memory()` / `recall --as-of`) |
422
+
423
+ The utility blend is `0.45 × importance + 0.30 × usage + 0.25 × recency`,
424
+ so a rarely-stated-but-constantly-recalled fact is *protected* while a
425
+ never-used stale row is expired. Age alone never decides — that is the
426
+ whole point of "In Defense of Forgotten" (arXiv 2605.27366).
427
+
428
+ The pass is a **dry run by default**: `--apply` (CLI) or `"apply": true`
429
+ (HTTP) is what actually writes. A dry run changes **no memory data** --
430
+ no expiry, no supersession, no rescore -- it only appends a
431
+ `cognitive_audit` row per proposed action (`action="suggest"`), which
432
+ is the reviewable plan the dashboard shows before you commit.
433
+
434
+ ```bash
435
+ loop-memory dream --json # machine-readable dry run
436
+ loop-memory dream --apply --ttl-days 60 # tighter horizon
437
+ loop-memory dream --apply --utility 0.2 # expire more aggressively
438
+ curl -X POST http://127.0.0.1:7767/api/v1/dream -d '{"apply": true}'
439
+ ```
440
+
441
+ After a dream pass, ask for the *current* truth explicitly:
442
+
443
+ ```bash
444
+ loop-memory recall "editor preference" --latest-only
445
+ ```
446
+
447
+ `--latest-only` (Mem0 v2 `latest_only` parity) drops rows outside their
448
+ validity window at *now*, collapses a subject-key slot to its newest live
449
+ row, and collapses near-duplicate paraphrases to the newest one.
450
+
406
451
  ### Cognitive sleep (v7)
407
452
 
408
453
  `loop-memory cognitive-sleep [--apply]` runs an **auditable cleanup
@@ -559,7 +604,7 @@ loop_memory/
559
604
  ingest/ # Codex / Claude / Hermes / OpenClaw / generic loaders
560
605
  wiki/ # distillation, classifier, scope auto-promotion
561
606
  graph/ # entity extraction + knowledge-graph build
562
- jobs/ # consolidate / evolve / cognitive-sleep / scheduler / contradiction / graph
607
+ jobs/ # consolidate / evolve / cognitive-sleep / dream / scheduler / contradiction / graph
563
608
  llm/ # provider protocol + OpenAI / Anthropic / Ollama / rule-based
564
609
  backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
565
610
  storage/ # SQLite-backed MemoryStore + migrations
@@ -78,6 +78,8 @@ def run_recall(args) -> int:
78
78
  validity_at: float | None = None
79
79
  max_chars: int | None = None
80
80
  level = 1
81
+ # Audit 2026-10-25 (loop-memory 0.4.16) -- latest-only mode.
82
+ latest_only = False
81
83
  qargs: list[str] = []
82
84
  i = 0
83
85
  while i < len(args):
@@ -85,6 +87,12 @@ def run_recall(args) -> int:
85
87
  if a == "--verbose":
86
88
  verbose = True
87
89
  i += 1
90
+ elif a == "--latest-only":
91
+ # Audit 2026-10-25: Mem0 v2 parity -- show one answer per
92
+ # logical fact (drops rows outside their validity window
93
+ # and collapses subject-key / near-duplicate versions).
94
+ latest_only = True
95
+ i += 1
88
96
  elif a == "--outline":
89
97
  # Audit 2026-09-13 (tigerless-labs/agent-memory v0.3.0) —
90
98
  # cheap L0 list instead of full text. Sets a flag and
@@ -162,7 +170,7 @@ def run_recall(args) -> int:
162
170
  qargs.append(a)
163
171
  i += 1
164
172
  if not qargs:
165
- return die("usage: loop-memory recall <query> [--verbose] [--outline] [--as-of <ISO|epoch>] [--limit N] [--max-chars N] [--level 0|1|2]")
173
+ return die("usage: loop-memory recall <query> [--verbose] [--outline] [--as-of <ISO|epoch>] [--latest-only] [--limit N] [--max-chars N] [--level 0|1|2]")
166
174
  store = MemoryStore(default_db_path())
167
175
  query = " ".join(qargs)
168
176
  if as_of is not None:
@@ -180,7 +188,9 @@ def run_recall(args) -> int:
180
188
  # to recall() so the new bi-temporal window
181
189
  # filter applies. ``None`` keeps the legacy
182
190
  # "everything" semantics.
183
- as_of_ts=validity_at)
191
+ as_of_ts=validity_at,
192
+ # Audit 2026-10-25: one answer per fact.
193
+ latest_only=latest_only)
184
194
  has = False
185
195
  if r["wiki"]:
186
196
  has = True
@@ -7,7 +7,7 @@ import os
7
7
  import urllib.error
8
8
  import urllib.request
9
9
 
10
- from .._common import DEFAULT_DB, die, parse_int_flag
10
+ from .._common import DEFAULT_DB, default_db_path, die, parse_int_flag
11
11
 
12
12
 
13
13
  def run_ingest(args) -> int:
@@ -115,6 +115,99 @@ def run_rescore(args) -> int:
115
115
  return 0
116
116
 
117
117
 
118
+ def run_dream(args) -> int:
119
+ """loop-memory dream [--apply] [--json] [tuning flags]
120
+
121
+ The sleep-time consolidation pass (audit 2026-10-25, loop-memory
122
+ 0.4.16). Runs a **dry sweep** by default and prints what it
123
+ *would* do; pass ``--apply`` to actually soft-expire stale rows and
124
+ merge near-duplicates.
125
+
126
+ Unlike ``loop-memory consolidate`` (which hard-deletes rows past
127
+ their TTL), the dream pass never deletes: it stamps ``invalid_at``
128
+ so the fact leaves the current view while its history stays
129
+ queryable through ``loop-memory recall --as-of``.
130
+ """
131
+ import sys
132
+
133
+ from ...jobs.dream import dream as dream_job
134
+ from ...storage.sqlite_store import MemoryStore
135
+
136
+ args = list(args)
137
+ apply_changes = False
138
+ as_json = False
139
+ half_life_days = 30.0
140
+ ttl_days = 90.0
141
+ utility_threshold = 0.30
142
+ similarity = 0.92
143
+ limit = 2000
144
+ i = 0
145
+ while i < len(args):
146
+ a = args[i]
147
+ if a == "--apply":
148
+ apply_changes = True
149
+ i += 1
150
+ elif a == "--json":
151
+ as_json = True
152
+ i += 1
153
+ elif a in ("--half-life", "--ttl-days", "--utility", "--similarity", "--limit"):
154
+ if i + 1 >= len(args):
155
+ return die(f"{a} needs a value")
156
+ raw = args[i + 1]
157
+ try:
158
+ if a == "--limit":
159
+ limit = int(raw)
160
+ else:
161
+ value = float(raw)
162
+ except ValueError:
163
+ return die(f"{a} must be a number, got {raw!r}")
164
+ if a == "--half-life":
165
+ half_life_days = value
166
+ elif a == "--ttl-days":
167
+ ttl_days = value
168
+ elif a == "--utility":
169
+ utility_threshold = value
170
+ else:
171
+ similarity = value
172
+ i += 2
173
+ elif a.startswith("--"):
174
+ return die(f"unknown flag: {a}")
175
+ else:
176
+ return die(f"unexpected argument: {a!r}")
177
+
178
+ # ``default_db_path()`` (not the import-time constant) so a host
179
+ # that changes $LOOP_MEMORY_DB after import is honoured.
180
+ store = MemoryStore(default_db_path())
181
+ report = dream_job(
182
+ store,
183
+ apply=apply_changes,
184
+ half_life_days=half_life_days,
185
+ ttl_days=ttl_days,
186
+ utility_threshold=utility_threshold,
187
+ similarity=similarity,
188
+ limit=limit,
189
+ )
190
+ if as_json:
191
+ print(json.dumps(report.to_dict(), indent=2, ensure_ascii=False))
192
+ return 0
193
+
194
+ mode = "applied" if apply_changes else "dry-run"
195
+ print(
196
+ f"dream ({mode}) — considered {report.considered}, "
197
+ f"expired {report.expired}, kept {report.kept}, "
198
+ f"superseded {report.superseded} in {report.elapsed_ms:.0f} ms"
199
+ )
200
+ if not apply_changes and (report.expired or report.superseded):
201
+ print(" (dry run — re-run with --apply to write these changes)")
202
+ for action in report.actions:
203
+ if action.kind == "keep":
204
+ continue
205
+ marker = "✔" if action.action == "applied" else "•"
206
+ print(f" {marker} {action.kind}: {action.target_text[:72]}")
207
+ print(f" {action.reason}")
208
+ return 0
209
+
210
+
118
211
  def run_consolidate_now(_args) -> int:
119
212
  """Ask the running server to trigger a consolidation pass right now."""
120
213
  import json as _json
@@ -15,6 +15,7 @@ Usage:
15
15
  loop-memory inject [query] # dump long-term context block (for SessionStart hooks)
16
16
  loop-memory install-hooks # auto-write MCP + SessionStart hooks for known clients
17
17
  loop-memory consolidate-now # ask the running server to trigger a pass right now
18
+ loop-memory dream [--apply] # sleep-time consolidation: utility-aware soft expiry + dedupe
18
19
  loop-memory export # legacy markdown export (no positional path)
19
20
  loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md (≤ max-chars bytes)
20
21
  loop-memory rules [--agent codex|claude|hermes|openclaw] [--write] [--force]
@@ -94,6 +95,9 @@ COMMANDS = {
94
95
  "ingest": write_cmd.run_ingest,
95
96
  "consolidate": write_cmd.run_consolidate,
96
97
  "consolidate-now": write_cmd.run_consolidate_now,
98
+ # Audit 2026-10-25 (loop-memory 0.4.16) -- sleep-time "dream"
99
+ # consolidation: utility-aware soft expiry + near-duplicate merge.
100
+ "dream": write_cmd.run_dream,
97
101
  "export": _run_export,
98
102
  "digest": read_cmd.run_digest,
99
103
  "ask": read_cmd.run_ask,
@@ -147,6 +151,7 @@ COMMAND_HELP: dict[str, str] = {
147
151
  "chat": "loop-memory chat # REPL with echo LLM.",
148
152
  "cognitive-sleep": "loop-memory cognitive-sleep [--apply] # dry-run / apply cognitive sweep.",
149
153
  "consolidate": "loop-memory consolidate # rescore + GC + dedupe.",
154
+ "dream": "loop-memory dream [--apply] [--json] [--ttl-days D] [--utility T] # sleep-time consolidation: utility-aware soft expiry + near-duplicate merge (audit 2026-10-25). Dry run unless --apply.",
150
155
  "consolidate-now": "loop-memory consolidate-now # ask the running server to trigger a pass right now.",
151
156
  "digest": "loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md.",
152
157
  "doctor": "loop-memory doctor [--json] [--verbose] [--fix] [--db PATH] # deep store-health check (db integrity + audit triggers + hooks + version + wiki orphans). Auto-repair with --fix (audit 2026-10-12; pattern adopted from cortexkit/magic-context + dsh-memento).",
@@ -164,7 +169,7 @@ COMMAND_HELP: dict[str, str] = {
164
169
  "install-hooks": "loop-memory install-hooks # auto-write MCP + SessionStart hooks for known clients.",
165
170
  "mcp": "loop-memory mcp # stdio MCP server (for codex/claude/hermes). Requires ``[serve]`` for HTTP transport.",
166
171
  "openclaw-setup": "loop-memory openclaw-setup # install the openclaw/clawx auto-ingest watcher (launchd on macOS).",
167
- "recall": "loop-memory recall <text> [--as-of <ISO|epoch>] [--max-chars N] [--level 0|1|2] # show top memories (--as-of = bi-temporal recall; --max-chars = total payload cap; --level 0 = L0 abstract ladder rung; audit 2026-10-05).",
172
+ "recall": "loop-memory recall <text> [--as-of <ISO|epoch>] [--latest-only] [--max-chars N] [--level 0|1|2] # show top memories (--as-of = bi-temporal recall; --latest-only = one answer per fact; --max-chars = total payload cap; --level 0 = L0 abstract ladder rung; audit 2026-10-05).",
168
173
  "rescore": "loop-memory rescore [--half-life 30] # recompute memory scores.",
169
174
  "serve": "loop-memory serve [--port 7767] [--no-browser] # start the local web UI. Requires the ``[serve]`` extra (fastapi + uvicorn).",
170
175
  "stats": "loop-memory stats # counters.",