loop-memory 0.4.8__tar.gz → 0.4.9__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 (149) hide show
  1. {loop_memory-0.4.8 → loop_memory-0.4.9}/PKG-INFO +3 -1
  2. {loop_memory-0.4.8 → loop_memory-0.4.9}/README.md +2 -0
  3. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/cognitive.py +33 -0
  4. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/read.py +34 -6
  5. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/main.py +5 -1
  6. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/system.py +55 -3
  7. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/sqlite_store.py +462 -0
  8. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/PKG-INFO +3 -1
  9. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/SOURCES.txt +5 -0
  10. {loop_memory-0.4.8 → loop_memory-0.4.9}/pyproject.toml +1 -1
  11. loop_memory-0.4.9/tests/test_cli_export_okf.py +89 -0
  12. loop_memory-0.4.9/tests/test_cli_recall_as_of.py +90 -0
  13. loop_memory-0.4.9/tests/test_export_okf.py +162 -0
  14. loop_memory-0.4.9/tests/test_recall_as_of.py +154 -0
  15. loop_memory-0.4.9/tests/test_routes_audit_2026_09_20.py +119 -0
  16. {loop_memory-0.4.8 → loop_memory-0.4.9}/LICENSE +0 -0
  17. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/__init__.py +0 -0
  18. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/__init__.py +0 -0
  19. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/embedding.py +0 -0
  20. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/sentence_embedder.py +0 -0
  21. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/vector_store.py +0 -0
  22. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/__init__.py +0 -0
  23. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/_common.py +0 -0
  24. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/__init__.py +0 -0
  25. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/diag.py +0 -0
  26. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/graph.py +0 -0
  27. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/hooks.py +0 -0
  28. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/rules.py +0 -0
  29. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/serve.py +0 -0
  30. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/write.py +0 -0
  31. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/__init__.py +0 -0
  32. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/loop.py +0 -0
  33. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/reflect.py +0 -0
  34. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/examples/__init__.py +0 -0
  35. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/examples/demo.py +0 -0
  36. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/export/__init__.py +0 -0
  37. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/export/memory_md.py +0 -0
  38. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/__init__.py +0 -0
  39. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/build.py +0 -0
  40. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/extract.py +0 -0
  41. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/__init__.py +0 -0
  42. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/loader.py +0 -0
  43. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/pipeline.py +0 -0
  44. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/__init__.py +0 -0
  45. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/cognitive.py +0 -0
  46. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/compact.py +0 -0
  47. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/consolidate.py +0 -0
  48. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/contradiction.py +0 -0
  49. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/evolution.py +0 -0
  50. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/graph.py +0 -0
  51. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/llm_compact_pass.py +0 -0
  52. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/llm_consolidate.py +0 -0
  53. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/scheduler.py +0 -0
  54. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/__init__.py +0 -0
  55. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/base.py +0 -0
  56. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/openai_adapter.py +0 -0
  57. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/providers.py +0 -0
  58. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/mcp/__init__.py +0 -0
  59. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/memory/__init__.py +0 -0
  60. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/memory/types.py +0 -0
  61. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/__init__.py +0 -0
  62. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/private.py +0 -0
  63. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/redact.py +0 -0
  64. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/py.typed +0 -0
  65. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/sdk.py +0 -0
  66. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/sdk_extensions.py +0 -0
  67. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/security/__init__.py +0 -0
  68. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/security/secrets.py +0 -0
  69. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/__init__.py +0 -0
  70. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/app.py +0 -0
  71. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/handlers.py +0 -0
  72. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/_shared.py +0 -0
  73. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/admin.py +0 -0
  74. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/cognitive.py +0 -0
  75. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/export.py +0 -0
  76. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/graph.py +0 -0
  77. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/insights.py +0 -0
  78. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/memories.py +0 -0
  79. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/sessions.py +0 -0
  80. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/wiki.py +0 -0
  81. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/static/__init__.py +0 -0
  82. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/static/index.html +0 -0
  83. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/watcher.py +0 -0
  84. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/__init__.py +0 -0
  85. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/retrieval.py +0 -0
  86. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/snapshot.py +0 -0
  87. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/__init__.py +0 -0
  88. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/backfill.py +0 -0
  89. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/classifier.py +0 -0
  90. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/prompts.py +0 -0
  91. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/scope.py +0 -0
  92. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/dependency_links.txt +0 -0
  93. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/entry_points.txt +0 -0
  94. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/requires.txt +0 -0
  95. {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/top_level.txt +0 -0
  96. {loop_memory-0.4.8 → loop_memory-0.4.9}/setup.cfg +0 -0
  97. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_admin_ingest_route.py +0 -0
  98. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agent_memory_api.py +0 -0
  99. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agent_memory_sdk.py +0 -0
  100. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agents_init.py +0 -0
  101. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_auth_token_rotate.py +0 -0
  102. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_audit_supersede.py +0 -0
  103. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_init.py +0 -0
  104. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_memory_stats.py +0 -0
  105. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_recall_paths.py +0 -0
  106. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_recall_verbose.py +0 -0
  107. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_rules.py +0 -0
  108. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_snapshot.py +0 -0
  109. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_v7.py +0 -0
  110. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_version.py +0 -0
  111. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_contradictions.py +0 -0
  112. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_evolution.py +0 -0
  113. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_evolution_quality.py +0 -0
  114. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_expanduser.py +0 -0
  115. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_export_ask.py +0 -0
  116. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_graph.py +0 -0
  117. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_ingest.py +0 -0
  118. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_consolidator.py +0 -0
  119. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_fingerprint_not_persisted.py +0 -0
  120. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_providers.py +0 -0
  121. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_test_endpoint.py +0 -0
  122. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_loop.py +0 -0
  123. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_mcp.py +0 -0
  124. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_memories_pagination.py +0 -0
  125. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_memory_stats.py +0 -0
  126. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_openclaw_loader.py +0 -0
  127. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_package_version.py +0 -0
  128. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_recall_paths.py +0 -0
  129. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_recall_provenance.py +0 -0
  130. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_reflection.py +0 -0
  131. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_repo_framing.py +0 -0
  132. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_routes_audit_2026_09_13.py +0 -0
  133. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_score_api.py +0 -0
  134. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_scoring_v2.py +0 -0
  135. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_secrets.py +0 -0
  136. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_serve_app.py +0 -0
  137. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_serve_handlers.py +0 -0
  138. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_session_order.py +0 -0
  139. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_short_query.py +0 -0
  140. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_snapshot.py +0 -0
  141. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_store.py +0 -0
  142. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_summarization.py +0 -0
  143. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_supersession_chain.py +0 -0
  144. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_universal_memory.py +0 -0
  145. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_vector_store.py +0 -0
  146. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_watcher.py +0 -0
  147. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_wiki_classifier.py +0 -0
  148. {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_wiki_export_escape.py +0 -0
  149. {loop_memory-0.4.8 → loop_memory-0.4.9}/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.8
3
+ Version: 0.4.9
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,8 @@ Dynamic: license-file
66
66
 
67
67
  ---
68
68
 
69
+ > **What's new in 0.4.9** — **OKF v0.2 export** + **bi-temporal `as_of` recall**. `loop-memory export-okf <dir>` writes an [Open Knowledge Format](https://github.com/okf/SPEC) v0.2 bundle — one `.md` file per wiki page with YAML frontmatter + an `index.md` index — so a Loop Memory store can be handed to any OKF-aware tool (Grep, Obsidian, akitaonrails/ai-memory, okf-agent-memory, ...) without re-parsing the body (audit 2026-09-20, Google OKF v0.2 spec + akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory, all Apache-2.0/MIT, 2026-09-01 → 2026-09-06). `loop-memory recall <q> --as-of <ISO|epoch>` answers the bi-temporal question "what did we know about this query at that moment?" — a memory written after the moment is invisible, and a superseded memory walks its chain to find whether the invalidation had happened by then (audit 2026-09-20, loomcycle v1.33-v1.49 RFCs BL/BS/BU/BV/BW, Apache-2.0). Both shapes ship with HTTP routes (`POST /api/export/okf`, `GET /api/recall?as_of=...`). 34 new regression cases pin both shapes. [Full CHANGELOG →](./CHANGELOG.md#049---2026-09-20)
70
+
69
71
  > **What's new in 0.4.8** — **L0 outline recall** +
70
72
  > **per-agent identity bootstrap**. `loop-memory recall-paths <q>`
71
73
  > (and the new `recall --outline` flag) returns only `id` +
@@ -22,6 +22,8 @@
22
22
 
23
23
  ---
24
24
 
25
+ > **What's new in 0.4.9** — **OKF v0.2 export** + **bi-temporal `as_of` recall**. `loop-memory export-okf <dir>` writes an [Open Knowledge Format](https://github.com/okf/SPEC) v0.2 bundle — one `.md` file per wiki page with YAML frontmatter + an `index.md` index — so a Loop Memory store can be handed to any OKF-aware tool (Grep, Obsidian, akitaonrails/ai-memory, okf-agent-memory, ...) without re-parsing the body (audit 2026-09-20, Google OKF v0.2 spec + akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory, all Apache-2.0/MIT, 2026-09-01 → 2026-09-06). `loop-memory recall <q> --as-of <ISO|epoch>` answers the bi-temporal question "what did we know about this query at that moment?" — a memory written after the moment is invisible, and a superseded memory walks its chain to find whether the invalidation had happened by then (audit 2026-09-20, loomcycle v1.33-v1.49 RFCs BL/BS/BU/BV/BW, Apache-2.0). Both shapes ship with HTTP routes (`POST /api/export/okf`, `GET /api/recall?as_of=...`). 34 new regression cases pin both shapes. [Full CHANGELOG →](./CHANGELOG.md#049---2026-09-20)
26
+
25
27
  > **What's new in 0.4.8** — **L0 outline recall** +
26
28
  > **per-agent identity bootstrap**. `loop-memory recall-paths <q>`
27
29
  > (and the new `recall --outline` flag) returns only `id` +
@@ -108,6 +108,17 @@ def _build_parser() -> argparse.ArgumentParser:
108
108
  rs.add_argument("in_path",
109
109
  help="Source .memory.sqlite file")
110
110
 
111
+ # Audit 2026-09-20: OKF v0.2 bundle export
112
+ # (akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory +
113
+ # Google OKF v0.2 spec).
114
+ eo = sub.add_parser(
115
+ "export-okf",
116
+ help="Export wiki pages as an OKF v0.2 markdown bundle")
117
+ eo.add_argument("out_dir",
118
+ help="Destination directory for the bundle")
119
+ eo.add_argument("--scope", default=None,
120
+ help="Filter: 'global' or a single source token (e.g. codex)")
121
+
111
122
  return p
112
123
 
113
124
 
@@ -342,3 +353,25 @@ def run_restore(args: list) -> int:
342
353
  return _emit(_restore(s, ns.in_path))
343
354
  except (FileNotFoundError, ValueError) as e:
344
355
  return _emit({"error": str(e)})
356
+
357
+
358
+ def run_export_okf(args) -> int:
359
+ """``loop-memory export-okf <out_dir> [--scope SCOPE]``
360
+
361
+ Audit 2026-09-20: OKF v0.2 bundle export
362
+ (akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory +
363
+ Google OKF v0.2 spec). Returns a summary dict with the bundle
364
+ root + per-page list so the caller can confirm the bundle is
365
+ sane. ``--scope`` accepts ``global`` (only globally-scoped
366
+ pages) or a single source token (only pages whose scope list
367
+ contains the token); omit the flag to emit every page.
368
+ """
369
+ import argparse as _ap
370
+ p = _ap.ArgumentParser(prog="loop-memory export-okf")
371
+ p.add_argument("out_dir")
372
+ p.add_argument("--scope", default=None)
373
+ p.add_argument("--db", default=os.environ.get("LOOP_MEMORY_DB", DEFAULT_DB))
374
+ ns = p.parse_args(args)
375
+ s = MemoryStore(ns.db)
376
+ r = s.export_okf(ns.out_dir, scope_filter=ns.scope)
377
+ return _emit(r)
@@ -51,13 +51,17 @@ def run_stats(_args) -> int:
51
51
 
52
52
 
53
53
  def run_recall(args) -> int:
54
- """Run ``loop-memory recall <query> [--verbose] [--limit N]``.
54
+ """Run ``loop-memory recall <query> [--verbose] [--outline] [--as-of T] [--limit N]``.
55
55
 
56
56
  With ``--verbose`` each memory hit is annotated with the
57
57
  ``why: [...]`` provenance labels produced by ``MemoryStore.recall``
58
- (audit 2026-08-30, agentmemory v1.2.0 pattern). The wiki and
59
- entity sections are unchanged — only the raw-memory block grows
60
- the new line.
58
+ (audit 2026-08-30, agentmemory v1.2.0 pattern). ``--outline``
59
+ switches to the L0 list (audit 2026-09-13, tigerless-labs/
60
+ agent-memory v0.3.0). ``--as-of <float-or-ISO>`` enables
61
+ bi-temporal recall (audit 2026-09-20, loomcycle v1.33+) so a
62
+ caller can ask "what did we know about this query at that
63
+ moment?". The wiki and entity sections are unchanged — only
64
+ the raw-memory block grows the new line.
61
65
  """
62
66
  from ...storage.sqlite_store import MemoryStore
63
67
  if not args:
@@ -68,6 +72,7 @@ def run_recall(args) -> int:
68
72
  verbose = False
69
73
  outline = False
70
74
  limit = 10
75
+ as_of: float | None = None
71
76
  qargs: list[str] = []
72
77
  i = 0
73
78
  while i < len(args):
@@ -81,6 +86,24 @@ def run_recall(args) -> int:
81
86
  # routes the call to ``recall_paths`` below.
82
87
  outline = True
83
88
  i += 1
89
+ elif a == "--as-of" and i + 1 < len(args):
90
+ # Audit 2026-09-20 (loomcycle v1.33+) — bi-temporal
91
+ # recall: "what did we know about this query at that
92
+ # moment?". Accepts float epoch OR ISO-8601 string.
93
+ raw = args[i + 1].strip()
94
+ try:
95
+ as_of = float(raw)
96
+ except ValueError:
97
+ try:
98
+ from datetime import datetime
99
+ as_of = datetime.fromisoformat(
100
+ raw.replace("Z", "+00:00")
101
+ ).timestamp()
102
+ except Exception:
103
+ return die(
104
+ f"--as-of must be float epoch or ISO-8601; got {raw!r}"
105
+ )
106
+ i += 2
84
107
  elif a == "--limit" and i + 1 < len(args):
85
108
  try:
86
109
  limit = int(args[i + 1])
@@ -93,10 +116,15 @@ def run_recall(args) -> int:
93
116
  qargs.append(a)
94
117
  i += 1
95
118
  if not qargs:
96
- return die("usage: loop-memory recall <query> [--verbose] [--outline] [--limit N]")
119
+ return die("usage: loop-memory recall <query> [--verbose] [--outline] [--as-of <ISO|epoch>] [--limit N]")
97
120
  store = MemoryStore(default_db_path())
98
121
  query = " ".join(qargs)
99
- if outline:
122
+ if as_of is not None:
123
+ try:
124
+ r = store.recall_as_of(query, as_of, limit=limit)
125
+ except ValueError as e:
126
+ return die(str(e))
127
+ elif outline:
100
128
  r = store.recall_paths(query, limit=limit)
101
129
  else:
102
130
  r = store.recall(query, limit=limit)
@@ -125,6 +125,9 @@ COMMANDS = {
125
125
  # Audit 2026-09-13 — L0 outline recall + per-agent identity bootstrap.
126
126
  "recall-paths": read_cmd.run_recall_paths,
127
127
  "init": hooks_cmd.run_init,
128
+ # Audit 2026-09-20 — OKF v0.2 export (akitaonrails/ai-memory 2.0
129
+ # + okf-agent-memory + Google OKF spec).
130
+ "export-okf": cognitive_cmd.run_export_okf,
128
131
  "rules": rules_cmd.run_rules,
129
132
  "version": _run_version,
130
133
  }
@@ -158,7 +161,7 @@ COMMAND_HELP: dict[str, str] = {
158
161
  "install-hooks": "loop-memory install-hooks # auto-write MCP + SessionStart hooks for known clients.",
159
162
  "mcp": "loop-memory mcp # stdio MCP server (for codex/claude/hermes). Requires ``[serve]`` for HTTP transport.",
160
163
  "openclaw-setup": "loop-memory openclaw-setup # install the openclaw/clawx auto-ingest watcher (launchd on macOS).",
161
- "recall": "loop-memory recall <text> # show top memories.",
164
+ "recall": "loop-memory recall <text> [--as-of <ISO|epoch>] # show top memories (--as-of = bi-temporal recall).",
162
165
  "rescore": "loop-memory rescore [--half-life 30] # recompute memory scores.",
163
166
  "serve": "loop-memory serve [--port 7767] [--no-browser] # start the local web UI. Requires the ``[serve]`` extra (fastapi + uvicorn).",
164
167
  "stats": "loop-memory stats # counters.",
@@ -168,6 +171,7 @@ COMMAND_HELP: dict[str, str] = {
168
171
  "wiki-reclassify-legacy": "loop-memory wiki-reclassify-legacy # back-fill scope + scope_filter for H4+H5 pages.",
169
172
  "memory-stats": "loop-memory memory-stats <id> [--prefix] # per-memory lifetime stats (audit 2026-09-06, agentmemory v1.3.0).",
170
173
  "snapshot": "loop-memory snapshot <out.memory.sqlite> # write a portable SQLite snapshot (audit 2026-09-06, codexa-memory v0.2.0).",
174
+ "export-okf": "loop-memory export-okf <out_dir> [--scope S] # write an OKF v0.2 bundle (audit 2026-09-20).",
171
175
  "restore": "loop-memory restore <in.memory.sqlite> # re-hydrate a portable SQLite snapshot.",
172
176
  # Audit 2026-09-13 — L0 outline recall (tigerless-labs/agent-memory v0.3.0).
173
177
  "recall-paths": "loop-memory recall-paths <query> [--limit N] # cheap L0 outline (id+abstract+score) instead of full text (audit 2026-09-13).",
@@ -350,7 +350,8 @@ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None,
350
350
  @app.get("/api/recall")
351
351
  def recall(query: str, limit: int = 10, include: str = "memories,wiki,entities",
352
352
  bump: int = 1, source: str | None = None,
353
- mode: str = "hybrid", level: int = 1):
353
+ mode: str = "hybrid", level: int = 1,
354
+ as_of: str | None = None):
354
355
  """Unified recall — wiki + memories + entities in one ranked stream.
355
356
 
356
357
  ``mode`` is 'hybrid' (default) for BM25+semantic+entity RRF,
@@ -363,11 +364,31 @@ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None,
363
364
  and memories whose source matches this token are boosted. The
364
365
  dashboard passes the currently-active client (codex/claude/
365
366
  hermes/openclaw); the MCP/CLI tools pass the calling client.
367
+
368
+ ``as_of`` (audit 2026-09-20, loomcycle v1.33+) enables
369
+ bi-temporal recall: a float epoch OR an ISO-8601 string that
370
+ answers "what did we know about this query at that moment?".
371
+ When unset (default) the call returns the latest answer
372
+ (preserves the pre-0.4.9 contract). When set, the call
373
+ routes through ``MemoryStore.recall_as_of``.
366
374
  """
367
375
  wanted = tuple(s.strip() for s in include.split(",") if s.strip())
368
376
  if not wanted:
369
377
  wanted = ("memories", "wiki", "entities")
370
- if mode == "legacy" or not hasattr(store, "recall_hybrid"):
378
+ if as_of is not None and str(as_of).strip():
379
+ try:
380
+ r = store.recall_as_of(
381
+ query,
382
+ as_of,
383
+ limit=limit,
384
+ include=wanted,
385
+ bump_signals=bool(bump),
386
+ source=source,
387
+ )
388
+ recall_mode = "as_of"
389
+ except ValueError as e:
390
+ raise HTTPException(400, str(e))
391
+ elif mode == "legacy" or not hasattr(store, "recall_hybrid"):
371
392
  r = store.recall(
372
393
  query,
373
394
  limit=limit,
@@ -375,23 +396,54 @@ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None,
375
396
  bump_signals=bool(bump),
376
397
  source=source,
377
398
  )
399
+ recall_mode = mode
378
400
  else:
379
401
  r = store.recall_hybrid(
380
402
  query, limit=limit, include=wanted,
381
403
  bump_signals=bool(bump), source=source, level=level,
382
404
  )
405
+ recall_mode = mode
383
406
  return {
384
407
  "query": query,
385
408
  "tokens": r.get("tokens", []),
386
409
  "memories": r.get("memories", []),
387
410
  "wiki": r.get("wiki", []),
388
411
  "entities": r.get("entities", []),
389
- "mode": mode,
412
+ "mode": recall_mode,
390
413
  "source": source,
391
414
  "level": level,
415
+ "as_of": as_of,
392
416
  }
393
417
 
394
418
 
419
+ @app.post("/api/export/okf")
420
+ def export_okf(body: dict):
421
+ """Audit 2026-09-20: OKF v0.2 bundle export.
422
+
423
+ Body shape::
424
+
425
+ {
426
+ "out_dir": "/abs/path/to/out",
427
+ "scope": "global" # optional: "global" or a source token
428
+ }
429
+
430
+ Writes one ``.md`` file per wiki page under
431
+ ``<out_dir>/pages/<slug>.md`` plus an ``index.md`` index.
432
+ See ``MemoryStore.export_okf`` for the field-mapping
433
+ contract.
434
+ """
435
+ out_dir = (body.get("out_dir") or "").strip()
436
+ if not out_dir:
437
+ raise HTTPException(400, "out_dir is required")
438
+ scope = body.get("scope")
439
+ if scope is not None:
440
+ scope = str(scope).strip() or None
441
+ try:
442
+ r = store.export_okf(out_dir, scope_filter=scope)
443
+ except Exception as e:
444
+ raise HTTPException(500, f"export-okf failed: {e}")
445
+ return r
446
+
395
447
  @app.get("/api/recall/outline")
396
448
  def recall_outline(query: str = "", limit: int = 12,
397
449
  include: str = "memories,wiki,entities",