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.
- {loop_memory-0.4.8 → loop_memory-0.4.9}/PKG-INFO +3 -1
- {loop_memory-0.4.8 → loop_memory-0.4.9}/README.md +2 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/cognitive.py +33 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/read.py +34 -6
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/main.py +5 -1
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/system.py +55 -3
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/sqlite_store.py +462 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/PKG-INFO +3 -1
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/SOURCES.txt +5 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/pyproject.toml +1 -1
- loop_memory-0.4.9/tests/test_cli_export_okf.py +89 -0
- loop_memory-0.4.9/tests/test_cli_recall_as_of.py +90 -0
- loop_memory-0.4.9/tests/test_export_okf.py +162 -0
- loop_memory-0.4.9/tests/test_recall_as_of.py +154 -0
- loop_memory-0.4.9/tests/test_routes_audit_2026_09_20.py +119 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/LICENSE +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/diag.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/rules.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/serve.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/cognitive.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/openai_adapter.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/llm/providers.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/sdk.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/sdk_extensions.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/security/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/cognitive.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/storage/snapshot.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/setup.cfg +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agent_memory_sdk.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_agents_init.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_audit_supersede.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_init.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_memory_stats.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_recall_paths.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_recall_verbose.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_rules.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_snapshot.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_graph.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_providers.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_loop.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_memory_stats.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_package_version.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_recall_paths.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_recall_provenance.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_repo_framing.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_routes_audit_2026_09_13.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_short_query.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_snapshot.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_store.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_supersession_chain.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_universal_memory.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.8 → loop_memory-0.4.9}/tests/test_wiki_export_escape.py +0 -0
- {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.
|
|
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).
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
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":
|
|
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",
|