loop-memory 0.4.19__tar.gz → 0.4.21__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.19 → loop_memory-0.4.21}/PKG-INFO +73 -77
- {loop_memory-0.4.19 → loop_memory-0.4.21}/README.md +72 -76
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/serve.py +19 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/doctor.py +75 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/sdk.py +67 -5
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/sdk_extensions.py +55 -13
- loop_memory-0.4.21/loop_memory/security/__init__.py +39 -0
- loop_memory-0.4.21/loop_memory/security/transport.py +339 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/PKG-INFO +73 -77
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/SOURCES.txt +2 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/pyproject.toml +1 -1
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_agent_memory_sdk.py +5 -1
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_doctor_2026_10_12.py +8 -7
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_packaging_2026_11_01.py +95 -5
- loop_memory-0.4.21/tests/test_transport_security_2026_11_08.py +456 -0
- loop_memory-0.4.19/loop_memory/security/__init__.py +0 -20
- {loop_memory-0.4.19 → loop_memory-0.4.21}/LICENSE +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/backends/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/backends/embedding.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/backends/sentence_embedder.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/backends/vector_store.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/bench/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/bench/fixtures/default.json +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/bench/runner.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/_common.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/bench.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/cognitive.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/diag.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/doctor.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/graph.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/hooks.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/read.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/rules.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/commands/write.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/cli/main.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/decorators.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/engine/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/engine/loop.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/engine/reflect.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/examples/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/examples/demo.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/export/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/export/memory_md.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/graph/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/graph/build.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/graph/extract.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/ingest/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/ingest/loader.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/ingest/pipeline.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/cognitive.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/compact.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/consolidate.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/contradiction.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/dream.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/evolution.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/graph.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/llm_compact_pass.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/llm_consolidate.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/jobs/scheduler.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/llm/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/llm/base.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/llm/openai_adapter.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/llm/providers.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/mcp/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/memory/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/memory/types.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/privacy/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/privacy/private.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/privacy/redact.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/py.typed +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/security/secrets.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/app.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/handlers.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/_shared.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/admin.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/cognitive.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/export.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/graph.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/insights.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/memories.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/sessions.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/system.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/routes/wiki.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/css/app.css +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/css/layout.css +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/i18n/en.json +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/i18n/zh.json +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/img/logo-light.svg +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/img/logo-mark.svg +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/img/logo.svg +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/index.html +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/App.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/api.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Dashboard.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Diagnostic.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/IngestPopover.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/KnowledgeGraph.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/RingMeter.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/RunStrip.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/SectionTitle.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Settings.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Sidebar.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Tabs.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Timeline.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Toast.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/TopBar.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/Wiki.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/components/WikiEditor.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/composables/useI18n.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/lib/sanitize.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/lib/vue.esm-browser.prod.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/main.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/static/js/store.js +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/serve/watcher.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/storage/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/storage/lexical.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/storage/retrieval.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/storage/snapshot.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/storage/sqlite_store.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/wiki/__init__.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/wiki/backfill.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/wiki/classifier.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/wiki/prompts.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory/wiki/scope.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/dependency_links.txt +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/entry_points.txt +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/requires.txt +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/loop_memory.egg-info/top_level.txt +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/setup.cfg +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_admin_ingest_route.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_agent_memory_api.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_agents_init.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_audit_triggers_2026_09_27.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_auth_token_rotate.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_bench_2026_11_01.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_busy_timeout_2026_09_24.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cache_cap_2026_09_23.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_audit_supersede.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_export_okf.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_init.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_memory_stats.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_recall_as_of.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_recall_paths.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_recall_verbose.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_rules.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_snapshot.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_v7.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_cli_version.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_content_hash_dedup_2026_10_18.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_contradictions.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_decorators_2026_10_18.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_dream_2026_10_25.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_dream_cli_http_2026_10_25.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_evolution.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_evolution_quality.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_expanduser.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_export_ask.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_export_okf.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_graph.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_healthz_readyz_2026_09_24.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_import_okf_2026_09_27.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_ingest.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_latest_only_2026_10_25.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_lexical_ranking_2026_11_01.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_llm_consolidator.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_llm_fingerprint_not_persisted.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_llm_providers.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_llm_test_endpoint.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_loop.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_mcp.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_memories_pagination.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_memory_stats.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_openclaw_loader.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_package_version.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_path_safety_2026_09_23.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_recall_as_of.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_recall_budget_2026_10_05.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_recall_paths.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_recall_provenance.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_recall_validity_http_2026_10_18.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_reflection.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_repo_framing.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_retrieval_channels_2026_10_18.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_routes_audit_2026_09_13.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_routes_audit_2026_09_20.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_score_api.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_scoring_v2.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_secrets.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_serve_app.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_serve_handlers.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_session_order.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_short_query.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_snapshot.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_store.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_subgraph_hops_2026_09_23.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_subject_key_2026_10_25.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_summarization.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_supersession_chain.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_universal_memory.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_vector_store.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_watcher.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_wiki_classifier.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/tests/test_wiki_export_escape.py +0 -0
- {loop_memory-0.4.19 → loop_memory-0.4.21}/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.21
|
|
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
|
|
@@ -67,80 +67,39 @@ Dynamic: license-file
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
> **What's new in 0.4.
|
|
71
|
-
|
|
72
|
-
>
|
|
73
|
-
>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
[
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
release: a hard cap on the in-process LLM response cache
|
|
104
|
-
(was unbounded; now 256 entries with FIFO eviction) and a
|
|
105
|
-
fix for ``subgraph_for(max_hops=...)`` which used to ignore
|
|
106
|
-
the argument and always walk one hop (now BFS up to N hops,
|
|
107
|
-
hard-capped at 8). 29 new regression cases. [Full
|
|
108
|
-
CHANGELOG →](./CHANGELOG.md#0410---2026-09-23)
|
|
109
|
-
|
|
110
|
-
> **What's new in 0.4.9** — **OKF v0.2 export** + **bi-temporal `as_of` recall**. `loop-memory export-okf <dir>` writes an [Open Knowledge Format](https://github.com/okf/SPEC) v0.2 bundle — one `.md` file per wiki page with YAML frontmatter + an `index.md` index — so a Loop Memory store can be handed to any OKF-aware tool (Grep, Obsidian, akitaonrails/ai-memory, okf-agent-memory, ...) without re-parsing the body (audit 2026-09-20, Google OKF v0.2 spec + akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory, all Apache-2.0/MIT, 2026-09-01 → 2026-09-06). `loop-memory recall <q> --as-of <ISO|epoch>` answers the bi-temporal question "what did we know about this query at that moment?" — a memory written after the moment is invisible, and a superseded memory walks its chain to find whether the invalidation had happened by then (audit 2026-09-20, loomcycle v1.33-v1.49 RFCs BL/BS/BU/BV/BW, Apache-2.0). Both shapes ship with HTTP routes (`POST /api/export/okf`, `GET /api/recall?as_of=...`). 34 new regression cases pin both shapes. [Full CHANGELOG →](./CHANGELOG.md#049---2026-09-20)
|
|
111
|
-
|
|
112
|
-
> **What's new in 0.4.8** — **L0 outline recall** +
|
|
113
|
-
> **per-agent identity bootstrap**. `loop-memory recall-paths <q>`
|
|
114
|
-
> (and the new `recall --outline` flag) returns only `id` +
|
|
115
|
-
> `abstract` + `score` + `why` for each hit — never the full
|
|
116
|
-
> body — so an agent can identify the top candidates cheaply
|
|
117
|
-
> before deciding which bodies to fetch (audit 2026-09-13,
|
|
118
|
-
> tigerless-labs/agent-memory v0.3.0 recall-ladder pattern).
|
|
119
|
-
> `loop-memory init --agent <name>` registers an agent in the
|
|
120
|
-
> new `agents` table and (with `--install-hooks`) also wires
|
|
121
|
-
> the MCP + SessionStart hooks for codex/claude/hermes; the
|
|
122
|
-
> index is bumped on every `install-hooks` run so a user can
|
|
123
|
-
> always answer "which CLIs are wired into this store?" with
|
|
124
|
-
> one SQL query (audit 2026-09-13, Mem0 CLI `init --agent`
|
|
125
|
-
> pattern). Both shapes ship with HTTP routes
|
|
126
|
-
> (`/api/recall/outline`, `/api/agents`, `/api/init/agent`).
|
|
127
|
-
> 57 new regression cases pin both shapes. [Full
|
|
128
|
-
> changelog →](CHANGELOG.md)
|
|
129
|
-
>
|
|
130
|
-
> **What's new in 0.4.7** — **per-memory lifetime stats** +
|
|
131
|
-
> **portable SQLite snapshot**. `loop-memory memory-stats <id>`
|
|
132
|
-
> returns a flat dict (recall_count, positive, negative, age,
|
|
133
|
-
> etc.) so you can ask "is anyone still using this memory?"
|
|
134
|
-
> without cracking open the SQLite file (audit 2026-09-06,
|
|
135
|
-
> agentmemory v1.3.0 pattern). `loop-memory snapshot <out>` /
|
|
136
|
-
> `loop-memory restore <in>` move a single-file, lossless
|
|
137
|
-
> snapshot of the entire store — including recall-quality
|
|
138
|
-
> signals and the supersession chain — to another machine
|
|
139
|
-
> (audit 2026-09-06, codexa-memory v0.2.0 pattern). Both shapes
|
|
140
|
-
> ship with HTTP routes (`/api/memories/{id}/stats`,
|
|
141
|
-
> `/api/snapshot`, `/api/snapshot/restore`). 33 new regression
|
|
142
|
-
> cases pin both shapes.
|
|
143
|
-
> [Full changelog →](CHANGELOG.md)
|
|
70
|
+
> **What's new in 0.4.21** — **the remote SDK can finally authenticate, and it refuses to send memory data over a plaintext connection** (audit 2026-11-08, weekly ecosystem sync). The audit for this release (2026-11-08) turned up a real hole rather than a hypothetical one. `MemoryClient.http(base_url=...)` had **no transport guard at all**: point it at `http://mem.example.com` and it opened a plain socket and shipped the user's memory text across the network — and because the HTTP backend had **no way to send the bearer token** (`sqlite_store.py` even carried the comment *"header when `MemoryClient.http(..., token=...)` is given"* for a parameter that did not exist), the only way for a remote client to talk to a token-protected `loop-memory serve` was to turn auth **off** — exactly the configuration in which both the memory text and the token cross the network in cleartext. Two upstream projects closed the same hole in the same week: `rohitg00/agentmemory` v0.9.32 (2026-11-04, Apache-2.0) made *"hooks send nothing to a remote plain `http://` server; use HTTPS for a remote server"* a **breaking change**, and `topoteretes/cognee` v1.6.7 (2026-11-06, Apache-2.0) refuses *"server file paths over HTTP"* by default — both are pattern references only, no code copied. **What shipped:** a new `loop_memory.security.transport` that classifies a base URL and refuses plaintext `http://` to a **non-loopback** host (plaintext to a **loopback** address stays allowed — that traffic never leaves the host, and it is the default deployment), refuses a URL with embedded credentials, and refuses an unsupported scheme; `Authorization: Bearer` support in the SDK and in the shared `http_*_json` helpers, so the documented remote path works against a protected server instead of requiring auth to be turned off; a redirect guard that refuses an `https://` → `http://` downgrade (urllib's stock handler follows it, which would undo the TLS guarantee one hop from the server) and refuses a cross-origin redirect while a token is attached (urllib replays the `Authorization` header onto the redirect request); and the server half — `loop-memory serve` records its bind address and a new `loop-memory doctor` check (`plaintext_exposure`) warns when the API is reachable from the network over plaintext. The rule is about the *destination*, not the scheme: a private tunnel or a VPN'd LAN is a legitimate deployment, so `allow_insecure=True` (or `LOOP_MEMORY_ALLOW_INSECURE=1`) exists — but it is never silent, it warns on every construction. 37 new regression cases; suite at **1041 passed / 4 skipped**. See [`docs/transport-security.md`](./docs/transport-security.md) for the full contract. [Full CHANGELOG →](./CHANGELOG.md#0421---2026-11-08)
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary><b>Older releases</b> — 0.4.20 and earlier, one line each (the CHANGELOG has the full detail)</summary>
|
|
74
|
+
|
|
75
|
+
| Version | Date | What changed | Full notes |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| [0.4.20](./CHANGELOG.md#0420---2026-11-01) | 2026-11-01 | The packaging test works on the oldest supported Python (no `tomllib` on 3.10) |
|
|
78
|
+
| [0.4.19](./CHANGELOG.md#0419---2026-11-01) | 2026-11-01 | The wheel ships the whole dashboard and the benchmark fixture (recursive package-data globs) |
|
|
79
|
+
| [0.4.18](./CHANGELOG.md#0418---2026-11-01) | 2026-11-01 | The BM25 channel no longer publishes a score that collapses to zero — plus a retrieval benchmark |
|
|
80
|
+
| [0.4.17](./CHANGELOG.md#0417---2026-10-25) | 2026-10-25 | A dry-run dream pass no longer writes to the database |
|
|
81
|
+
| [0.4.16](./CHANGELOG.md#0416---2026-10-25) | 2026-10-25 | Subject-key logical fact slots, `latest_only` recall, and the sleep-time "dream" pass |
|
|
82
|
+
| [0.4.15](./CHANGELOG.md#0415---2026-10-18) | 2026-10-18 | Content-hash dedup, bi-temporal validity window, evidence chain |
|
|
83
|
+
| [0.4.14](./CHANGELOG.md#0414---2026-10-12) | 2026-10-12 | `loop-memory doctor` — a deep store-health check |
|
|
84
|
+
| [0.4.13](./CHANGELOG.md#0413---2026-10-05) | 2026-10-05 | Recall token / char budget cap and a unified `level=0|1|2` tiered-loader knob |
|
|
85
|
+
| [0.4.12](./CHANGELOG.md#0412---2026-09-27) | 2026-09-27 | OKF bundle import and DB-level audit enforcement (12 SQLite triggers) |
|
|
86
|
+
| [0.4.11](./CHANGELOG.md#0411---2026-09-24) | 2026-09-24 | Operational probes (`/api/healthz`, `/api/readyz`) and the SQLite write-contention fix |
|
|
87
|
+
| [0.4.10](./CHANGELOG.md#0410---2026-09-23) | 2026-09-23 | Path-safety guard for the five export / snapshot endpoints, LLM cache cap, `subgraph_for(max_hops=)` |
|
|
88
|
+
| [0.4.9](./CHANGELOG.md#049---2026-09-20) | 2026-09-20 | OKF v0.2 export and bi-temporal `as_of` recall |
|
|
89
|
+
| [0.4.8](./CHANGELOG.md#048---2026-09-13) | 2026-09-13 | L0 outline recall and per-agent identity bootstrap |
|
|
90
|
+
| [0.4.7](./CHANGELOG.md#047---2026-09-06) | 2026-09-06 | Per-memory lifetime stats and a portable SQLite snapshot |
|
|
91
|
+
| [0.4.6](./CHANGELOG.md#046---2026-08-30) | 2026-08-30 | The memory supersession chain (no hard deletes) |
|
|
92
|
+
| [0.4.5](./CHANGELOG.md#045---2026-08-23) | 2026-08-23 | `loop_memory.__version__` was stale at `"0.2.0"` |
|
|
93
|
+
| [0.4.4](./CHANGELOG.md#044---2026-08-23) | 2026-08-23 | `loop-memory rules --agent <name> --write` |
|
|
94
|
+
| [0.4.3](./CHANGELOG.md#043---2026-08-16) | 2026-08-16 | The cognitive sweep N+1 fix |
|
|
95
|
+
| [0.4.2](./CHANGELOG.md#042---2026-08-09) | 2026-08-09 | README refresh |
|
|
96
|
+
| [0.4.1](./CHANGELOG.md#041---2026-08-09) | 2026-08-09 | CLI dispatch hardened against common `--help` crashes |
|
|
97
|
+
| [0.4.0](./CHANGELOG.md#040---2026-08-09) | 2026-08-09 | OpenAI base URL env-var fallback |
|
|
98
|
+
| [0.3.0](./CHANGELOG.md#030--2026-07-11) | 2026-07-11 | The run progress strip is back |
|
|
99
|
+
| [0.2.0](./CHANGELOG.md#020--2026-07-10) | 2026-07-10 | The SQLite-backed `MemoryStore` |
|
|
100
|
+
| [0.1.0](./CHANGELOG.md#010--2026-07-10) | 2026-07-10 | The dashboard tab empty-data regression |
|
|
101
|
+
|
|
102
|
+
</details>
|
|
144
103
|
|
|
145
104
|
---
|
|
146
105
|
|
|
@@ -308,6 +267,7 @@ Hermes, clawx) can read and write, we built this for you.
|
|
|
308
267
|
| [docs/api.md](docs/api.md) | HTTP API reference — every route, request body, and response shape the UI consumes |
|
|
309
268
|
| [docs/agent-memory-api.md](docs/agent-memory-api.md) | Stable four-verb SDK / HTTP / MCP contract for any Agent |
|
|
310
269
|
| [docs/universal-agent-memory.md](docs/universal-agent-memory.md) | v7 graph memory, cognitive sleep, portable bundles, namespaces, and MCP/CLI extensions |
|
|
270
|
+
| [docs/transport-security.md](docs/transport-security.md) | How a remote client is allowed to talk to `loop-memory serve` — the transport guard, bearer-token auth, redirect hardening, and the `plaintext_exposure` doctor check (0.4.21) |
|
|
311
271
|
| [docs/providers.md](docs/providers.md) | LLM provider reference — built-in providers, defaults, base URLs, and how to add a new one |
|
|
312
272
|
| [docs/auto-capture.md](docs/auto-capture.md) | Hooking Codex / Claude / Hermes / OpenClaw watchers (filesystem, launchd, systemd, cron) |
|
|
313
273
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | Local dev loop, pytest, where secrets live, how to add a provider/source |
|
|
@@ -661,7 +621,7 @@ loop_memory/
|
|
|
661
621
|
backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
|
|
662
622
|
storage/ # SQLite-backed MemoryStore + migrations
|
|
663
623
|
privacy/ # <private> stripping + regex redaction
|
|
664
|
-
security/ # Keychain / 0600-file secrets wrapper
|
|
624
|
+
security/ # Keychain / 0600-file secrets wrapper + the transport guard
|
|
665
625
|
mcp/ # stdio JSON-RPC MCP server
|
|
666
626
|
serve/ # FastAPI app, watcher, web UI (Timeline / Dashboard / Wiki / Graph)
|
|
667
627
|
export/ # markdown + v7 portable bundle export/import
|
|
@@ -671,16 +631,18 @@ loop_memory/
|
|
|
671
631
|
memory/types.py # MemoryItem + 4 tiers
|
|
672
632
|
examples/demo.py # runnable, zero-API-key demo
|
|
673
633
|
py.typed
|
|
674
|
-
tests/ #
|
|
634
|
+
tests/ # 1041 unit tests across memory / SDK / serve / CLI / scripts
|
|
675
635
|
docs/
|
|
676
636
|
auto-capture.md # launchd / systemd / cron recipes
|
|
677
637
|
architecture.md # layered view + 5-stage evolution pipeline
|
|
678
638
|
api.md # HTTP API reference
|
|
639
|
+
transport-security.md # how a remote client may talk to `serve` (0.4.21)
|
|
679
640
|
agent-memory-api.md # four-verb SDK / HTTP / MCP contract
|
|
680
641
|
universal-agent-memory.md # v7 graph memory + cognitive sleep + bundles
|
|
681
642
|
providers.md # LLM provider reference
|
|
682
643
|
settings.md # settings table + secrets file
|
|
683
644
|
weekly-research-automation.md # how the project auto-evolves from upstream research
|
|
645
|
+
research/ # the weekly ecosystem notes each release is based on
|
|
684
646
|
```
|
|
685
647
|
|
|
686
648
|
---
|
|
@@ -710,6 +672,34 @@ The web UI stores it in `localStorage` under `loop_auth_token` and attaches
|
|
|
710
672
|
it automatically; non-browser clients (curl, MCP, SDK) must opt in by
|
|
711
673
|
passing it explicitly.
|
|
712
674
|
|
|
675
|
+
**Remote clients (SDK):** the HTTP SDK can send the token for you, and it
|
|
676
|
+
refuses to send it — or any memory text — over an unencrypted connection
|
|
677
|
+
to a remote host:
|
|
678
|
+
|
|
679
|
+
```python
|
|
680
|
+
client = MemoryClient.http(
|
|
681
|
+
"https://memory.example.com",
|
|
682
|
+
token=open("token.txt").read().strip(), # Authorization: Bearer …
|
|
683
|
+
)
|
|
684
|
+
client.remember("deploys go out on Tuesdays")
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Plain `http://` is still fine for `127.0.0.1` / `localhost` / `::1` (the
|
|
688
|
+
traffic never leaves the machine — that is the default deployment), but
|
|
689
|
+
plain `http://` to **any other host is refused**: the memory text and the
|
|
690
|
+
bearer token would cross the network in cleartext. A private tunnel or a
|
|
691
|
+
VPN'd LAN is a legitimate deployment, so `allow_insecure=True` (or
|
|
692
|
+
`LOOP_MEMORY_ALLOW_INSECURE=1`) exists — but it is never silent, it
|
|
693
|
+
warns on every construction. Redirects that downgrade `https://` →
|
|
694
|
+
`http://`, and cross-origin redirects that would replay the token, are
|
|
695
|
+
refused too. The full contract is in
|
|
696
|
+
[docs/transport-security.md](docs/transport-security.md).
|
|
697
|
+
|
|
698
|
+
**Server side:** `loop-memory serve --host <non-loopback>` prints a
|
|
699
|
+
warning and records the bind address, so `loop-memory doctor` reports it
|
|
700
|
+
as a `plaintext_exposure` warning — the API is reachable from the network
|
|
701
|
+
over plaintext.
|
|
702
|
+
|
|
713
703
|
**When to set a token:**
|
|
714
704
|
|
|
715
705
|
- ✅ Always, even on loopback. The default of "no token" exists only as a
|
|
@@ -726,6 +716,12 @@ passing it explicitly.
|
|
|
726
716
|
|
|
727
717
|
- `install-hooks` runs Python that touches `~/.*` — it's gated by the
|
|
728
718
|
bearer token like every other `POST /api/admin/*` route.
|
|
719
|
+
- A remote client cannot leak memory text to the network in cleartext:
|
|
720
|
+
the SDK refuses a plaintext `http://` base URL that is not loopback,
|
|
721
|
+
refuses a URL with embedded credentials, and refuses redirects that
|
|
722
|
+
downgrade the scheme or would replay the `Authorization` header
|
|
723
|
+
(audit 2026-11-08, 0.4.21 — see
|
|
724
|
+
[docs/transport-security.md](docs/transport-security.md)).
|
|
729
725
|
- The `<private>...</private>` span stripping in the privacy layer keeps
|
|
730
726
|
user-marked secrets out of long-term storage; the regex redaction layer
|
|
731
727
|
then catches API keys / tokens / private keys / JWTs / generic
|
|
@@ -22,80 +22,39 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
> **What's new in 0.4.
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
[
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
release: a hard cap on the in-process LLM response cache
|
|
59
|
-
(was unbounded; now 256 entries with FIFO eviction) and a
|
|
60
|
-
fix for ``subgraph_for(max_hops=...)`` which used to ignore
|
|
61
|
-
the argument and always walk one hop (now BFS up to N hops,
|
|
62
|
-
hard-capped at 8). 29 new regression cases. [Full
|
|
63
|
-
CHANGELOG →](./CHANGELOG.md#0410---2026-09-23)
|
|
64
|
-
|
|
65
|
-
> **What's new in 0.4.9** — **OKF v0.2 export** + **bi-temporal `as_of` recall**. `loop-memory export-okf <dir>` writes an [Open Knowledge Format](https://github.com/okf/SPEC) v0.2 bundle — one `.md` file per wiki page with YAML frontmatter + an `index.md` index — so a Loop Memory store can be handed to any OKF-aware tool (Grep, Obsidian, akitaonrails/ai-memory, okf-agent-memory, ...) without re-parsing the body (audit 2026-09-20, Google OKF v0.2 spec + akitaonrails/ai-memory 2.0 + okf-memory/okf-agent-memory, all Apache-2.0/MIT, 2026-09-01 → 2026-09-06). `loop-memory recall <q> --as-of <ISO|epoch>` answers the bi-temporal question "what did we know about this query at that moment?" — a memory written after the moment is invisible, and a superseded memory walks its chain to find whether the invalidation had happened by then (audit 2026-09-20, loomcycle v1.33-v1.49 RFCs BL/BS/BU/BV/BW, Apache-2.0). Both shapes ship with HTTP routes (`POST /api/export/okf`, `GET /api/recall?as_of=...`). 34 new regression cases pin both shapes. [Full CHANGELOG →](./CHANGELOG.md#049---2026-09-20)
|
|
66
|
-
|
|
67
|
-
> **What's new in 0.4.8** — **L0 outline recall** +
|
|
68
|
-
> **per-agent identity bootstrap**. `loop-memory recall-paths <q>`
|
|
69
|
-
> (and the new `recall --outline` flag) returns only `id` +
|
|
70
|
-
> `abstract` + `score` + `why` for each hit — never the full
|
|
71
|
-
> body — so an agent can identify the top candidates cheaply
|
|
72
|
-
> before deciding which bodies to fetch (audit 2026-09-13,
|
|
73
|
-
> tigerless-labs/agent-memory v0.3.0 recall-ladder pattern).
|
|
74
|
-
> `loop-memory init --agent <name>` registers an agent in the
|
|
75
|
-
> new `agents` table and (with `--install-hooks`) also wires
|
|
76
|
-
> the MCP + SessionStart hooks for codex/claude/hermes; the
|
|
77
|
-
> index is bumped on every `install-hooks` run so a user can
|
|
78
|
-
> always answer "which CLIs are wired into this store?" with
|
|
79
|
-
> one SQL query (audit 2026-09-13, Mem0 CLI `init --agent`
|
|
80
|
-
> pattern). Both shapes ship with HTTP routes
|
|
81
|
-
> (`/api/recall/outline`, `/api/agents`, `/api/init/agent`).
|
|
82
|
-
> 57 new regression cases pin both shapes. [Full
|
|
83
|
-
> changelog →](CHANGELOG.md)
|
|
84
|
-
>
|
|
85
|
-
> **What's new in 0.4.7** — **per-memory lifetime stats** +
|
|
86
|
-
> **portable SQLite snapshot**. `loop-memory memory-stats <id>`
|
|
87
|
-
> returns a flat dict (recall_count, positive, negative, age,
|
|
88
|
-
> etc.) so you can ask "is anyone still using this memory?"
|
|
89
|
-
> without cracking open the SQLite file (audit 2026-09-06,
|
|
90
|
-
> agentmemory v1.3.0 pattern). `loop-memory snapshot <out>` /
|
|
91
|
-
> `loop-memory restore <in>` move a single-file, lossless
|
|
92
|
-
> snapshot of the entire store — including recall-quality
|
|
93
|
-
> signals and the supersession chain — to another machine
|
|
94
|
-
> (audit 2026-09-06, codexa-memory v0.2.0 pattern). Both shapes
|
|
95
|
-
> ship with HTTP routes (`/api/memories/{id}/stats`,
|
|
96
|
-
> `/api/snapshot`, `/api/snapshot/restore`). 33 new regression
|
|
97
|
-
> cases pin both shapes.
|
|
98
|
-
> [Full changelog →](CHANGELOG.md)
|
|
25
|
+
> **What's new in 0.4.21** — **the remote SDK can finally authenticate, and it refuses to send memory data over a plaintext connection** (audit 2026-11-08, weekly ecosystem sync). The audit for this release (2026-11-08) turned up a real hole rather than a hypothetical one. `MemoryClient.http(base_url=...)` had **no transport guard at all**: point it at `http://mem.example.com` and it opened a plain socket and shipped the user's memory text across the network — and because the HTTP backend had **no way to send the bearer token** (`sqlite_store.py` even carried the comment *"header when `MemoryClient.http(..., token=...)` is given"* for a parameter that did not exist), the only way for a remote client to talk to a token-protected `loop-memory serve` was to turn auth **off** — exactly the configuration in which both the memory text and the token cross the network in cleartext. Two upstream projects closed the same hole in the same week: `rohitg00/agentmemory` v0.9.32 (2026-11-04, Apache-2.0) made *"hooks send nothing to a remote plain `http://` server; use HTTPS for a remote server"* a **breaking change**, and `topoteretes/cognee` v1.6.7 (2026-11-06, Apache-2.0) refuses *"server file paths over HTTP"* by default — both are pattern references only, no code copied. **What shipped:** a new `loop_memory.security.transport` that classifies a base URL and refuses plaintext `http://` to a **non-loopback** host (plaintext to a **loopback** address stays allowed — that traffic never leaves the host, and it is the default deployment), refuses a URL with embedded credentials, and refuses an unsupported scheme; `Authorization: Bearer` support in the SDK and in the shared `http_*_json` helpers, so the documented remote path works against a protected server instead of requiring auth to be turned off; a redirect guard that refuses an `https://` → `http://` downgrade (urllib's stock handler follows it, which would undo the TLS guarantee one hop from the server) and refuses a cross-origin redirect while a token is attached (urllib replays the `Authorization` header onto the redirect request); and the server half — `loop-memory serve` records its bind address and a new `loop-memory doctor` check (`plaintext_exposure`) warns when the API is reachable from the network over plaintext. The rule is about the *destination*, not the scheme: a private tunnel or a VPN'd LAN is a legitimate deployment, so `allow_insecure=True` (or `LOOP_MEMORY_ALLOW_INSECURE=1`) exists — but it is never silent, it warns on every construction. 37 new regression cases; suite at **1041 passed / 4 skipped**. See [`docs/transport-security.md`](./docs/transport-security.md) for the full contract. [Full CHANGELOG →](./CHANGELOG.md#0421---2026-11-08)
|
|
26
|
+
|
|
27
|
+
<details>
|
|
28
|
+
<summary><b>Older releases</b> — 0.4.20 and earlier, one line each (the CHANGELOG has the full detail)</summary>
|
|
29
|
+
|
|
30
|
+
| Version | Date | What changed | Full notes |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| [0.4.20](./CHANGELOG.md#0420---2026-11-01) | 2026-11-01 | The packaging test works on the oldest supported Python (no `tomllib` on 3.10) |
|
|
33
|
+
| [0.4.19](./CHANGELOG.md#0419---2026-11-01) | 2026-11-01 | The wheel ships the whole dashboard and the benchmark fixture (recursive package-data globs) |
|
|
34
|
+
| [0.4.18](./CHANGELOG.md#0418---2026-11-01) | 2026-11-01 | The BM25 channel no longer publishes a score that collapses to zero — plus a retrieval benchmark |
|
|
35
|
+
| [0.4.17](./CHANGELOG.md#0417---2026-10-25) | 2026-10-25 | A dry-run dream pass no longer writes to the database |
|
|
36
|
+
| [0.4.16](./CHANGELOG.md#0416---2026-10-25) | 2026-10-25 | Subject-key logical fact slots, `latest_only` recall, and the sleep-time "dream" pass |
|
|
37
|
+
| [0.4.15](./CHANGELOG.md#0415---2026-10-18) | 2026-10-18 | Content-hash dedup, bi-temporal validity window, evidence chain |
|
|
38
|
+
| [0.4.14](./CHANGELOG.md#0414---2026-10-12) | 2026-10-12 | `loop-memory doctor` — a deep store-health check |
|
|
39
|
+
| [0.4.13](./CHANGELOG.md#0413---2026-10-05) | 2026-10-05 | Recall token / char budget cap and a unified `level=0|1|2` tiered-loader knob |
|
|
40
|
+
| [0.4.12](./CHANGELOG.md#0412---2026-09-27) | 2026-09-27 | OKF bundle import and DB-level audit enforcement (12 SQLite triggers) |
|
|
41
|
+
| [0.4.11](./CHANGELOG.md#0411---2026-09-24) | 2026-09-24 | Operational probes (`/api/healthz`, `/api/readyz`) and the SQLite write-contention fix |
|
|
42
|
+
| [0.4.10](./CHANGELOG.md#0410---2026-09-23) | 2026-09-23 | Path-safety guard for the five export / snapshot endpoints, LLM cache cap, `subgraph_for(max_hops=)` |
|
|
43
|
+
| [0.4.9](./CHANGELOG.md#049---2026-09-20) | 2026-09-20 | OKF v0.2 export and bi-temporal `as_of` recall |
|
|
44
|
+
| [0.4.8](./CHANGELOG.md#048---2026-09-13) | 2026-09-13 | L0 outline recall and per-agent identity bootstrap |
|
|
45
|
+
| [0.4.7](./CHANGELOG.md#047---2026-09-06) | 2026-09-06 | Per-memory lifetime stats and a portable SQLite snapshot |
|
|
46
|
+
| [0.4.6](./CHANGELOG.md#046---2026-08-30) | 2026-08-30 | The memory supersession chain (no hard deletes) |
|
|
47
|
+
| [0.4.5](./CHANGELOG.md#045---2026-08-23) | 2026-08-23 | `loop_memory.__version__` was stale at `"0.2.0"` |
|
|
48
|
+
| [0.4.4](./CHANGELOG.md#044---2026-08-23) | 2026-08-23 | `loop-memory rules --agent <name> --write` |
|
|
49
|
+
| [0.4.3](./CHANGELOG.md#043---2026-08-16) | 2026-08-16 | The cognitive sweep N+1 fix |
|
|
50
|
+
| [0.4.2](./CHANGELOG.md#042---2026-08-09) | 2026-08-09 | README refresh |
|
|
51
|
+
| [0.4.1](./CHANGELOG.md#041---2026-08-09) | 2026-08-09 | CLI dispatch hardened against common `--help` crashes |
|
|
52
|
+
| [0.4.0](./CHANGELOG.md#040---2026-08-09) | 2026-08-09 | OpenAI base URL env-var fallback |
|
|
53
|
+
| [0.3.0](./CHANGELOG.md#030--2026-07-11) | 2026-07-11 | The run progress strip is back |
|
|
54
|
+
| [0.2.0](./CHANGELOG.md#020--2026-07-10) | 2026-07-10 | The SQLite-backed `MemoryStore` |
|
|
55
|
+
| [0.1.0](./CHANGELOG.md#010--2026-07-10) | 2026-07-10 | The dashboard tab empty-data regression |
|
|
56
|
+
|
|
57
|
+
</details>
|
|
99
58
|
|
|
100
59
|
---
|
|
101
60
|
|
|
@@ -263,6 +222,7 @@ Hermes, clawx) can read and write, we built this for you.
|
|
|
263
222
|
| [docs/api.md](docs/api.md) | HTTP API reference — every route, request body, and response shape the UI consumes |
|
|
264
223
|
| [docs/agent-memory-api.md](docs/agent-memory-api.md) | Stable four-verb SDK / HTTP / MCP contract for any Agent |
|
|
265
224
|
| [docs/universal-agent-memory.md](docs/universal-agent-memory.md) | v7 graph memory, cognitive sleep, portable bundles, namespaces, and MCP/CLI extensions |
|
|
225
|
+
| [docs/transport-security.md](docs/transport-security.md) | How a remote client is allowed to talk to `loop-memory serve` — the transport guard, bearer-token auth, redirect hardening, and the `plaintext_exposure` doctor check (0.4.21) |
|
|
266
226
|
| [docs/providers.md](docs/providers.md) | LLM provider reference — built-in providers, defaults, base URLs, and how to add a new one |
|
|
267
227
|
| [docs/auto-capture.md](docs/auto-capture.md) | Hooking Codex / Claude / Hermes / OpenClaw watchers (filesystem, launchd, systemd, cron) |
|
|
268
228
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | Local dev loop, pytest, where secrets live, how to add a provider/source |
|
|
@@ -616,7 +576,7 @@ loop_memory/
|
|
|
616
576
|
backends/ # embedding (hashing / sentence-transformers) + vector store (memory / chroma)
|
|
617
577
|
storage/ # SQLite-backed MemoryStore + migrations
|
|
618
578
|
privacy/ # <private> stripping + regex redaction
|
|
619
|
-
security/ # Keychain / 0600-file secrets wrapper
|
|
579
|
+
security/ # Keychain / 0600-file secrets wrapper + the transport guard
|
|
620
580
|
mcp/ # stdio JSON-RPC MCP server
|
|
621
581
|
serve/ # FastAPI app, watcher, web UI (Timeline / Dashboard / Wiki / Graph)
|
|
622
582
|
export/ # markdown + v7 portable bundle export/import
|
|
@@ -626,16 +586,18 @@ loop_memory/
|
|
|
626
586
|
memory/types.py # MemoryItem + 4 tiers
|
|
627
587
|
examples/demo.py # runnable, zero-API-key demo
|
|
628
588
|
py.typed
|
|
629
|
-
tests/ #
|
|
589
|
+
tests/ # 1041 unit tests across memory / SDK / serve / CLI / scripts
|
|
630
590
|
docs/
|
|
631
591
|
auto-capture.md # launchd / systemd / cron recipes
|
|
632
592
|
architecture.md # layered view + 5-stage evolution pipeline
|
|
633
593
|
api.md # HTTP API reference
|
|
594
|
+
transport-security.md # how a remote client may talk to `serve` (0.4.21)
|
|
634
595
|
agent-memory-api.md # four-verb SDK / HTTP / MCP contract
|
|
635
596
|
universal-agent-memory.md # v7 graph memory + cognitive sleep + bundles
|
|
636
597
|
providers.md # LLM provider reference
|
|
637
598
|
settings.md # settings table + secrets file
|
|
638
599
|
weekly-research-automation.md # how the project auto-evolves from upstream research
|
|
600
|
+
research/ # the weekly ecosystem notes each release is based on
|
|
639
601
|
```
|
|
640
602
|
|
|
641
603
|
---
|
|
@@ -665,6 +627,34 @@ The web UI stores it in `localStorage` under `loop_auth_token` and attaches
|
|
|
665
627
|
it automatically; non-browser clients (curl, MCP, SDK) must opt in by
|
|
666
628
|
passing it explicitly.
|
|
667
629
|
|
|
630
|
+
**Remote clients (SDK):** the HTTP SDK can send the token for you, and it
|
|
631
|
+
refuses to send it — or any memory text — over an unencrypted connection
|
|
632
|
+
to a remote host:
|
|
633
|
+
|
|
634
|
+
```python
|
|
635
|
+
client = MemoryClient.http(
|
|
636
|
+
"https://memory.example.com",
|
|
637
|
+
token=open("token.txt").read().strip(), # Authorization: Bearer …
|
|
638
|
+
)
|
|
639
|
+
client.remember("deploys go out on Tuesdays")
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Plain `http://` is still fine for `127.0.0.1` / `localhost` / `::1` (the
|
|
643
|
+
traffic never leaves the machine — that is the default deployment), but
|
|
644
|
+
plain `http://` to **any other host is refused**: the memory text and the
|
|
645
|
+
bearer token would cross the network in cleartext. A private tunnel or a
|
|
646
|
+
VPN'd LAN is a legitimate deployment, so `allow_insecure=True` (or
|
|
647
|
+
`LOOP_MEMORY_ALLOW_INSECURE=1`) exists — but it is never silent, it
|
|
648
|
+
warns on every construction. Redirects that downgrade `https://` →
|
|
649
|
+
`http://`, and cross-origin redirects that would replay the token, are
|
|
650
|
+
refused too. The full contract is in
|
|
651
|
+
[docs/transport-security.md](docs/transport-security.md).
|
|
652
|
+
|
|
653
|
+
**Server side:** `loop-memory serve --host <non-loopback>` prints a
|
|
654
|
+
warning and records the bind address, so `loop-memory doctor` reports it
|
|
655
|
+
as a `plaintext_exposure` warning — the API is reachable from the network
|
|
656
|
+
over plaintext.
|
|
657
|
+
|
|
668
658
|
**When to set a token:**
|
|
669
659
|
|
|
670
660
|
- ✅ Always, even on loopback. The default of "no token" exists only as a
|
|
@@ -681,6 +671,12 @@ passing it explicitly.
|
|
|
681
671
|
|
|
682
672
|
- `install-hooks` runs Python that touches `~/.*` — it's gated by the
|
|
683
673
|
bearer token like every other `POST /api/admin/*` route.
|
|
674
|
+
- A remote client cannot leak memory text to the network in cleartext:
|
|
675
|
+
the SDK refuses a plaintext `http://` base URL that is not loopback,
|
|
676
|
+
refuses a URL with embedded credentials, and refuses redirects that
|
|
677
|
+
downgrade the scheme or would replay the `Authorization` header
|
|
678
|
+
(audit 2026-11-08, 0.4.21 — see
|
|
679
|
+
[docs/transport-security.md](docs/transport-security.md)).
|
|
684
680
|
- The `<private>...</private>` span stripping in the privacy layer keeps
|
|
685
681
|
user-marked secrets out of long-term storage; the regex redaction layer
|
|
686
682
|
then catches API keys / tokens / private keys / JWTs / generic
|
|
@@ -40,6 +40,25 @@ def run_serve(args) -> int:
|
|
|
40
40
|
" Press Ctrl-C now unless you know what you are doing.\n",
|
|
41
41
|
file=sys.stderr,
|
|
42
42
|
)
|
|
43
|
+
# Audit 2026-11-08 (loop-memory 0.4.21): record the bind address
|
|
44
|
+
# in the store so `loop-memory doctor` can report the network
|
|
45
|
+
# exposure of a non-loopback bind. The client-side transport
|
|
46
|
+
# guard (loop_memory.security.transport) refuses a plaintext
|
|
47
|
+
# remote URL; this setting is the server-side half of the same
|
|
48
|
+
# story — it tells the operator that the API they just exposed
|
|
49
|
+
# is reachable in cleartext from the network.
|
|
50
|
+
try:
|
|
51
|
+
from ...storage.sqlite_store import MemoryStore
|
|
52
|
+
import time as _time
|
|
53
|
+
MemoryStore(DEFAULT_DB).set_setting(
|
|
54
|
+
"loop_memory_serve_bind",
|
|
55
|
+
{"host": host, "port": int(port), "scheme": "http",
|
|
56
|
+
"recorded_at": _time.time()},
|
|
57
|
+
)
|
|
58
|
+
except Exception as _exc: # pragma: no cover - never block startup
|
|
59
|
+
print(f"[loop-memory] warning: could not record the bind address: {_exc}",
|
|
60
|
+
file=sys.stderr)
|
|
61
|
+
|
|
43
62
|
from ...serve.app import serve as _serve
|
|
44
63
|
print(f"[loop-memory] serving UI on http://{host}:{port}")
|
|
45
64
|
print(f"[loop-memory] db = {DEFAULT_DB}")
|