loop-memory 0.4.16__tar.gz → 0.4.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loop_memory-0.4.16 → loop_memory-0.4.17}/PKG-INFO +5 -2
- {loop_memory-0.4.16 → loop_memory-0.4.17}/README.md +4 -1
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/dream.py +20 -3
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/storage/sqlite_store.py +15 -6
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/PKG-INFO +5 -2
- {loop_memory-0.4.16 → loop_memory-0.4.17}/pyproject.toml +1 -1
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_dream_2026_10_25.py +38 -1
- {loop_memory-0.4.16 → loop_memory-0.4.17}/LICENSE +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/cognitive.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/diag.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/doctor.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/read.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/rules.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/serve.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/cli/main.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/decorators.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/doctor.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/cognitive.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/llm/openai_adapter.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/llm/providers.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/sdk.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/sdk_extensions.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/security/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/cognitive.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/system.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/storage/snapshot.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/SOURCES.txt +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/setup.cfg +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_agent_memory_sdk.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_agents_init.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_audit_triggers_2026_09_27.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_busy_timeout_2026_09_24.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cache_cap_2026_09_23.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_audit_supersede.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_export_okf.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_init.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_memory_stats.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_recall_as_of.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_recall_paths.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_recall_verbose.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_rules.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_snapshot.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_content_hash_dedup_2026_10_18.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_decorators_2026_10_18.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_doctor_2026_10_12.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_dream_cli_http_2026_10_25.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_export_okf.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_graph.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_healthz_readyz_2026_09_24.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_import_okf_2026_09_27.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_latest_only_2026_10_25.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_llm_providers.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_loop.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_memory_stats.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_package_version.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_path_safety_2026_09_23.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_recall_as_of.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_recall_budget_2026_10_05.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_recall_paths.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_recall_provenance.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_recall_validity_http_2026_10_18.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_repo_framing.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_retrieval_channels_2026_10_18.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_routes_audit_2026_09_13.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_routes_audit_2026_09_20.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_short_query.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_snapshot.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_store.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_subgraph_hops_2026_09_23.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_subject_key_2026_10_25.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_supersession_chain.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_universal_memory.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_wiki_export_escape.py +0 -0
- {loop_memory-0.4.16 → loop_memory-0.4.17}/tests/test_wiki_prompts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loop-memory
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.17
|
|
4
4
|
Summary: A general-purpose, local memory system for every AI agent you run. Loop Memory auto-captures conversations from Codex / Claude / Hermes / OpenClaw, scores them by importance × recency × usage × feedback, distils them into a curated wiki, and serves everything from a single web UI.
|
|
5
5
|
Author: Loop Memory contributors <loop-memory@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -470,7 +470,10 @@ never-used stale row is expired. Age alone never decides — that is the
|
|
|
470
470
|
whole point of "In Defense of Forgotten" (arXiv 2605.27366).
|
|
471
471
|
|
|
472
472
|
The pass is a **dry run by default**: `--apply` (CLI) or `"apply": true`
|
|
473
|
-
(HTTP) is what actually writes.
|
|
473
|
+
(HTTP) is what actually writes. A dry run changes **no memory data** --
|
|
474
|
+
no expiry, no supersession, no rescore -- it only appends a
|
|
475
|
+
`cognitive_audit` row per proposed action (`action="suggest"`), which
|
|
476
|
+
is the reviewable plan the dashboard shows before you commit.
|
|
474
477
|
|
|
475
478
|
```bash
|
|
476
479
|
loop-memory dream --json # machine-readable dry run
|
|
@@ -426,7 +426,10 @@ never-used stale row is expired. Age alone never decides — that is the
|
|
|
426
426
|
whole point of "In Defense of Forgotten" (arXiv 2605.27366).
|
|
427
427
|
|
|
428
428
|
The pass is a **dry run by default**: `--apply` (CLI) or `"apply": true`
|
|
429
|
-
(HTTP) is what actually writes.
|
|
429
|
+
(HTTP) is what actually writes. A dry run changes **no memory data** --
|
|
430
|
+
no expiry, no supersession, no rescore -- it only appends a
|
|
431
|
+
`cognitive_audit` row per proposed action (`action="suggest"`), which
|
|
432
|
+
is the reviewable plan the dashboard shows before you commit.
|
|
430
433
|
|
|
431
434
|
```bash
|
|
432
435
|
loop-memory dream --json # machine-readable dry run
|
|
@@ -30,7 +30,9 @@ What this job does
|
|
|
30
30
|
pass:
|
|
31
31
|
|
|
32
32
|
1. **Decay.** Rescore every row with the existing exponential
|
|
33
|
-
half-life model so recency-sensitive ordering stays honest.
|
|
33
|
+
half-life model so recency-sensitive ordering stays honest. A dry
|
|
34
|
+
run computes the new scores and reports how many rows *would*
|
|
35
|
+
change, but writes nothing (audit 2026-10-25, fix).
|
|
34
36
|
|
|
35
37
|
2. **Utility-aware selective forgetting.** A row is a *forgetting
|
|
36
38
|
candidate* once it is past its own TTL (or past the sweep's
|
|
@@ -48,7 +50,12 @@ pass:
|
|
|
48
50
|
``recall(latest_only=True)`` can collapse to one answer.
|
|
49
51
|
|
|
50
52
|
Everything is opt-in: ``apply=False`` (the default) produces a dry-run
|
|
51
|
-
report and
|
|
53
|
+
report and **changes no memory data** -- no expiry, no supersession,
|
|
54
|
+
no rescore. The only write a dry run performs is one
|
|
55
|
+
``cognitive_audit`` row per proposed action (with ``action="suggest"``
|
|
56
|
+
instead of ``"applied"``), because that row *is* the reviewable plan:
|
|
57
|
+
it is what lets the dashboard show what a real run would do before
|
|
58
|
+
you commit to it. Use ``record_audit=False`` to suppress even that.
|
|
52
59
|
"""
|
|
53
60
|
|
|
54
61
|
from __future__ import annotations
|
|
@@ -282,7 +289,17 @@ class Dreamer:
|
|
|
282
289
|
def _stage_decay(self, report: DreamReport) -> None:
|
|
283
290
|
t = time.time()
|
|
284
291
|
try:
|
|
285
|
-
|
|
292
|
+
# Audit 2026-10-25 (fix) -- the decay stage used to write
|
|
293
|
+
# unconditionally, so a *dry run* still rewrote the
|
|
294
|
+
# ``score`` column of every memory. That broke this
|
|
295
|
+
# module's own contract ("nothing is written unless
|
|
296
|
+
# apply=True") and the README / CHANGELOG wording. The
|
|
297
|
+
# store call now runs in dry-run mode, which computes the
|
|
298
|
+
# new scores and reports how many rows *would* change
|
|
299
|
+
# without touching the database.
|
|
300
|
+
report.rescored = self.store.rescore_all(
|
|
301
|
+
self.half_life_days, dry_run=not self.apply
|
|
302
|
+
)
|
|
286
303
|
except Exception: # pragma: no cover - decay must never abort the pass
|
|
287
304
|
log.warning("dream: rescore_all failed; continuing", exc_info=True)
|
|
288
305
|
report.stages["decay"] = round((time.time() - t) * 1000, 1)
|
|
@@ -5110,10 +5110,18 @@ class MemoryStore:
|
|
|
5110
5110
|
blended = 0.35 * importance + 0.65 * recency
|
|
5111
5111
|
return max(0.0, min(1.0, blended))
|
|
5112
5112
|
|
|
5113
|
-
def rescore_all(self, half_life_days: float = 30.0
|
|
5113
|
+
def rescore_all(self, half_life_days: float = 30.0,
|
|
5114
|
+
dry_run: bool = False) -> int:
|
|
5114
5115
|
"""Recompute score for every memory using v2 (importance × recency
|
|
5115
5116
|
× usage × feedback). One row at a time keeps WAL writes small
|
|
5116
|
-
and lets us skip rows whose components didn't change.
|
|
5117
|
+
and lets us skip rows whose components didn't change.
|
|
5118
|
+
|
|
5119
|
+
``dry_run=True`` computes every new score but writes nothing,
|
|
5120
|
+
and returns how many rows *would* change. The dream pass uses
|
|
5121
|
+
it to honour its own "nothing is written unless apply=True"
|
|
5122
|
+
contract while still reporting the size of the decay job
|
|
5123
|
+
(audit 2026-10-25).
|
|
5124
|
+
"""
|
|
5117
5125
|
updated = 0
|
|
5118
5126
|
now = time.time()
|
|
5119
5127
|
with self._conn() as c:
|
|
@@ -5143,10 +5151,11 @@ class MemoryStore:
|
|
|
5143
5151
|
"SELECT score FROM memories WHERE id=?", (r["id"],)
|
|
5144
5152
|
).fetchone()
|
|
5145
5153
|
if cur is None or abs((cur["score"] or 0) - new) > 1e-3:
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5154
|
+
if not dry_run:
|
|
5155
|
+
c.execute(
|
|
5156
|
+
"UPDATE memories SET score=? WHERE id=?",
|
|
5157
|
+
(new, r["id"]),
|
|
5158
|
+
)
|
|
5150
5159
|
updated += 1
|
|
5151
5160
|
return updated
|
|
5152
5161
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loop-memory
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.17
|
|
4
4
|
Summary: A general-purpose, local memory system for every AI agent you run. Loop Memory auto-captures conversations from Codex / Claude / Hermes / OpenClaw, scores them by importance × recency × usage × feedback, distils them into a curated wiki, and serves everything from a single web UI.
|
|
5
5
|
Author: Loop Memory contributors <loop-memory@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -470,7 +470,10 @@ never-used stale row is expired. Age alone never decides — that is the
|
|
|
470
470
|
whole point of "In Defense of Forgotten" (arXiv 2605.27366).
|
|
471
471
|
|
|
472
472
|
The pass is a **dry run by default**: `--apply` (CLI) or `"apply": true`
|
|
473
|
-
(HTTP) is what actually writes.
|
|
473
|
+
(HTTP) is what actually writes. A dry run changes **no memory data** --
|
|
474
|
+
no expiry, no supersession, no rescore -- it only appends a
|
|
475
|
+
`cognitive_audit` row per proposed action (`action="suggest"`), which
|
|
476
|
+
is the reviewable plan the dashboard shows before you commit.
|
|
474
477
|
|
|
475
478
|
```bash
|
|
476
479
|
loop-memory dream --json # machine-readable dry run
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "loop-memory"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.17"
|
|
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"
|
|
@@ -8,7 +8,9 @@ contract:
|
|
|
8
8
|
"what did we believe back then?";
|
|
9
9
|
* **utility-aware, not age-only** -- a row that is past its deadline
|
|
10
10
|
but is recalled often is *protected*;
|
|
11
|
-
* **dry run by default** --
|
|
11
|
+
* **dry run by default** -- no memory *data* is changed unless
|
|
12
|
+
``apply=True`` (the only write a dry run performs is the
|
|
13
|
+
``cognitive_audit`` ``suggest`` row that records the plan);
|
|
12
14
|
* **audited** -- every decision lands in ``cognitive_audit``.
|
|
13
15
|
"""
|
|
14
16
|
from __future__ import annotations
|
|
@@ -84,6 +86,41 @@ class DreamPassTests(unittest.TestCase):
|
|
|
84
86
|
self.assertFalse(report.applied)
|
|
85
87
|
self.assertIsNone(self.store.get_memory(m.id).invalid_at)
|
|
86
88
|
|
|
89
|
+
def test_dry_run_does_not_touch_the_score_column(self) -> None:
|
|
90
|
+
"""Regression (audit 2026-10-25, fix): the decay stage used to
|
|
91
|
+
call ``rescore_all`` unconditionally, so a dry run still
|
|
92
|
+
rewrote ``memories.score`` for every row -- breaking the
|
|
93
|
+
"nothing is written unless apply=True" contract."""
|
|
94
|
+
m = self.store.upsert_memory(kind="fact", text="a fact worth scoring")
|
|
95
|
+
with self.store._conn() as c:
|
|
96
|
+
c.execute("UPDATE memories SET score=0.01 WHERE id=?", (m.id,))
|
|
97
|
+
before = self.store.get_memory(m.id).score
|
|
98
|
+
self.assertEqual(before, 0.01)
|
|
99
|
+
|
|
100
|
+
report = dream(self.store, apply=False)
|
|
101
|
+
self.assertEqual(
|
|
102
|
+
self.store.get_memory(m.id).score, before,
|
|
103
|
+
"a dry run must not rewrite the score column",
|
|
104
|
+
)
|
|
105
|
+
# ...but it still reports the size of the decay job.
|
|
106
|
+
self.assertGreaterEqual(report.rescored, 1)
|
|
107
|
+
|
|
108
|
+
def test_apply_does_rescore(self) -> None:
|
|
109
|
+
m = self.store.upsert_memory(kind="fact", text="a fact worth scoring")
|
|
110
|
+
with self.store._conn() as c:
|
|
111
|
+
c.execute("UPDATE memories SET score=0.01 WHERE id=?", (m.id,))
|
|
112
|
+
report = dream(self.store, apply=True)
|
|
113
|
+
self.assertGreaterEqual(report.rescored, 1)
|
|
114
|
+
self.assertNotEqual(self.store.get_memory(m.id).score, 0.01)
|
|
115
|
+
|
|
116
|
+
def test_rescore_all_dry_run_writes_nothing(self) -> None:
|
|
117
|
+
m = self.store.upsert_memory(kind="fact", text="a fact worth scoring")
|
|
118
|
+
with self.store._conn() as c:
|
|
119
|
+
c.execute("UPDATE memories SET score=0.01 WHERE id=?", (m.id,))
|
|
120
|
+
n = self.store.rescore_all(dry_run=True)
|
|
121
|
+
self.assertGreaterEqual(n, 1)
|
|
122
|
+
self.assertEqual(self.store.get_memory(m.id).score, 0.01)
|
|
123
|
+
|
|
87
124
|
def test_dry_run_reports_the_action(self) -> None:
|
|
88
125
|
self.store.upsert_memory(
|
|
89
126
|
kind="fact", text="the old build server is decommissioned",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|