loop-memory 0.4.2__tar.gz → 0.4.4__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.2 → loop_memory-0.4.4}/PKG-INFO +34 -1
- {loop_memory-0.4.2 → loop_memory-0.4.4}/README.md +33 -0
- loop_memory-0.4.4/loop_memory/cli/commands/rules.py +245 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/main.py +5 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/cognitive.py +153 -19
- loop_memory-0.4.4/loop_memory/llm/openai_adapter.py +64 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/llm/providers.py +74 -6
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/sdk_extensions.py +7 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/cognitive.py +7 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/storage/sqlite_store.py +47 -1
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/PKG-INFO +34 -1
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/SOURCES.txt +3 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/pyproject.toml +1 -1
- loop_memory-0.4.4/tests/test_cli_rules.py +161 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_graph.py +52 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_llm_providers.py +111 -0
- loop_memory-0.4.4/tests/test_short_query.py +143 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_universal_memory.py +210 -0
- loop_memory-0.4.2/loop_memory/llm/openai_adapter.py +0 -31
- {loop_memory-0.4.2 → loop_memory-0.4.4}/LICENSE +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/cognitive.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/diag.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/read.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/serve.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/sdk.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/security/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/system.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/setup.cfg +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_agent_memory_sdk.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_loop.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_repo_framing.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_store.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/tests/test_wiki_export_escape.py +0 -0
- {loop_memory-0.4.2 → loop_memory-0.4.4}/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.4
|
|
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
|
|
@@ -402,6 +402,15 @@ pass** over the store:
|
|
|
402
402
|
|
|
403
403
|
Dry-run by default; pass `--apply` to commit.
|
|
404
404
|
|
|
405
|
+
**Observability (since 0.4.3).** The report carries per-stage
|
|
406
|
+
timings (``scan``, ``stale``, ``merge``, ``contradict``, ``apply``,
|
|
407
|
+
``audit``) and an explicit ``aborted`` flag with an ``abort_reason``
|
|
408
|
+
that names the stage the budget fired in. Pass
|
|
409
|
+
``--deadline-seconds <N>`` (HTTP: ``POST /api/v1/cognitive/sleep``
|
|
410
|
+
with ``{"deadline_seconds": N}``; SDK: ``client.cognitive_sleep(deadline_seconds=N)``)
|
|
411
|
+
to bound the sweep — useful for nightly cron, where a stuck
|
|
412
|
+
sweep should leave a loud trace instead of a silent spinner.
|
|
413
|
+
|
|
405
414
|
### Knowledge graph
|
|
406
415
|
|
|
407
416
|
`loop-memory graph-rebuild` extracts entities from every distilled
|
|
@@ -423,6 +432,7 @@ automatically:
|
|
|
423
432
|
| Command | What it does |
|
|
424
433
|
| -------------------------------- | ---------------------------------------------------------------------------- |
|
|
425
434
|
| `loop-memory install-hooks` | Auto-detect `~/.codex`, `~/.claude`, `~/.hermes` and write MCP + SessionStart hook configs in place. Idempotent — re-run any time. |
|
|
435
|
+
| `loop-memory rules --write` | Append the three-phase memory-discipline block (task start / mid-task / wrap-up) into the agent's rule file (`AGENTS.md` for codex / hermes / openclaw, `CLAUDE.md` for claude). **Never overwrites user content.** |
|
|
426
436
|
| `loop-memory inject [query]` | Print a `# Long-term memory context` markdown block (distilled wiki + recent relevant memories) for a SessionStart hook. |
|
|
427
437
|
| `loop-memory mcp` | Run the **stdio MCP server** with memory, graph, and cognitive tools (`recall`, `remember`, `forget`, `feedback`, `remember_edge`, `subgraph`, `cognitive_sleep`, `audit`, and wiki tools). |
|
|
428
438
|
|
|
@@ -439,6 +449,11 @@ loop-memory install-hooks # writes ~/.codex/config.toml + ~/.claude/{mcp.j
|
|
|
439
449
|
Manual smoke-test without restarting the client:
|
|
440
450
|
|
|
441
451
|
```bash
|
|
452
|
+
# one-shot: install the memory-discipline block into the agent's
|
|
453
|
+
# rule file so the client calls `recall` on every task start.
|
|
454
|
+
loop-memory rules --agent codex --write # writes ./AGENTS.md (append, never overwrite)
|
|
455
|
+
loop-memory rules --agent claude --write # writes ./CLAUDE.md
|
|
456
|
+
|
|
442
457
|
loop-memory inject # dumps the warm-start block to stdout
|
|
443
458
|
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\n{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"wiki_summary"}}\n' \
|
|
444
459
|
| loop-memory mcp # round-trips JSON-RPC over stdio
|
|
@@ -632,6 +647,24 @@ classifier is local and makes no model or network request on a wiki write.
|
|
|
632
647
|
|
|
633
648
|
---
|
|
634
649
|
|
|
650
|
+
### LLM env-var overrides
|
|
651
|
+
|
|
652
|
+
The OpenAI-compat / Anthropic / Ollama providers (and the optional
|
|
653
|
+
``openai`` adapter) honour two env-var knobs so you can pin
|
|
654
|
+
distillation deterministically without touching the behaviour
|
|
655
|
+
config:
|
|
656
|
+
|
|
657
|
+
- ``LLM_TEMPERATURE`` — float, defaults to ``0.3`` (or the explicit
|
|
658
|
+
``kwargs.temperature``). Invalid values are ignored with a warning.
|
|
659
|
+
- ``LLM_SEED`` — int, sent as ``seed`` for OpenAI / Anthropic /
|
|
660
|
+
Ollama where supported. Omitting it preserves the existing
|
|
661
|
+
"no seed" behaviour so older call sites do not need to migrate.
|
|
662
|
+
|
|
663
|
+
Both env vars are read at every ``complete()`` call, so a single
|
|
664
|
+
``export LLM_SEED=42`` plus a nightly cron makes wiki distillation
|
|
665
|
+
reproducible. Pinned by 11 cases in
|
|
666
|
+
``tests/test_llm_providers.py::LLMEnvVarTests``.
|
|
667
|
+
|
|
635
668
|
## FAQ & troubleshooting
|
|
636
669
|
|
|
637
670
|
**Q: `pip install loop-memory` succeeds but `loop-memory serve` says `ModuleNotFoundError: No module named 'fastapi'`.**
|
|
@@ -358,6 +358,15 @@ pass** over the store:
|
|
|
358
358
|
|
|
359
359
|
Dry-run by default; pass `--apply` to commit.
|
|
360
360
|
|
|
361
|
+
**Observability (since 0.4.3).** The report carries per-stage
|
|
362
|
+
timings (``scan``, ``stale``, ``merge``, ``contradict``, ``apply``,
|
|
363
|
+
``audit``) and an explicit ``aborted`` flag with an ``abort_reason``
|
|
364
|
+
that names the stage the budget fired in. Pass
|
|
365
|
+
``--deadline-seconds <N>`` (HTTP: ``POST /api/v1/cognitive/sleep``
|
|
366
|
+
with ``{"deadline_seconds": N}``; SDK: ``client.cognitive_sleep(deadline_seconds=N)``)
|
|
367
|
+
to bound the sweep — useful for nightly cron, where a stuck
|
|
368
|
+
sweep should leave a loud trace instead of a silent spinner.
|
|
369
|
+
|
|
361
370
|
### Knowledge graph
|
|
362
371
|
|
|
363
372
|
`loop-memory graph-rebuild` extracts entities from every distilled
|
|
@@ -379,6 +388,7 @@ automatically:
|
|
|
379
388
|
| Command | What it does |
|
|
380
389
|
| -------------------------------- | ---------------------------------------------------------------------------- |
|
|
381
390
|
| `loop-memory install-hooks` | Auto-detect `~/.codex`, `~/.claude`, `~/.hermes` and write MCP + SessionStart hook configs in place. Idempotent — re-run any time. |
|
|
391
|
+
| `loop-memory rules --write` | Append the three-phase memory-discipline block (task start / mid-task / wrap-up) into the agent's rule file (`AGENTS.md` for codex / hermes / openclaw, `CLAUDE.md` for claude). **Never overwrites user content.** |
|
|
382
392
|
| `loop-memory inject [query]` | Print a `# Long-term memory context` markdown block (distilled wiki + recent relevant memories) for a SessionStart hook. |
|
|
383
393
|
| `loop-memory mcp` | Run the **stdio MCP server** with memory, graph, and cognitive tools (`recall`, `remember`, `forget`, `feedback`, `remember_edge`, `subgraph`, `cognitive_sleep`, `audit`, and wiki tools). |
|
|
384
394
|
|
|
@@ -395,6 +405,11 @@ loop-memory install-hooks # writes ~/.codex/config.toml + ~/.claude/{mcp.j
|
|
|
395
405
|
Manual smoke-test without restarting the client:
|
|
396
406
|
|
|
397
407
|
```bash
|
|
408
|
+
# one-shot: install the memory-discipline block into the agent's
|
|
409
|
+
# rule file so the client calls `recall` on every task start.
|
|
410
|
+
loop-memory rules --agent codex --write # writes ./AGENTS.md (append, never overwrite)
|
|
411
|
+
loop-memory rules --agent claude --write # writes ./CLAUDE.md
|
|
412
|
+
|
|
398
413
|
loop-memory inject # dumps the warm-start block to stdout
|
|
399
414
|
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\n{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"wiki_summary"}}\n' \
|
|
400
415
|
| loop-memory mcp # round-trips JSON-RPC over stdio
|
|
@@ -588,6 +603,24 @@ classifier is local and makes no model or network request on a wiki write.
|
|
|
588
603
|
|
|
589
604
|
---
|
|
590
605
|
|
|
606
|
+
### LLM env-var overrides
|
|
607
|
+
|
|
608
|
+
The OpenAI-compat / Anthropic / Ollama providers (and the optional
|
|
609
|
+
``openai`` adapter) honour two env-var knobs so you can pin
|
|
610
|
+
distillation deterministically without touching the behaviour
|
|
611
|
+
config:
|
|
612
|
+
|
|
613
|
+
- ``LLM_TEMPERATURE`` — float, defaults to ``0.3`` (or the explicit
|
|
614
|
+
``kwargs.temperature``). Invalid values are ignored with a warning.
|
|
615
|
+
- ``LLM_SEED`` — int, sent as ``seed`` for OpenAI / Anthropic /
|
|
616
|
+
Ollama where supported. Omitting it preserves the existing
|
|
617
|
+
"no seed" behaviour so older call sites do not need to migrate.
|
|
618
|
+
|
|
619
|
+
Both env vars are read at every ``complete()`` call, so a single
|
|
620
|
+
``export LLM_SEED=42`` plus a nightly cron makes wiki distillation
|
|
621
|
+
reproducible. Pinned by 11 cases in
|
|
622
|
+
``tests/test_llm_providers.py::LLMEnvVarTests``.
|
|
623
|
+
|
|
591
624
|
## FAQ & troubleshooting
|
|
592
625
|
|
|
593
626
|
**Q: `pip install loop-memory` succeeds but `loop-memory serve` says `ModuleNotFoundError: No module named 'fastapi'`.**
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""rules: install a memory-discipline block into the agent's rule file.
|
|
2
|
+
|
|
3
|
+
Audit 2026-08-23: pattern adopted from
|
|
4
|
+
``2672243194/agentbrain`` v0.4.3 (``agentbrain rules --agent … --write``,
|
|
5
|
+
which appends a three-phase workflow block into CLAUDE.md /
|
|
6
|
+
AGENTS.md / Cursor / Trae rule files). Loop Memory's port wires
|
|
7
|
+
the same discipline into the four shipped agent targets:
|
|
8
|
+
|
|
9
|
+
* ``codex`` -> ``AGENTS.md`` (Codex CLI convention)
|
|
10
|
+
* ``claude`` -> ``CLAUDE.md`` (Claude Code convention)
|
|
11
|
+
* ``hermes`` -> ``AGENTS.md`` (Hermes CLI also reads AGENTS.md)
|
|
12
|
+
* ``openclaw`` -> ``AGENTS.md`` (OpenClaw reads AGENTS.md)
|
|
13
|
+
* ``generic`` -> ``AGENTS.md`` (any other AGENTS.md-aware client)
|
|
14
|
+
|
|
15
|
+
Safety properties:
|
|
16
|
+
|
|
17
|
+
* Existing rule files are **never overwritten** — the block is
|
|
18
|
+
appended after the user's content. Their rules stay byte-identical.
|
|
19
|
+
* **Idempotent** — a marker comment detects a prior install and
|
|
20
|
+
skips the write.
|
|
21
|
+
* The CLI exits 0 even when the target file does not exist (it
|
|
22
|
+
creates an empty one) and when no ``--agent`` is given (it just
|
|
23
|
+
prints the generic block to stdout).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Marker line used to detect a prior install. The leading space +
|
|
33
|
+
# trailing space keep the marker from accidentally colliding with a
|
|
34
|
+
# user's prose like "loop-memory is great". Keep this string in sync
|
|
35
|
+
# with the appended block below.
|
|
36
|
+
MARKER = "<!-- loop-memory:rules:installed -->"
|
|
37
|
+
|
|
38
|
+
# Agent-name -> relative path under cwd. Unknown agents fall back
|
|
39
|
+
# to ``generic`` (AGENTS.md).
|
|
40
|
+
AGENT_TARGETS: dict[str, str] = {
|
|
41
|
+
"codex": "AGENTS.md",
|
|
42
|
+
"claude": "CLAUDE.md",
|
|
43
|
+
"hermes": "AGENTS.md",
|
|
44
|
+
"openclaw": "AGENTS.md",
|
|
45
|
+
"generic": "AGENTS.md",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _generic_block() -> str:
|
|
50
|
+
"""The discipline block written to every agent's rule file."""
|
|
51
|
+
return (
|
|
52
|
+
f"\n{MARKER}\n"
|
|
53
|
+
"# Memory discipline (auto-installed by `loop-memory rules`).\n"
|
|
54
|
+
"# Re-run the same command to refresh in place; manual edits\n"
|
|
55
|
+
"# between the marker lines will be preserved on the next refresh.\n"
|
|
56
|
+
"\n"
|
|
57
|
+
"## At task start\n"
|
|
58
|
+
"\n"
|
|
59
|
+
"Before you read or write anything in this project, query Loop\n"
|
|
60
|
+
"Memory first so you can act on prior work instead of rebuilding\n"
|
|
61
|
+
"it. Use the project's existing memory tooling — for example\n"
|
|
62
|
+
"`loop-memory recall \"<task topic>\"` (shell), the `recall` MCP\n"
|
|
63
|
+
"tool, or the `inject` SessionStart hook if one is wired up.\n"
|
|
64
|
+
"Read the top hits before you start typing.\n"
|
|
65
|
+
"\n"
|
|
66
|
+
"## Mid-task\n"
|
|
67
|
+
"\n"
|
|
68
|
+
"Re-query whenever you switch subtask, hit an unexpected error,\n"
|
|
69
|
+
"or follow up a topic the initial query did not cover. Plain\n"
|
|
70
|
+
"continuation of the same line of thought does not need a\n"
|
|
71
|
+
"re-query — that is what saves tokens.\n"
|
|
72
|
+
"\n"
|
|
73
|
+
"## Wrap-up\n"
|
|
74
|
+
"\n"
|
|
75
|
+
"Each distinct reusable lesson — a non-obvious gotcha, a\n"
|
|
76
|
+
"convention you wish you'd known, a fix to a bug class —\n"
|
|
77
|
+
"should be written back via `loop-memory ingest` / the `add`\n"
|
|
78
|
+
"MCP tool, with user confirmation so nothing private lands in\n"
|
|
79
|
+
"the vault. Never paste secrets, tokens, or local paths into\n"
|
|
80
|
+
"a memory; use `${ENV:VAR_NAME}` placeholders or omit them.\n"
|
|
81
|
+
f"\n{MARKER} (end)\n"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _render_block(agent: str) -> str:
|
|
86
|
+
"""Return the discipline block tailored to ``agent``."""
|
|
87
|
+
base = _generic_block()
|
|
88
|
+
if agent in {"claude", "codex"}:
|
|
89
|
+
return base
|
|
90
|
+
# Hermes / OpenClaw: same text but the explicit tool name in the
|
|
91
|
+
# hook line differs because the MCP tool prefix varies. The block
|
|
92
|
+
# already names the tool generically; we add one agent-specific
|
|
93
|
+
# clarifying line at the bottom for these two.
|
|
94
|
+
if agent == "hermes":
|
|
95
|
+
extra = (
|
|
96
|
+
"\n## Hermes-specific\n"
|
|
97
|
+
"\n"
|
|
98
|
+
"Hermes exposes Loop Memory through MCP too. The same\n"
|
|
99
|
+
"`recall` / `add` tool names apply; if you do not see\n"
|
|
100
|
+
"them in the MCP tool list, run\n"
|
|
101
|
+
"`loop-memory install-hooks` once and restart Hermes.\n"
|
|
102
|
+
)
|
|
103
|
+
return base.replace(f"\n{MARKER} (end)\n", extra + f"\n{MARKER} (end)\n")
|
|
104
|
+
if agent == "openclaw":
|
|
105
|
+
extra = (
|
|
106
|
+
"\n## OpenClaw-specific\n"
|
|
107
|
+
"\n"
|
|
108
|
+
"OpenClaw sessions are auto-ingested when\n"
|
|
109
|
+
"`loop-memory hook --source openclaw --watch <sessions-dir>`\n"
|
|
110
|
+
"is running. If the recall hits are empty, check that\n"
|
|
111
|
+
"the watcher is alive (`loop-memory doctor`) and that\n"
|
|
112
|
+
"the workspace log directory exists.\n"
|
|
113
|
+
)
|
|
114
|
+
return base.replace(f"\n{MARKER} (end)\n", extra + f"\n{MARKER} (end)\n")
|
|
115
|
+
return base
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _resolve_target(agent: str, cwd: Path) -> Path:
|
|
119
|
+
"""Map an agent name to its rule-file path under ``cwd``."""
|
|
120
|
+
rel = AGENT_TARGETS.get(agent)
|
|
121
|
+
if rel is None:
|
|
122
|
+
# Unknown agent — keep the same path as ``generic`` so the
|
|
123
|
+
# user still gets a working AGENTS.md and can rename it.
|
|
124
|
+
rel = AGENT_TARGETS["generic"]
|
|
125
|
+
return cwd / rel
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _install(cwd: Path, agent: str, *, force: bool) -> tuple[str, str]:
|
|
129
|
+
"""Install the block; return (status, message).
|
|
130
|
+
|
|
131
|
+
``status`` is one of: ``installed``, ``already-installed``,
|
|
132
|
+
``appended``, ``dry-run``.
|
|
133
|
+
"""
|
|
134
|
+
target = _resolve_target(agent, cwd)
|
|
135
|
+
existing = ""
|
|
136
|
+
if target.exists():
|
|
137
|
+
try:
|
|
138
|
+
existing = target.read_text(encoding="utf-8")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
return "error", f"could not read {target}: {e}"
|
|
141
|
+
if MARKER in existing and not force:
|
|
142
|
+
return "already-installed", str(target)
|
|
143
|
+
# ``run_rules`` does the actual write; this helper just classifies
|
|
144
|
+
# the request so callers can unit-test the marker-detection path.
|
|
145
|
+
return "installed", str(target)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def run_rules(args: list) -> int:
|
|
149
|
+
"""``loop-memory rules [--agent NAME] [--write] [--force] [--cwd PATH]``.
|
|
150
|
+
|
|
151
|
+
Behaviour:
|
|
152
|
+
* no args -> print the generic block to stdout
|
|
153
|
+
* ``--agent NAME`` -> print the agent-tailored block
|
|
154
|
+
* ``--write`` -> install (append) into the agent's
|
|
155
|
+
rule file under cwd (or ``--cwd``)
|
|
156
|
+
* ``--force`` -> overwrite a prior install in place
|
|
157
|
+
(still never touches user content
|
|
158
|
+
outside the marker lines)
|
|
159
|
+
"""
|
|
160
|
+
agent = "generic"
|
|
161
|
+
write = False
|
|
162
|
+
force = False
|
|
163
|
+
cwd = Path.cwd()
|
|
164
|
+
it = iter(args)
|
|
165
|
+
for tok in it:
|
|
166
|
+
if tok == "--agent":
|
|
167
|
+
try:
|
|
168
|
+
agent = next(it)
|
|
169
|
+
except StopIteration:
|
|
170
|
+
print("--agent requires a name", file=sys.stderr)
|
|
171
|
+
return 2
|
|
172
|
+
elif tok == "--write":
|
|
173
|
+
write = True
|
|
174
|
+
elif tok == "--force":
|
|
175
|
+
force = True
|
|
176
|
+
elif tok == "--cwd":
|
|
177
|
+
try:
|
|
178
|
+
cwd = Path(next(it)).expanduser()
|
|
179
|
+
except StopIteration:
|
|
180
|
+
print("--cwd requires a path", file=sys.stderr)
|
|
181
|
+
return 2
|
|
182
|
+
elif tok in {"-h", "--help"}:
|
|
183
|
+
print(__doc__ or "loop-memory rules [--agent NAME] [--write] [--force]")
|
|
184
|
+
return 0
|
|
185
|
+
else:
|
|
186
|
+
print(f"unknown argument: {tok}", file=sys.stderr)
|
|
187
|
+
return 2
|
|
188
|
+
|
|
189
|
+
if not write:
|
|
190
|
+
sys.stdout.write(_render_block(agent))
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
target = _resolve_target(agent, cwd)
|
|
194
|
+
existing = ""
|
|
195
|
+
if target.exists():
|
|
196
|
+
try:
|
|
197
|
+
existing = target.read_text(encoding="utf-8")
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print(f"could not read {target}: {e}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
|
|
202
|
+
block = _render_block(agent)
|
|
203
|
+
if MARKER in existing and not force:
|
|
204
|
+
print(f"[loop-memory] rules: already installed in {target}")
|
|
205
|
+
return 0
|
|
206
|
+
if MARKER in existing and force:
|
|
207
|
+
# Replace the existing marker span in place: keep everything
|
|
208
|
+
# before the start marker and everything after the close
|
|
209
|
+
# marker, splice a fresh block between them. User content
|
|
210
|
+
# outside the marker lines is preserved byte-for-byte.
|
|
211
|
+
start_idx = existing.index(MARKER)
|
|
212
|
+
close_idx = existing.find(MARKER + " (end)", start_idx)
|
|
213
|
+
if close_idx < 0:
|
|
214
|
+
# Partial / corrupted install — fall back to append.
|
|
215
|
+
close_idx = len(existing)
|
|
216
|
+
else:
|
|
217
|
+
close_idx = existing.index("\n", close_idx)
|
|
218
|
+
new_content = existing[:start_idx].rstrip("\n") + "\n" + block + existing[close_idx:]
|
|
219
|
+
action = "refreshed"
|
|
220
|
+
else:
|
|
221
|
+
sep = ""
|
|
222
|
+
if existing and not existing.endswith("\n"):
|
|
223
|
+
sep = "\n"
|
|
224
|
+
new_content = existing + sep + block
|
|
225
|
+
action = "appended" if existing else "created"
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
229
|
+
target.write_text(new_content, encoding="utf-8")
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"[loop-memory] rules: write failed: {e}", file=sys.stderr)
|
|
232
|
+
return 1
|
|
233
|
+
print(f"[loop-memory] rules: {action} {target}")
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
__all__ = [
|
|
238
|
+
"AGENT_TARGETS",
|
|
239
|
+
"MARKER",
|
|
240
|
+
"_generic_block",
|
|
241
|
+
"_install",
|
|
242
|
+
"_render_block",
|
|
243
|
+
"_resolve_target",
|
|
244
|
+
"run_rules",
|
|
245
|
+
]
|
|
@@ -17,6 +17,8 @@ Usage:
|
|
|
17
17
|
loop-memory consolidate-now # ask the running server to trigger a pass right now
|
|
18
18
|
loop-memory export # legacy markdown export (no positional path)
|
|
19
19
|
loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md (≤ max-chars bytes)
|
|
20
|
+
loop-memory rules [--agent codex|claude|hermes|openclaw] [--write] [--force]
|
|
21
|
+
# install the three-phase memory discipline block into the agent's rule file
|
|
20
22
|
loop-memory ask "what about…" # print a paste-ready context block for any LLM client
|
|
21
23
|
loop-memory cognitive-sleep [--apply] # dry-run / apply cognitive sweep (v7)
|
|
22
24
|
loop-memory audit [--kind X] [--action Y] # read the cognitive audit trail
|
|
@@ -38,6 +40,7 @@ from .commands import diag as diag_cmd
|
|
|
38
40
|
from .commands import graph as graph_cmd
|
|
39
41
|
from .commands import hooks as hooks_cmd
|
|
40
42
|
from .commands import read as read_cmd
|
|
43
|
+
from .commands import rules as rules_cmd
|
|
41
44
|
from .commands import serve as serve_cmd
|
|
42
45
|
from .commands import write as write_cmd
|
|
43
46
|
|
|
@@ -113,6 +116,7 @@ COMMANDS = {
|
|
|
113
116
|
"subgraph": cognitive_cmd.run_subgraph,
|
|
114
117
|
"graph-rebuild": cognitive_cmd.run_graph_rebuild,
|
|
115
118
|
"wiki-reclassify-legacy": cognitive_cmd.run_wiki_reclassify_legacy,
|
|
119
|
+
"rules": rules_cmd.run_rules,
|
|
116
120
|
"version": _run_version,
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -152,6 +156,7 @@ COMMAND_HELP: dict[str, str] = {
|
|
|
152
156
|
"subgraph": "loop-memory subgraph <query> # print a small subgraph.",
|
|
153
157
|
"version": "loop-memory version # print the installed distribution version.",
|
|
154
158
|
"wiki-reclassify-legacy": "loop-memory wiki-reclassify-legacy # back-fill scope + scope_filter for H4+H5 pages.",
|
|
159
|
+
"rules": "loop-memory rules [--agent codex|claude|hermes|openclaw] [--write] [--force]\n Print, or append into the agent's rule file, the three-phase memory discipline (task start / mid-task / wrap-up). Never overwrites user content.",
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
|