loop-memory 0.4.13__tar.gz → 0.4.14__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.13 → loop_memory-0.4.14}/PKG-INFO +3 -1
- {loop_memory-0.4.13 → loop_memory-0.4.14}/README.md +2 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/diag.py +11 -88
- loop_memory-0.4.14/loop_memory/cli/commands/doctor.py +144 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/main.py +1 -1
- loop_memory-0.4.14/loop_memory/doctor.py +678 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/PKG-INFO +3 -1
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/SOURCES.txt +3 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/pyproject.toml +1 -1
- loop_memory-0.4.14/tests/test_doctor_2026_10_12.py +319 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/LICENSE +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/cognitive.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/read.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/rules.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/serve.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/cognitive.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/llm/openai_adapter.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/llm/providers.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/sdk.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/sdk_extensions.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/security/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/cognitive.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/system.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/storage/snapshot.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/storage/sqlite_store.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/setup.cfg +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_agent_memory_sdk.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_agents_init.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_audit_triggers_2026_09_27.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_busy_timeout_2026_09_24.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cache_cap_2026_09_23.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_audit_supersede.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_export_okf.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_init.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_memory_stats.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_recall_as_of.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_recall_paths.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_recall_verbose.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_rules.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_snapshot.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_export_okf.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_graph.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_healthz_readyz_2026_09_24.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_import_okf_2026_09_27.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_llm_providers.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_loop.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_memory_stats.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_package_version.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_path_safety_2026_09_23.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_recall_as_of.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_recall_budget_2026_10_05.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_recall_paths.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_recall_provenance.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_repo_framing.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_routes_audit_2026_09_13.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_routes_audit_2026_09_20.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_short_query.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_snapshot.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_store.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_subgraph_hops_2026_09_23.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_supersession_chain.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_universal_memory.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/tests/test_wiki_export_escape.py +0 -0
- {loop_memory-0.4.13 → loop_memory-0.4.14}/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.14
|
|
4
4
|
Summary: A general-purpose, local memory system for every AI agent you run. Loop Memory auto-captures conversations from Codex / Claude / Hermes / OpenClaw, scores them by importance × recency × usage × feedback, distils them into a curated wiki, and serves everything from a single web UI.
|
|
5
5
|
Author: Loop Memory contributors <loop-memory@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -66,6 +66,8 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
> **What's new in 0.4.14** — **`loop-memory doctor` deep store-health check** (audit 2026-10-12). Ten diagnostic checks — `db_exists`, `db_integrity`, `schema_version`, `audit_triggers`, `audit_log_writable`, `audit_log_size`, `hooks_installed`, `version_consistent`, `wiki_no_orphans`, `recall_paths_works` — report `ok`/`info`/`warn`/`error` for the live `MemoryStore`. `--fix` auto-repairs the `audit_triggers` check. `--json` emits a stable shape for CI. `--list` prints the check function names without touching the store. `--verbose` shows `info`-level checks. Exit code `1` on any `error` status, `0` otherwise. Source: `cortexkit/magic-context doctor` auto-diagnostic pattern (MIT, 2026-10-12) + `dsh-memento` write-approval-gate pattern (Apache-2.0, 2026-10-12). Stdlib-only — we re-implement from scratch against our own `MemoryStore`. Also fixed a Python 3.14 bug: chained `store._conn().__enter__()` immediately triggers the contextmanager's `finally` and closes the connection mid-query — both call sites in `doctor.py` now use explicit `with` form. 27 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0414---2026-10-12)
|
|
70
|
+
|
|
69
71
|
> **What's new in 0.4.13** — **recall token / char budget cap** + **unified `level=0|1|2` tiered-loader knob** (audit 2026-10-05). New `max_chars` parameter on `MemoryStore.recall()` / `recall_paths()` / `recall_hybrid()` / `recall_as_of()` caps the *total* payload across all returned hits — when the budget is exceeded the lowest-ranked hit shrinks first so the top-scoring hits always keep their full body. `recall()` / `recall_paths()` / `recall_as_of()` now also accept `level=0|1|2` so a caller can ask for the L0 abstract ladder rung inline (titles + tags + abstract only, never the full `text` / `body`) without routing through `recall_paths()` separately. Both knobs are wired through the HTTP routes (`GET /api/recall?max_chars=&level=`, `GET /api/recall/outline?max_chars=`), the CLI (`loop-memory recall --max-chars N --level 0|1|2`, `recall-paths --max-chars N`), the SDK (`LoopMemoryClient.recall(max_chars=, level=)`), and the MCP `tool_recall` server. Source: `vectorize-io/hindsight v0.10.2` "concise extraction by default" (Apache-2.0, 2026-09-29) + `aiming-lab/SimpleMem` ICML'26 semantic-lossless-compression (Apache-2.0, 2026-10-04) + `volcengine/OpenViking` tiered-loader (AGPLv3, 2026-10-02 — *pattern only, no code copied*). 26 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0413---2026-10-05)
|
|
70
72
|
|
|
71
73
|
> **What's new in 0.4.12** — **OKF bundle import** + **DB-level audit enforcement** (audit 2026-09-27). The new `loop-memory import-okf <in_dir>` (and `POST /api/import/okf`) is the symmetric counterpart to `export-okf` (0.4.9) — bring an OKF v0.2 bundle back into Loop Memory as wiki pages, with idempotent upsert on `(scope, slug)`, optional `--dry-run` and `--skip-conflicts`. Source: `thecolourfoundation/rune` HN launch (MIT, 2026-09-25) + `okf-memory/okf-agent-memory` Issues #12-#15 + `Deja-Vu` (MIT, 2026-09-24) + Google OKF v0.2 spec (Apache-2.0, 2026-09-01). The new `MemoryStore._install_audit_triggers()` installs 12 SQLite triggers on `memories` / `wiki_pages` / `relations` / `consolidation_runs` so the `audit_log` table cannot be skipped — even a raw `sqlite3` writer that bypasses the Python wrapper still appends an audit row with `actor='db_trigger'`. New `MemoryStore.list_audit_log()` reads the log; `GET /api/stats` reports `audit_log` + `audit_triggers` counts. Source: `cogstate-eng/memory-eternal v0.4.3` `enforceAudit()` pattern (MIT, 2026-09-23). 31 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0412---2026-09-27)
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
> **What's new in 0.4.14** — **`loop-memory doctor` deep store-health check** (audit 2026-10-12). Ten diagnostic checks — `db_exists`, `db_integrity`, `schema_version`, `audit_triggers`, `audit_log_writable`, `audit_log_size`, `hooks_installed`, `version_consistent`, `wiki_no_orphans`, `recall_paths_works` — report `ok`/`info`/`warn`/`error` for the live `MemoryStore`. `--fix` auto-repairs the `audit_triggers` check. `--json` emits a stable shape for CI. `--list` prints the check function names without touching the store. `--verbose` shows `info`-level checks. Exit code `1` on any `error` status, `0` otherwise. Source: `cortexkit/magic-context doctor` auto-diagnostic pattern (MIT, 2026-10-12) + `dsh-memento` write-approval-gate pattern (Apache-2.0, 2026-10-12). Stdlib-only — we re-implement from scratch against our own `MemoryStore`. Also fixed a Python 3.14 bug: chained `store._conn().__enter__()` immediately triggers the contextmanager's `finally` and closes the connection mid-query — both call sites in `doctor.py` now use explicit `with` form. 27 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0414---2026-10-12)
|
|
26
|
+
|
|
25
27
|
> **What's new in 0.4.13** — **recall token / char budget cap** + **unified `level=0|1|2` tiered-loader knob** (audit 2026-10-05). New `max_chars` parameter on `MemoryStore.recall()` / `recall_paths()` / `recall_hybrid()` / `recall_as_of()` caps the *total* payload across all returned hits — when the budget is exceeded the lowest-ranked hit shrinks first so the top-scoring hits always keep their full body. `recall()` / `recall_paths()` / `recall_as_of()` now also accept `level=0|1|2` so a caller can ask for the L0 abstract ladder rung inline (titles + tags + abstract only, never the full `text` / `body`) without routing through `recall_paths()` separately. Both knobs are wired through the HTTP routes (`GET /api/recall?max_chars=&level=`, `GET /api/recall/outline?max_chars=`), the CLI (`loop-memory recall --max-chars N --level 0|1|2`, `recall-paths --max-chars N`), the SDK (`LoopMemoryClient.recall(max_chars=, level=)`), and the MCP `tool_recall` server. Source: `vectorize-io/hindsight v0.10.2` "concise extraction by default" (Apache-2.0, 2026-09-29) + `aiming-lab/SimpleMem` ICML'26 semantic-lossless-compression (Apache-2.0, 2026-10-04) + `volcengine/OpenViking` tiered-loader (AGPLv3, 2026-10-02 — *pattern only, no code copied*). 26 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0413---2026-10-05)
|
|
26
28
|
|
|
27
29
|
> **What's new in 0.4.12** — **OKF bundle import** + **DB-level audit enforcement** (audit 2026-09-27). The new `loop-memory import-okf <in_dir>` (and `POST /api/import/okf`) is the symmetric counterpart to `export-okf` (0.4.9) — bring an OKF v0.2 bundle back into Loop Memory as wiki pages, with idempotent upsert on `(scope, slug)`, optional `--dry-run` and `--skip-conflicts`. Source: `thecolourfoundation/rune` HN launch (MIT, 2026-09-25) + `okf-memory/okf-agent-memory` Issues #12-#15 + `Deja-Vu` (MIT, 2026-09-24) + Google OKF v0.2 spec (Apache-2.0, 2026-09-01). The new `MemoryStore._install_audit_triggers()` installs 12 SQLite triggers on `memories` / `wiki_pages` / `relations` / `consolidation_runs` so the `audit_log` table cannot be skipped — even a raw `sqlite3` writer that bypasses the Python wrapper still appends an audit row with `actor='db_trigger'`. New `MemoryStore.list_audit_log()` reads the log; `GET /api/stats` reports `audit_log` + `audit_triggers` counts. Source: `cogstate-eng/memory-eternal v0.4.3` `enforceAudit()` pattern (MIT, 2026-09-23). 31 new regression cases. [Full CHANGELOG →](./CHANGELOG.md#0412---2026-09-27)
|
|
@@ -133,95 +133,18 @@ def _detect_watcher() -> bool:
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def run_doctor(_args) -> int:
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
"""`loop-memory doctor` — deep store-health check.
|
|
137
|
+
|
|
138
|
+
Audit 2026-10-12. Delegates to :mod:`loop_memory.doctor` which runs
|
|
139
|
+
a small library of health checks against the live
|
|
140
|
+
`MemoryStore` and renders a human-readable report. Exits
|
|
141
|
+
non-zero if any check is in 'error' state. Source:
|
|
142
|
+
cortexkit/magic-context doctor (MIT, 2026-10-12) +
|
|
143
|
+
PerryLink/dsh-memento dsh-plugin-doctor (Apache-2.0,
|
|
144
|
+
2026-10-12) — pattern only.
|
|
140
145
|
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
cli_path = shutil.which("loop-memory")
|
|
144
|
-
print(_dot(cli_path is not None,
|
|
145
|
-
f"CLI on PATH: {'yes (' + cli_path + ')' if cli_path else 'no'}"))
|
|
146
|
-
# 2) Database
|
|
147
|
-
db_path = Path(DEFAULT_DB)
|
|
148
|
-
print(_dot(db_path.exists(),
|
|
149
|
-
f"database: {db_path}" + ("" if db_path.exists() else " (will be created on first run)")))
|
|
150
|
-
# 3) Server
|
|
151
|
-
server_running = False
|
|
152
|
-
try:
|
|
153
|
-
import urllib.request
|
|
154
|
-
with urllib.request.urlopen("http://127.0.0.1:7767/api/stats", timeout=1) as r:
|
|
155
|
-
server_running = r.status == 200
|
|
156
|
-
except Exception:
|
|
157
|
-
server_running = False
|
|
158
|
-
print(_dot(server_running,
|
|
159
|
-
"web UI server: http://127.0.0.1:7767" + (" (running)" if server_running else " (not running — start with `loop-memory serve &`)")))
|
|
160
|
-
|
|
161
|
-
# 4) Clients
|
|
162
|
-
print()
|
|
163
|
-
print("clients:")
|
|
164
|
-
clients = _detect_clients()
|
|
165
|
-
for name in ("codex", "claude", "hermes", "openclaw"):
|
|
166
|
-
info = clients[name]
|
|
167
|
-
if name == "openclaw":
|
|
168
|
-
print(_dot(info["installed"],
|
|
169
|
-
f" openclaw: {'installed' if info['installed'] else 'not installed'}"))
|
|
170
|
-
if info["installed"]:
|
|
171
|
-
paths = ", ".join(Path(p).name for p in info["watch_paths"]) or "no transcript paths found"
|
|
172
|
-
print(f" {paths}")
|
|
173
|
-
print(_dot(_detect_watcher(),
|
|
174
|
-
" watcher running" if _detect_watcher() else " watcher not running — start with `loop-memory openclaw-setup`"))
|
|
175
|
-
else:
|
|
176
|
-
cfg = info["config"]
|
|
177
|
-
installed = info["installed"]
|
|
178
|
-
cfg_str = f"@ {cfg}" if installed else ""
|
|
179
|
-
print(_dot(installed,
|
|
180
|
-
f" {name}: {'installed' if installed else 'not installed'} {cfg_str}"))
|
|
181
|
-
if installed:
|
|
182
|
-
print(_dot(info["mcp_configured"],
|
|
183
|
-
f" MCP server wired: {'yes' if info['mcp_configured'] else 'no — run `loop-memory install-hooks`'}"))
|
|
184
|
-
|
|
185
|
-
# 5) LLM provider
|
|
186
|
-
print()
|
|
187
|
-
print("LLM provider:")
|
|
188
|
-
try:
|
|
189
|
-
from ...storage.sqlite_store import MemoryStore
|
|
190
|
-
from ...llm.providers import default_config
|
|
191
|
-
from ...security import backend_display_name, has_secret
|
|
192
|
-
store = MemoryStore(DEFAULT_DB)
|
|
193
|
-
cfg = store.get_setting("llm_consolidator", default_config())
|
|
194
|
-
provider = cfg.get("provider") or "echo"
|
|
195
|
-
print(f" provider: {provider}")
|
|
196
|
-
print(f" model: {cfg.get('model', '—')}")
|
|
197
|
-
print(f" secret backend: {backend_display_name()}")
|
|
198
|
-
if provider != "echo":
|
|
199
|
-
from ...security import account_for
|
|
200
|
-
account = cfg.get("api_key_account") or account_for(provider)
|
|
201
|
-
has = has_secret(account)
|
|
202
|
-
print(_dot(has,
|
|
203
|
-
f" API key: {'configured' if has else 'MISSING — open the web UI → ⚙ Model → paste your key'}"))
|
|
204
|
-
except Exception as e:
|
|
205
|
-
print(_dot(False, f" could not read LLM config: {e}"))
|
|
206
|
-
|
|
207
|
-
# 6) Data summary
|
|
208
|
-
print()
|
|
209
|
-
print("data:")
|
|
210
|
-
try:
|
|
211
|
-
from ...storage.sqlite_store import MemoryStore
|
|
212
|
-
store = MemoryStore(DEFAULT_DB)
|
|
213
|
-
n_mem = store.count_memories()
|
|
214
|
-
n_sess = store.count_sessions()
|
|
215
|
-
n_wiki = store.count_wiki_pages()
|
|
216
|
-
n_ent = store.count_entities()
|
|
217
|
-
print(f" {n_mem} memories · {n_sess} sessions · {n_wiki} wiki pages · {n_ent} entities")
|
|
218
|
-
except Exception as e:
|
|
219
|
-
print(f" could not read counters: {e}")
|
|
220
|
-
|
|
221
|
-
print()
|
|
222
|
-
print(_DIM + "Tip: run `loop-memory status` for a one-screen summary, "
|
|
223
|
-
"or `loop-memory install-hooks` to auto-configure your CLIs." + _RESET)
|
|
224
|
-
return 0
|
|
146
|
+
from . import doctor as _doctor_cli
|
|
147
|
+
return _doctor_cli.run_doctor(list(_args))
|
|
225
148
|
|
|
226
149
|
|
|
227
150
|
def run_status(_args) -> int:
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""``loop-memory doctor`` subcommand.
|
|
2
|
+
|
|
3
|
+
Audit 2026-10-12. Wraps :mod:`loop_memory.doctor` and renders the
|
|
4
|
+
result for a human or as JSON. See ``loop_memory/doctor.py`` for
|
|
5
|
+
the check library and the source attribution.
|
|
6
|
+
|
|
7
|
+
Usage examples::
|
|
8
|
+
|
|
9
|
+
loop-memory doctor # human-readable report
|
|
10
|
+
loop-memory doctor --json # machine-readable report
|
|
11
|
+
loop-memory doctor --verbose # also show INFO-level checks
|
|
12
|
+
loop-memory doctor --fix # auto-repair auto_fixable checks
|
|
13
|
+
loop-memory doctor --db /path/to/store.db # run against a specific DB
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from .._common import die
|
|
23
|
+
from ... import doctor as _doctor
|
|
24
|
+
from ...storage.sqlite_store import MemoryStore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
28
|
+
p = argparse.ArgumentParser(
|
|
29
|
+
prog="loop-memory doctor",
|
|
30
|
+
description=(
|
|
31
|
+
"Run a small library of health checks against the loop-memory "
|
|
32
|
+
"store. Reports ok / info / warn / error per check and exits "
|
|
33
|
+
"non-zero if any check is in 'error' state. Source: cortexkit/"
|
|
34
|
+
"magic-context doctor (MIT, 2026-10-12) + PerryLink/dsh-memento "
|
|
35
|
+
"dsh-plugin-doctor (Apache-2.0, 2026-10-12) — pattern only."
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
p.add_argument(
|
|
39
|
+
"--db",
|
|
40
|
+
default=None,
|
|
41
|
+
help="path to the SQLite file (default: $LOOP_MEMORY_DB or ~/.loop_memory/loop_memory.db)",
|
|
42
|
+
)
|
|
43
|
+
p.add_argument(
|
|
44
|
+
"--json",
|
|
45
|
+
action="store_true",
|
|
46
|
+
help="emit the result as JSON for scripting",
|
|
47
|
+
)
|
|
48
|
+
p.add_argument(
|
|
49
|
+
"--verbose",
|
|
50
|
+
action="store_true",
|
|
51
|
+
help="also show INFO-level checks in human output",
|
|
52
|
+
)
|
|
53
|
+
p.add_argument(
|
|
54
|
+
"--fix",
|
|
55
|
+
action="store_true",
|
|
56
|
+
help="auto-repair any auto_fixable check that fails (idempotent; safe to re-run)",
|
|
57
|
+
)
|
|
58
|
+
p.add_argument(
|
|
59
|
+
"--list",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="list every check the doctor would run and exit (no DB access)",
|
|
62
|
+
)
|
|
63
|
+
return p
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _resolve_db_path(arg: str | None) -> Path:
|
|
67
|
+
"""Pick the store path: ``--db`` wins, else ``$LOOP_MEMORY_DB``,
|
|
68
|
+
else the default ``~/.loop_memory/loop_memory.db``."""
|
|
69
|
+
if arg:
|
|
70
|
+
return Path(arg).expanduser()
|
|
71
|
+
import os
|
|
72
|
+
env = os.environ.get("LOOP_MEMORY_DB")
|
|
73
|
+
if env:
|
|
74
|
+
return Path(env).expanduser()
|
|
75
|
+
from .._common import default_db_path
|
|
76
|
+
return Path(default_db_path())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_doctor(args: list[str]) -> int:
|
|
80
|
+
parser = _build_parser()
|
|
81
|
+
parsed = parser.parse_args(args)
|
|
82
|
+
|
|
83
|
+
if parsed.list:
|
|
84
|
+
for fn in _doctor.CHECKS:
|
|
85
|
+
print(fn.__name__)
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
db_path = _resolve_db_path(parsed.db)
|
|
89
|
+
if not db_path.exists():
|
|
90
|
+
msg = (
|
|
91
|
+
f"loop-memory doctor: store file not found at {db_path}. "
|
|
92
|
+
"Run `loop-memory init` first, or pass --db to point at an existing store."
|
|
93
|
+
)
|
|
94
|
+
if parsed.json:
|
|
95
|
+
print(_doctor.render_json([
|
|
96
|
+
_doctor.DoctorCheck(
|
|
97
|
+
name="db_exists",
|
|
98
|
+
status=_doctor.STATUS_ERROR,
|
|
99
|
+
message=msg,
|
|
100
|
+
fix_hint="run `loop-memory init` to create the store",
|
|
101
|
+
),
|
|
102
|
+
]))
|
|
103
|
+
return 1
|
|
104
|
+
return die(msg)
|
|
105
|
+
|
|
106
|
+
# ``MemoryStore(<missing_path>)`` creates a fresh DB on open.
|
|
107
|
+
# We want doctor to FAIL when the path is missing rather than
|
|
108
|
+
# silently materialise an empty store, so the missing-path
|
|
109
|
+
# check above is the contract.
|
|
110
|
+
try:
|
|
111
|
+
store = MemoryStore(str(db_path))
|
|
112
|
+
except Exception as e:
|
|
113
|
+
msg = f"loop-memory doctor: could not open store at {db_path}: {e}"
|
|
114
|
+
if parsed.json:
|
|
115
|
+
print(_doctor.render_json([
|
|
116
|
+
_doctor.DoctorCheck(
|
|
117
|
+
name="db_exists",
|
|
118
|
+
status=_doctor.STATUS_ERROR,
|
|
119
|
+
message=msg,
|
|
120
|
+
),
|
|
121
|
+
]))
|
|
122
|
+
return 1
|
|
123
|
+
return die(msg)
|
|
124
|
+
|
|
125
|
+
checks = _doctor.run_checks(store)
|
|
126
|
+
|
|
127
|
+
if parsed.fix:
|
|
128
|
+
# Apply auto-fixes in a single pass. We re-run the checks
|
|
129
|
+
# so the user sees the post-fix state in the same command.
|
|
130
|
+
for c in checks:
|
|
131
|
+
if c.status in (_doctor.STATUS_ERROR, _doctor.STATUS_WARN) and c.auto_fixable:
|
|
132
|
+
post = _doctor.apply_fix(store, c.name)
|
|
133
|
+
# Replace the failing check with the post-fix result
|
|
134
|
+
# so the human / JSON output reflects reality.
|
|
135
|
+
idx = checks.index(c)
|
|
136
|
+
checks[idx] = post
|
|
137
|
+
checks = _doctor.run_checks(store)
|
|
138
|
+
|
|
139
|
+
if parsed.json:
|
|
140
|
+
sys.stdout.write(_doctor.render_json(checks))
|
|
141
|
+
else:
|
|
142
|
+
sys.stdout.write(_doctor.render_human(checks, verbose=parsed.verbose))
|
|
143
|
+
|
|
144
|
+
return _doctor.exit_code(checks)
|
|
@@ -149,7 +149,7 @@ COMMAND_HELP: dict[str, str] = {
|
|
|
149
149
|
"consolidate": "loop-memory consolidate # rescore + GC + dedupe.",
|
|
150
150
|
"consolidate-now": "loop-memory consolidate-now # ask the running server to trigger a pass right now.",
|
|
151
151
|
"digest": "loop-memory digest [--out PATH] # compact knowledge digest for AGENTS.md.",
|
|
152
|
-
"doctor": "loop-memory doctor #
|
|
152
|
+
"doctor": "loop-memory doctor [--json] [--verbose] [--fix] [--db PATH] # deep store-health check (db integrity + audit triggers + hooks + version + wiki orphans). Auto-repair with --fix (audit 2026-10-12; pattern adopted from cortexkit/magic-context + dsh-memento).",
|
|
153
153
|
"export": "loop-memory export [--out FILE | <out_dir>] # markdown dump or v7 bundle export.",
|
|
154
154
|
"export-bundle": "loop-memory export-bundle <out_dir> # explicit v7 bundle alias.",
|
|
155
155
|
"flush": "loop-memory flush # drop volatile caches; keep persisted data.",
|