loop-memory 0.4.0__tar.gz → 0.4.1__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 (127) hide show
  1. {loop_memory-0.4.0 → loop_memory-0.4.1}/PKG-INFO +17 -11
  2. {loop_memory-0.4.0 → loop_memory-0.4.1}/README.md +16 -10
  3. loop_memory-0.4.1/loop_memory/cli/main.py +184 -0
  4. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/static/index.html +1 -1
  5. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/PKG-INFO +17 -11
  6. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/SOURCES.txt +2 -0
  7. {loop_memory-0.4.0 → loop_memory-0.4.1}/pyproject.toml +1 -1
  8. loop_memory-0.4.1/tests/test_cli_version.py +130 -0
  9. loop_memory-0.4.1/tests/test_repo_framing.py +198 -0
  10. loop_memory-0.4.0/loop_memory/cli/main.py +0 -115
  11. {loop_memory-0.4.0 → loop_memory-0.4.1}/LICENSE +0 -0
  12. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/__init__.py +0 -0
  13. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/backends/__init__.py +0 -0
  14. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/backends/embedding.py +0 -0
  15. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/backends/sentence_embedder.py +0 -0
  16. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/backends/vector_store.py +0 -0
  17. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/__init__.py +0 -0
  18. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/_common.py +0 -0
  19. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/__init__.py +0 -0
  20. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/cognitive.py +0 -0
  21. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/diag.py +0 -0
  22. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/graph.py +0 -0
  23. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/hooks.py +0 -0
  24. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/read.py +0 -0
  25. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/serve.py +0 -0
  26. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/cli/commands/write.py +0 -0
  27. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/engine/__init__.py +0 -0
  28. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/engine/loop.py +0 -0
  29. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/engine/reflect.py +0 -0
  30. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/examples/__init__.py +0 -0
  31. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/examples/demo.py +0 -0
  32. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/export/__init__.py +0 -0
  33. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/export/memory_md.py +0 -0
  34. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/graph/__init__.py +0 -0
  35. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/graph/build.py +0 -0
  36. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/graph/extract.py +0 -0
  37. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/ingest/__init__.py +0 -0
  38. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/ingest/loader.py +0 -0
  39. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/ingest/pipeline.py +0 -0
  40. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/__init__.py +0 -0
  41. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/cognitive.py +0 -0
  42. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/compact.py +0 -0
  43. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/consolidate.py +0 -0
  44. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/contradiction.py +0 -0
  45. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/evolution.py +0 -0
  46. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/graph.py +0 -0
  47. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/llm_compact_pass.py +0 -0
  48. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/llm_consolidate.py +0 -0
  49. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/jobs/scheduler.py +0 -0
  50. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/llm/__init__.py +0 -0
  51. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/llm/base.py +0 -0
  52. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/llm/openai_adapter.py +0 -0
  53. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/llm/providers.py +0 -0
  54. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/mcp/__init__.py +0 -0
  55. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/memory/__init__.py +0 -0
  56. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/memory/types.py +0 -0
  57. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/privacy/__init__.py +0 -0
  58. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/privacy/private.py +0 -0
  59. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/privacy/redact.py +0 -0
  60. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/py.typed +0 -0
  61. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/sdk.py +0 -0
  62. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/sdk_extensions.py +0 -0
  63. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/security/__init__.py +0 -0
  64. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/security/secrets.py +0 -0
  65. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/__init__.py +0 -0
  66. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/app.py +0 -0
  67. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/handlers.py +0 -0
  68. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/_shared.py +0 -0
  69. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/admin.py +0 -0
  70. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/cognitive.py +0 -0
  71. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/export.py +0 -0
  72. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/graph.py +0 -0
  73. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/insights.py +0 -0
  74. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/memories.py +0 -0
  75. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/sessions.py +0 -0
  76. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/system.py +0 -0
  77. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/routes/wiki.py +0 -0
  78. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/static/__init__.py +0 -0
  79. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/serve/watcher.py +0 -0
  80. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/storage/__init__.py +0 -0
  81. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/storage/retrieval.py +0 -0
  82. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/storage/sqlite_store.py +0 -0
  83. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/wiki/__init__.py +0 -0
  84. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/wiki/backfill.py +0 -0
  85. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/wiki/classifier.py +0 -0
  86. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/wiki/prompts.py +0 -0
  87. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory/wiki/scope.py +0 -0
  88. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/dependency_links.txt +0 -0
  89. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/entry_points.txt +0 -0
  90. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/requires.txt +0 -0
  91. {loop_memory-0.4.0 → loop_memory-0.4.1}/loop_memory.egg-info/top_level.txt +0 -0
  92. {loop_memory-0.4.0 → loop_memory-0.4.1}/setup.cfg +0 -0
  93. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_admin_ingest_route.py +0 -0
  94. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_agent_memory_api.py +0 -0
  95. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_agent_memory_sdk.py +0 -0
  96. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_auth_token_rotate.py +0 -0
  97. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_cli_v7.py +0 -0
  98. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_contradictions.py +0 -0
  99. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_evolution.py +0 -0
  100. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_evolution_quality.py +0 -0
  101. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_expanduser.py +0 -0
  102. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_export_ask.py +0 -0
  103. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_graph.py +0 -0
  104. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_ingest.py +0 -0
  105. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_llm_consolidator.py +0 -0
  106. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_llm_fingerprint_not_persisted.py +0 -0
  107. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_llm_providers.py +0 -0
  108. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_llm_test_endpoint.py +0 -0
  109. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_loop.py +0 -0
  110. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_mcp.py +0 -0
  111. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_memories_pagination.py +0 -0
  112. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_openclaw_loader.py +0 -0
  113. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_reflection.py +0 -0
  114. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_score_api.py +0 -0
  115. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_scoring_v2.py +0 -0
  116. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_secrets.py +0 -0
  117. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_serve_app.py +0 -0
  118. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_serve_handlers.py +0 -0
  119. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_session_order.py +0 -0
  120. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_store.py +0 -0
  121. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_summarization.py +0 -0
  122. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_universal_memory.py +0 -0
  123. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_vector_store.py +0 -0
  124. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_watcher.py +0 -0
  125. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_wiki_classifier.py +0 -0
  126. {loop_memory-0.4.0 → loop_memory-0.4.1}/tests/test_wiki_export_escape.py +0 -0
  127. {loop_memory-0.4.0 → loop_memory-0.4.1}/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.0
3
+ Version: 0.4.1
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
@@ -48,11 +48,14 @@ Dynamic: license-file
48
48
 
49
49
  # Loop Memory
50
50
 
51
- > **A general-purpose memory system for every AI agent you run locally.**
51
+ > **A general-purpose, local-first memory system that closes the agent loop for every AI agent you run.**
52
52
  >
53
- > Auto-captures every Codex / Claude / Hermes / OpenClaw conversation,
54
- > distils them into a tight wiki of stable knowledge, and lets the
55
- > agent recall what matters on demand.
53
+ > Point Loop Memory at any agent's transcript directory and it
54
+ > quietly catches every conversation, scores it, distils the long
55
+ > tail into a tight wiki of stable knowledge, and re-injects the
56
+ > relevant pieces into the next session. Out of the box: hooks for
57
+ > Codex, Claude, Hermes, and OpenClaw; an SDK + generic watcher
58
+ > CLI cover everything else.
56
59
 
57
60
  [![CI](https://img.shields.io/github/actions/workflow/status/smartfind/loop-memory/tests.yml?branch=main&style=flat-square)](https://github.com/smartfind/loop-memory/actions)
58
61
  [![PyPI](https://img.shields.io/pypi/v/loop-memory.svg?style=flat-square)](https://pypi.org/project/loop-memory/)
@@ -66,11 +69,14 @@ Dynamic: license-file
66
69
  ## What it does
67
70
 
68
71
  **Loop Memory** gives every agent you use a single, persistent brain
69
- that outlives any one conversation. Each agent (Codex CLI, Claude
70
- Code, Hermes, OpenClaw / clawx, …) drops its transcripts onto disk;
71
- Loop Memory quietly catches them, scores every fragment by *importance
72
- × recency × usage × feedback*, distils the long tail into a curated
73
- wiki, and re-injects the relevant pieces into the next session.
72
+ that outlives any one conversation. Any agent that drops transcripts
73
+ on disk — Codex CLI, Claude Code, Hermes, OpenClaw / clawx, Aider,
74
+ Cursor, works; the shipped hooks cover the popular ones and a
75
+ generic watcher CLI (`loop-memory hook --source <name> --watch <dir>`)
76
+ accepts anything else. Loop Memory quietly catches every fragment,
77
+ scores it by *importance × recency × usage × feedback*, distils the
78
+ long tail into a curated wiki, and re-injects the relevant pieces
79
+ into the next session.
74
80
 
75
81
  ```mermaid
76
82
  flowchart LR
@@ -142,7 +148,7 @@ following — every other project we looked at lacks at least one:
142
148
 
143
149
  | Capability | **Loop Memory** | Mem0 v3 | Hindsight | OpenViking | A-MEM |
144
150
  | --- | --- | --- | --- | --- | --- |
145
- | Multi-source capture (Codex / Claude / Hermes / OpenClaw) | ✅ out of the box | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
151
+ | Multi-source capture (any agent with on-disk transcripts) | ✅ generic watcher CLI; hooks shipped for Codex / Claude / Hermes / OpenClaw | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
146
152
  | Local-first SQLite (zero external services) | ✅ | ❌ Postgres + Qdrant | ❌ Postgres + Qdrant | ⚠ file-system + cloud | ⚠ ChromaDB |
147
153
  | Hybrid recall: BM25 + semantic + entity (RRF) | ✅ | ✅ | ✅ | ✅ | ⚠ entity-only |
148
154
  | Temporal reasoning in retrieval (boost / suppress by date intent) | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -4,11 +4,14 @@
4
4
 
5
5
  # Loop Memory
6
6
 
7
- > **A general-purpose memory system for every AI agent you run locally.**
7
+ > **A general-purpose, local-first memory system that closes the agent loop for every AI agent you run.**
8
8
  >
9
- > Auto-captures every Codex / Claude / Hermes / OpenClaw conversation,
10
- > distils them into a tight wiki of stable knowledge, and lets the
11
- > agent recall what matters on demand.
9
+ > Point Loop Memory at any agent's transcript directory and it
10
+ > quietly catches every conversation, scores it, distils the long
11
+ > tail into a tight wiki of stable knowledge, and re-injects the
12
+ > relevant pieces into the next session. Out of the box: hooks for
13
+ > Codex, Claude, Hermes, and OpenClaw; an SDK + generic watcher
14
+ > CLI cover everything else.
12
15
 
13
16
  [![CI](https://img.shields.io/github/actions/workflow/status/smartfind/loop-memory/tests.yml?branch=main&style=flat-square)](https://github.com/smartfind/loop-memory/actions)
14
17
  [![PyPI](https://img.shields.io/pypi/v/loop-memory.svg?style=flat-square)](https://pypi.org/project/loop-memory/)
@@ -22,11 +25,14 @@
22
25
  ## What it does
23
26
 
24
27
  **Loop Memory** gives every agent you use a single, persistent brain
25
- that outlives any one conversation. Each agent (Codex CLI, Claude
26
- Code, Hermes, OpenClaw / clawx, …) drops its transcripts onto disk;
27
- Loop Memory quietly catches them, scores every fragment by *importance
28
- × recency × usage × feedback*, distils the long tail into a curated
29
- wiki, and re-injects the relevant pieces into the next session.
28
+ that outlives any one conversation. Any agent that drops transcripts
29
+ on disk — Codex CLI, Claude Code, Hermes, OpenClaw / clawx, Aider,
30
+ Cursor, works; the shipped hooks cover the popular ones and a
31
+ generic watcher CLI (`loop-memory hook --source <name> --watch <dir>`)
32
+ accepts anything else. Loop Memory quietly catches every fragment,
33
+ scores it by *importance × recency × usage × feedback*, distils the
34
+ long tail into a curated wiki, and re-injects the relevant pieces
35
+ into the next session.
30
36
 
31
37
  ```mermaid
32
38
  flowchart LR
@@ -98,7 +104,7 @@ following — every other project we looked at lacks at least one:
98
104
 
99
105
  | Capability | **Loop Memory** | Mem0 v3 | Hindsight | OpenViking | A-MEM |
100
106
  | --- | --- | --- | --- | --- | --- |
101
- | Multi-source capture (Codex / Claude / Hermes / OpenClaw) | ✅ out of the box | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
107
+ | Multi-source capture (any agent with on-disk transcripts) | ✅ generic watcher CLI; hooks shipped for Codex / Claude / Hermes / OpenClaw | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
102
108
  | Local-first SQLite (zero external services) | ✅ | ❌ Postgres + Qdrant | ❌ Postgres + Qdrant | ⚠ file-system + cloud | ⚠ ChromaDB |
103
109
  | Hybrid recall: BM25 + semantic + entity (RRF) | ✅ | ✅ | ✅ | ✅ | ⚠ entity-only |
104
110
  | Temporal reasoning in retrieval (boost / suppress by date intent) | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -0,0 +1,184 @@
1
+ """CLI for Loop Memory.
2
+
3
+ Usage:
4
+
5
+ loop-memory chat # REPL with echo LLM
6
+ loop-memory stats # counters
7
+ loop-memory recall <text> # show top memories
8
+ loop-memory ingest codex # ingest local Codex transcripts
9
+ loop-memory ingest claude # ingest local Claude Code transcripts
10
+ loop-memory ingest hermes # ingest Hermes transcripts
11
+ loop-memory consolidate # rescore + GC + dedupe
12
+ loop-memory rescore [--half-life 30]
13
+ loop-memory serve [--port 7767] # start the local web UI
14
+ loop-memory mcp # stdio MCP server (for codex/claude/hermes)
15
+ loop-memory inject [query] # dump long-term context block (for SessionStart hooks)
16
+ loop-memory install-hooks # auto-write MCP + SessionStart hooks for known clients
17
+ loop-memory consolidate-now # ask the running server to trigger a pass right now
18
+ loop-memory export # legacy markdown export (no positional path)
19
+ loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md (≤ max-chars bytes)
20
+ loop-memory ask "what about…" # print a paste-ready context block for any LLM client
21
+ loop-memory cognitive-sleep [--apply] # dry-run / apply cognitive sweep (v7)
22
+ loop-memory audit [--kind X] [--action Y] # read the cognitive audit trail
23
+ loop-memory export <out_dir> # write a MEMORY.md bundle (v7)
24
+ loop-memory export-bundle <out_dir> # explicit v7 bundle alias
25
+ loop-memory import <in_dir> # re-hydrate a bundle
26
+ loop-memory fork [--branch-tag T] # snapshot every wiki page
27
+ loop-memory graph-edge <src> <dst> [--kind K] [--weight W] # push a relation
28
+ loop-memory subgraph <query> # print a small subgraph
29
+ loop-memory graph-rebuild # rebuild entities + entity_mentions
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import sys
35
+
36
+ from .commands import cognitive as cognitive_cmd
37
+ from .commands import diag as diag_cmd
38
+ from .commands import graph as graph_cmd
39
+ from .commands import hooks as hooks_cmd
40
+ from .commands import read as read_cmd
41
+ from .commands import serve as serve_cmd
42
+ from .commands import write as write_cmd
43
+
44
+ # Backwards-compatible re-exports. Earlier tests imported these
45
+ # private symbols from this module; keep the names available so
46
+ # external code doesn't break after the 0.3.0 split.
47
+ _upsert_block = hooks_cmd._upsert_block
48
+ cmd_inject = read_cmd.run_inject
49
+
50
+
51
+ def _run_export(args: list[str]) -> int:
52
+ """Keep the legacy markdown export and expose the v7 bundle export.
53
+
54
+ A positional output directory selects the v7 bundle. The historical
55
+ ``--out`` / ``--q`` form remains available so existing scripts keep
56
+ producing a single markdown file.
57
+ """
58
+ if not args or any(flag in args for flag in ("--out", "--q")):
59
+ return read_cmd.run_export(args)
60
+ return cognitive_cmd.run_export(args)
61
+
62
+
63
+ def _run_version(_args: list[str]) -> int:
64
+ """Print the installed distribution version.
65
+
66
+ Resolved through :mod:`importlib.metadata` so the answer comes from
67
+ whatever wheel or sdist the user actually has installed, including
68
+ editable installs (``pip install -e .``). Falls back to ``"unknown"``
69
+ if the package metadata is missing (which would itself indicate a
70
+ broken install).
71
+ """
72
+ from importlib import metadata as _ilmd
73
+ try:
74
+ ver = _ilmd.version("loop-memory")
75
+ except _ilmd.PackageNotFoundError:
76
+ ver = "unknown"
77
+ print(f"loop-memory {ver}")
78
+ return 0
79
+
80
+
81
+ # Dispatch table: command name -> (callable, optional module doc).
82
+ # Each module's ``run_<name>`` function takes ``args: list[str]`` and
83
+ # returns an integer exit code. Keep the keys matching the docstring
84
+ # at the top of this file.
85
+ COMMANDS = {
86
+ "chat": read_cmd.run_chat,
87
+ "stats": read_cmd.run_stats,
88
+ "recall": read_cmd.run_recall,
89
+ "ingest": write_cmd.run_ingest,
90
+ "consolidate": write_cmd.run_consolidate,
91
+ "consolidate-now": write_cmd.run_consolidate_now,
92
+ "export": _run_export,
93
+ "digest": read_cmd.run_digest,
94
+ "ask": read_cmd.run_ask,
95
+ "rescore": write_cmd.run_rescore,
96
+ "serve": serve_cmd.run_serve,
97
+ "hook": serve_cmd.run_hook,
98
+ "mcp": serve_cmd.run_mcp,
99
+ "inject": read_cmd.run_inject,
100
+ "install-hooks": hooks_cmd.run_install_hooks,
101
+ "flush": write_cmd.run_flush,
102
+ "graph": graph_cmd.run_graph,
103
+ "doctor": diag_cmd.run_doctor,
104
+ "status": diag_cmd.run_status,
105
+ "openclaw-setup": diag_cmd.run_openclaw_setup,
106
+ # Universal Agent Memory v7 — graph, cognitive, export, fork
107
+ "cognitive-sleep": cognitive_cmd.run_cognitive_sleep,
108
+ "audit": cognitive_cmd.run_audit,
109
+ "export-bundle": cognitive_cmd.run_export,
110
+ "import": cognitive_cmd.run_import,
111
+ "fork": cognitive_cmd.run_fork,
112
+ "graph-edge": cognitive_cmd.run_graph_edge,
113
+ "subgraph": cognitive_cmd.run_subgraph,
114
+ "graph-rebuild": cognitive_cmd.run_graph_rebuild,
115
+ "wiki-reclassify-legacy": cognitive_cmd.run_wiki_reclassify_legacy,
116
+ "version": _run_version,
117
+ }
118
+
119
+
120
+ # Per-command usage lines. The dispatcher prints these on
121
+ # ``<subcommand> --help`` *without* invoking the handler, so missing
122
+ # optional deps (e.g. ``[serve]`` for fastapi) and missing required
123
+ # positional args never crash the help path.
124
+ COMMAND_HELP: dict[str, str] = {
125
+ "ask": "loop-memory ask '<question>' # print a paste-ready context block for any LLM client.",
126
+ "audit": "loop-memory audit [--kind X] [--action Y] # read the cognitive audit trail.",
127
+ "chat": "loop-memory chat # REPL with echo LLM.",
128
+ "cognitive-sleep": "loop-memory cognitive-sleep [--apply] # dry-run / apply cognitive sweep.",
129
+ "consolidate": "loop-memory consolidate # rescore + GC + dedupe.",
130
+ "consolidate-now": "loop-memory consolidate-now # ask the running server to trigger a pass right now.",
131
+ "digest": "loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md.",
132
+ "doctor": "loop-memory doctor # diagnose local install (db, hooks, optional deps).",
133
+ "export": "loop-memory export [--out FILE | <out_dir>] # markdown dump or v7 bundle export.",
134
+ "export-bundle": "loop-memory export-bundle <out_dir> # explicit v7 bundle alias.",
135
+ "flush": "loop-memory flush # drop volatile caches; keep persisted data.",
136
+ "fork": "loop-memory fork [--branch-tag T] # snapshot every wiki page.",
137
+ "graph": "loop-memory graph # print the entities + relations table.",
138
+ "graph-edge": "loop-memory graph-edge <src> <dst> [--kind K] [--weight W] # push a relation.",
139
+ "graph-rebuild": "loop-memory graph-rebuild # rebuild entities + entity_mentions.",
140
+ "hook": "loop-memory hook --source <codex|claude|hermes|openclaw> --watch <path> [--idle SECONDS]\n Watch a directory for new transcripts and ingest them.",
141
+ "import": "loop-memory import <in_dir> # re-hydrate a v7 bundle.",
142
+ "ingest": "loop-memory ingest {codex|claude|hermes|openclaw}\n Ingest transcripts from a local source into the SQLite store.",
143
+ "inject": "loop-memory inject [query] # dump long-term context block (for SessionStart hooks).",
144
+ "install-hooks": "loop-memory install-hooks # auto-write MCP + SessionStart hooks for known clients.",
145
+ "mcp": "loop-memory mcp # stdio MCP server (for codex/claude/hermes). Requires ``[serve]`` for HTTP transport.",
146
+ "openclaw-setup": "loop-memory openclaw-setup # install the openclaw/clawx auto-ingest watcher (launchd on macOS).",
147
+ "recall": "loop-memory recall <text> # show top memories.",
148
+ "rescore": "loop-memory rescore [--half-life 30] # recompute memory scores.",
149
+ "serve": "loop-memory serve [--port 7767] [--no-browser] # start the local web UI. Requires the ``[serve]`` extra (fastapi + uvicorn).",
150
+ "stats": "loop-memory stats # counters.",
151
+ "status": "loop-memory status # one-shot summary of ingest / recall / serve / hook state.",
152
+ "subgraph": "loop-memory subgraph <query> # print a small subgraph.",
153
+ "version": "loop-memory version # print the installed distribution version.",
154
+ "wiki-reclassify-legacy": "loop-memory wiki-reclassify-legacy # back-fill scope + scope_filter for H4+H5 pages.",
155
+ }
156
+
157
+
158
+
159
+ def main(argv: list | None = None) -> int:
160
+ args = list(sys.argv[1:] if argv is None else argv)
161
+ if not args or args[0] in {"-h", "--help"}:
162
+ print(__doc__)
163
+ return 0
164
+ if args[0] in {"-V", "--version"}:
165
+ return _run_version(args[1:])
166
+ cmd, rest = args[0], args[1:]
167
+ # Per-subcommand --help / -h: print the static help line and exit 0
168
+ # *without* invoking the handler. Avoids ValueError on ingest (when
169
+ # ``--help`` would be treated as a source name) and ModuleNotFoundError
170
+ # on serve (when [serve] is not installed). Each handler that wants
171
+ # richer help can still implement it locally — our interceptor is a
172
+ # fallback.
173
+ if rest and rest[0] in {"-h", "--help"} and cmd in COMMAND_HELP:
174
+ print(COMMAND_HELP[cmd])
175
+ return 0
176
+ fn = COMMANDS.get(cmd)
177
+ if fn is None:
178
+ print(f"unknown command: {cmd}", file=sys.stderr)
179
+ return 2
180
+ return fn(rest)
181
+
182
+
183
+ if __name__ == "__main__":
184
+ sys.exit(main())
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>循环记忆 · Loop Memory</title>
7
- <meta name="description" content="Loop Memory — local-first memory loop for Codex, Claude, Hermes and OpenClaw. Capture, distill and recall your AI conversation knowledge." />
7
+ <meta name="description" content="Loop Memory — local-first memory system that closes the agent loop for every AI agent. Hooks for Codex / Claude / Hermes / OpenClaw; SDK + generic watcher CLI for the rest." />
8
8
  <link rel="stylesheet" href="static/css/app.css" />
9
9
  <link rel="stylesheet" href="static/css/layout.css" />
10
10
  </head>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loop-memory
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -48,11 +48,14 @@ Dynamic: license-file
48
48
 
49
49
  # Loop Memory
50
50
 
51
- > **A general-purpose memory system for every AI agent you run locally.**
51
+ > **A general-purpose, local-first memory system that closes the agent loop for every AI agent you run.**
52
52
  >
53
- > Auto-captures every Codex / Claude / Hermes / OpenClaw conversation,
54
- > distils them into a tight wiki of stable knowledge, and lets the
55
- > agent recall what matters on demand.
53
+ > Point Loop Memory at any agent's transcript directory and it
54
+ > quietly catches every conversation, scores it, distils the long
55
+ > tail into a tight wiki of stable knowledge, and re-injects the
56
+ > relevant pieces into the next session. Out of the box: hooks for
57
+ > Codex, Claude, Hermes, and OpenClaw; an SDK + generic watcher
58
+ > CLI cover everything else.
56
59
 
57
60
  [![CI](https://img.shields.io/github/actions/workflow/status/smartfind/loop-memory/tests.yml?branch=main&style=flat-square)](https://github.com/smartfind/loop-memory/actions)
58
61
  [![PyPI](https://img.shields.io/pypi/v/loop-memory.svg?style=flat-square)](https://pypi.org/project/loop-memory/)
@@ -66,11 +69,14 @@ Dynamic: license-file
66
69
  ## What it does
67
70
 
68
71
  **Loop Memory** gives every agent you use a single, persistent brain
69
- that outlives any one conversation. Each agent (Codex CLI, Claude
70
- Code, Hermes, OpenClaw / clawx, …) drops its transcripts onto disk;
71
- Loop Memory quietly catches them, scores every fragment by *importance
72
- × recency × usage × feedback*, distils the long tail into a curated
73
- wiki, and re-injects the relevant pieces into the next session.
72
+ that outlives any one conversation. Any agent that drops transcripts
73
+ on disk — Codex CLI, Claude Code, Hermes, OpenClaw / clawx, Aider,
74
+ Cursor, works; the shipped hooks cover the popular ones and a
75
+ generic watcher CLI (`loop-memory hook --source <name> --watch <dir>`)
76
+ accepts anything else. Loop Memory quietly catches every fragment,
77
+ scores it by *importance × recency × usage × feedback*, distils the
78
+ long tail into a curated wiki, and re-injects the relevant pieces
79
+ into the next session.
74
80
 
75
81
  ```mermaid
76
82
  flowchart LR
@@ -142,7 +148,7 @@ following — every other project we looked at lacks at least one:
142
148
 
143
149
  | Capability | **Loop Memory** | Mem0 v3 | Hindsight | OpenViking | A-MEM |
144
150
  | --- | --- | --- | --- | --- | --- |
145
- | Multi-source capture (Codex / Claude / Hermes / OpenClaw) | ✅ out of the box | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
151
+ | Multi-source capture (any agent with on-disk transcripts) | ✅ generic watcher CLI; hooks shipped for Codex / Claude / Hermes / OpenClaw | ⚠ requires plugin per client | ⚠ hosted only | ⚠ SDK + companion app | ❌ |
146
152
  | Local-first SQLite (zero external services) | ✅ | ❌ Postgres + Qdrant | ❌ Postgres + Qdrant | ⚠ file-system + cloud | ⚠ ChromaDB |
147
153
  | Hybrid recall: BM25 + semantic + entity (RRF) | ✅ | ✅ | ✅ | ✅ | ⚠ entity-only |
148
154
  | Temporal reasoning in retrieval (boost / suppress by date intent) | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -90,6 +90,7 @@ tests/test_agent_memory_api.py
90
90
  tests/test_agent_memory_sdk.py
91
91
  tests/test_auth_token_rotate.py
92
92
  tests/test_cli_v7.py
93
+ tests/test_cli_version.py
93
94
  tests/test_contradictions.py
94
95
  tests/test_evolution.py
95
96
  tests/test_evolution_quality.py
@@ -106,6 +107,7 @@ tests/test_mcp.py
106
107
  tests/test_memories_pagination.py
107
108
  tests/test_openclaw_loader.py
108
109
  tests/test_reflection.py
110
+ tests/test_repo_framing.py
109
111
  tests/test_score_api.py
110
112
  tests/test_scoring_v2.py
111
113
  tests/test_secrets.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loop-memory"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "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."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,130 @@
1
+ """Regression tests for the ``loop-memory --version`` family of flags.
2
+
3
+ Before this fix, ``loop-memory --version`` returned ``unknown command:
4
+ --version`` which tripped users on the most basic operation a CLI
5
+ must support. These tests pin all three forms (``--version``, ``-V``,
6
+ ``version``) and assert the printed value matches the installed
7
+ distribution's metadata.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import io
12
+ import sys
13
+ import unittest
14
+ from contextlib import redirect_stdout
15
+ from importlib import metadata as importlib_metadata
16
+
17
+ from loop_memory.cli.main import main
18
+
19
+
20
+ def _run(args: list[str]) -> tuple[int, str]:
21
+ """Run ``loop-memory <args>`` and return ``(rc, stdout)``."""
22
+ buf = io.StringIO()
23
+ with redirect_stdout(buf):
24
+ rc = main(args)
25
+ return rc, buf.getvalue()
26
+
27
+
28
+ class CliVersionTests(unittest.TestCase):
29
+ def test_dash_dash_version_prints_distribution_version(self) -> None:
30
+ expected = importlib_metadata.version("loop-memory")
31
+ rc, out = _run(["--version"])
32
+ self.assertEqual(rc, 0, out)
33
+ self.assertIn(expected, out.strip(), out)
34
+ # The output should be human-friendly: at minimum include the package name.
35
+ self.assertIn("loop-memory", out)
36
+
37
+ def test_dash_V_short_flag_also_works(self) -> None:
38
+ rc, out = _run(["-V"])
39
+ self.assertEqual(rc, 0, out)
40
+ self.assertIn(importlib_metadata.version("loop-memory"), out.strip(), out)
41
+
42
+ def test_version_subcommand_also_works(self) -> None:
43
+ rc, out = _run(["version"])
44
+ self.assertEqual(rc, 0, out)
45
+ self.assertIn(importlib_metadata.version("loop-memory"), out.strip(), out)
46
+
47
+ def test_version_does_not_require_a_database(self) -> None:
48
+ """``version`` must work even when ``$LOOP_MEMORY_DB`` points
49
+ somewhere nonexistent — it is a metadata-only probe.
50
+ """
51
+ # Save and unset
52
+ prev = sys.modules.pop("loop_memory.storage.sqlite_store", None)
53
+ try:
54
+ rc, out = _run(["version"])
55
+ self.assertEqual(rc, 0, out)
56
+ finally:
57
+ if prev is not None:
58
+ sys.modules["loop_memory.storage.sqlite_store"] = prev
59
+
60
+
61
+
62
+
63
+
64
+ class CliSubcommandHelpTests(unittest.TestCase):
65
+ """Pin dispatcher behaviour for ``<subcommand> --help`` / ``-h``.
66
+
67
+ The install smoke test caught three real bugs against the
68
+ published 0.4.0 wheel:
69
+
70
+ * ``ingest --help`` crashed with ``ValueError: unknown source:
71
+ '--help'`` because the handler treated ``--help`` as a source name.
72
+ * ``serve --help`` crashed with ``ModuleNotFoundError: No module
73
+ named 'fastapi'`` because the [serve] extra wasn't installed
74
+ (the handler lazy-imports fastapi). Users on a base install
75
+ couldn't read serve's help text.
76
+ * ``hook --help`` returned rc=2 from the ``die()`` helper even
77
+ though it printed the correct usage line.
78
+
79
+ The dispatcher now intercepts ``-h`` / ``--help`` AFTER the
80
+ subcommand name and prints a static help line, never invoking
81
+ the handler — so all three forms exit cleanly with rc=0 even on
82
+ a ``pip install loop-memory`` (no [serve] extra) install.
83
+ """
84
+
85
+ def _run(self, args: list[str]) -> tuple[int, str]:
86
+ import io
87
+ from contextlib import redirect_stdout
88
+ buf = io.StringIO()
89
+ with redirect_stdout(buf):
90
+ rc = main(args)
91
+ return rc, buf.getvalue()
92
+
93
+ def test_ingest_help_does_not_crash_with_value_error(self) -> None:
94
+ rc, out = self._run(["ingest", "--help"])
95
+ self.assertEqual(rc, 0, out)
96
+ self.assertIn("ingest", out.lower())
97
+
98
+ def test_serve_help_works_without_serve_extra(self) -> None:
99
+ """Must not require the [serve] extra to be installed."""
100
+ rc, out = self._run(["serve", "--help"])
101
+ self.assertEqual(rc, 0, out)
102
+ self.assertIn("serve", out.lower())
103
+
104
+ def test_hook_help_returns_zero(self) -> None:
105
+ rc, out = self._run(["hook", "--help"])
106
+ self.assertEqual(rc, 0, out)
107
+ self.assertIn("hook", out.lower())
108
+
109
+ def test_short_form_dash_h_also_works(self) -> None:
110
+ rc, _ = self._run(["ingest", "-h"])
111
+ self.assertEqual(rc, 0)
112
+
113
+ def test_unknown_subcommand_still_returns_2(self) -> None:
114
+ """Make sure the help interceptor doesn't swallow real
115
+ unknown-command errors.
116
+ """
117
+ rc, _ = self._run(["totally-not-a-command", "--help"])
118
+ self.assertEqual(rc, 2)
119
+
120
+ def test_every_registered_subcommand_has_help_text(self) -> None:
121
+ """Every COMMANDS entry MUST have a corresponding help string
122
+ so a fresh user can ``--help`` any subcommand without surprises.
123
+ """
124
+ from loop_memory.cli import main as cli_main
125
+ missing = sorted(c for c in cli_main.COMMANDS if c not in cli_main.COMMAND_HELP)
126
+ self.assertEqual(missing, [], f"missing COMMAND_HELP for: {missing}")
127
+
128
+
129
+ if __name__ == "__main__":
130
+ unittest.main()