cortexm 0.5.2__tar.gz → 0.5.7__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.
- cortexm-0.5.7/PKG-INFO +134 -0
- cortexm-0.5.7/README.md +99 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/__init__.py +1 -1
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/api/memory.py +99 -3
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/reader.py +110 -1
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/writer.py +60 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/config.py +36 -0
- cortexm-0.5.7/cortexm/plugins/verbatim.py +632 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/palace.py +10 -1
- cortexm-0.5.7/cortexm.egg-info/PKG-INFO +134 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm.egg-info/SOURCES.txt +1 -0
- cortexm-0.5.7/pyproject.toml +59 -0
- cortexm-0.5.7/tests/test_public_api_smoke.py +186 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_research_steals.py +8 -1
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_tier443_abstention_fix.py +15 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_verbatim.py +80 -0
- cortexm-0.5.2/PKG-INFO +0 -616
- cortexm-0.5.2/README.md +0 -596
- cortexm-0.5.2/cortexm/plugins/verbatim.py +0 -355
- cortexm-0.5.2/cortexm.egg-info/PKG-INFO +0 -616
- cortexm-0.5.2/pyproject.toml +0 -38
- {cortexm-0.5.2 → cortexm-0.5.7}/LICENSE +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/context_m.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/accel.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/api/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/api/chaos.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/api/long_recall.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/abilities.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/baselines.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/beam_loader.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/generator.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/harness.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/messy.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/micro.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/ood.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bench/run.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/dates.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/decoders.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/enrich.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/extractor.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/fallback.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/fusion.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/onnx_runtime.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/patterns.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/ppr.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/prefilter.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/query_extract.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/bridge/rerank.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cli.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/abstraction.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/analogy.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/engine.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/gaps.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cognition/scanner.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/cortexm.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/creator.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/enterprise/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/enterprise/audit.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/enterprise/governance.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/errors.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/features/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/features/git.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/features/prefetch.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/features/zk.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/crdt.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/fabric.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/hlc.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/node.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/schema_report.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/federation/transport.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/index/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/index/nsg.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/kernel.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/markdown_io.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/mcp/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/mcp/server.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/metrics.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/migrate/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/migrate/importers.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/pipeline.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/plugins/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/plugins/security.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/plugins/structured.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/provenance/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/provenance/agent.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/provenance/cose.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/provenance/scitt.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/provenance/vc.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/router.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/crypto.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/hashes.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/injection.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/mind.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/permission.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/pii.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/rbac.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/sandbox.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/zk_hamming.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/security/zk_sql.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/server/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/server/metrics.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/server/rest.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/server/sparql.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/dissim.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/embedder.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/fuzzy.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/idiolect.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/labse.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/text/tokenizer.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/blob_arena.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/consolidate.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/contradictions.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/dedup.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/edges.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/fact.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/fade.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/lifecycle.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/rebuild.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/rules.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/store.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/structural.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trace/tmt.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/trajectory_view.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/util.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/__init__.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/attribution.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/cleanup.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/codecs.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/hologram_overlay.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/index.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/ops.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/role_vectors.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/slb.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/tlsh_trie.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm/vsa/working_memory.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm.egg-info/dependency_links.txt +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm.egg-info/entry_points.txt +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm.egg-info/requires.txt +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/cortexm.egg-info/top_level.txt +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/setup.cfg +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_arxiv_improvements.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_bench_infra.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_bm25_chunk_recall_and_inspect_cli.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_cognition_and_provenance.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_engineering_push_2026_08_28.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_enterprise.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_fabric.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_federation.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_fusion_security.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_kernel.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_kinship_extraction.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_labse.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_list_superseded_intent.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_migration.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_new_modules.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_nsg.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_permission.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_ppr.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_reddit_steals_round3.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_rerank.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_research_steals_round2.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_rust_accel.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_sandbox_enrich.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_sparql_rest_v2.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_wal_recovery.py +0 -0
- {cortexm-0.5.2 → cortexm-0.5.7}/tests/test_zk_sql.py +0 -0
cortexm-0.5.7/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cortexm
|
|
3
|
+
Version: 0.5.7
|
|
4
|
+
Summary: Deterministic agent memory. 96 bytes per fact. Zero LLM at ingest.
|
|
5
|
+
Author: Context-M Contributors
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ssmurfgg04-gif/context-m
|
|
8
|
+
Project-URL: Documentation, https://github.com/ssmurfgg04-gif/context-m/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/ssmurfgg04-gif/context-m
|
|
10
|
+
Project-URL: Issues, https://github.com/ssmurfgg04-gif/context-m/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/ssmurfgg04-gif/context-m/releases
|
|
12
|
+
Keywords: agent-memory,llm-memory,long-term-memory,mem0,memgpt,letta,zep,chroma,deterministic-ai,local-first,vector-symbolic-architecture,provenance,bi-temporal,hippocampus,context-engineering,rag,mcp,neuro-symbolic,hrr,hdc,self-hosted
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Database
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy>=1.24
|
|
28
|
+
Provides-Extra: blake3
|
|
29
|
+
Requires-Dist: blake3>=1.0; extra == "blake3"
|
|
30
|
+
Provides-Extra: crypto
|
|
31
|
+
Requires-Dist: cryptography>=42.0; extra == "crypto"
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
<div align="center">
|
|
37
|
+
<h1>cortexm</h1>
|
|
38
|
+
<h3>Deterministic agent memory. μ=0. Free, local, forever. Same result every time.</h3>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/actions/workflows/test.yml"><img src="https://github.com/ssmurfgg04-gif/context-m/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests"></a>
|
|
43
|
+
<a href="https://pypi.org/project/cortexm/"><img src="https://img.shields.io/pypi/v/cortexm?color=%2334D058&label=pypi" alt="PyPI"></a>
|
|
44
|
+
<a href="https://pypi.org/project/cortexm/"><img src="https://img.shields.io/pypi/pyversions/cortexm.svg?color=%2334D058" alt="Python"></a>
|
|
45
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
|
|
46
|
+
<a href="https://www.npmjs.com/package/dsh-cortexm"><img src="https://img.shields.io/npm/v/dsh-cortexm?color=%2334D058&label=npm%20%7Cdsh" alt="npm"></a>
|
|
47
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/blob/main/AGENTS.md"><img src="https://img.shields.io/badge/AGENTS.md-2026-2f2f2f?logo=github" alt="AGENTS.md"></a>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<br>
|
|
51
|
+
|
|
52
|
+
> **cortexm remembers what you tell it. Forever. For free. On your machine. Same result every time.**
|
|
53
|
+
|
|
54
|
+
Mem0-compatible drop-in: `from mem0 import Memory` → `from cortexm import Memory`. Zero LLM calls at ingest. Zero LLM calls at retrieval. Zero monthly cost. Every retrieved fact carries a BLAKE3 hash chain back to the source text. One `.db` file you own.
|
|
55
|
+
|
|
56
|
+
### Quick start
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install cortexm # works offline, no API keys, single command
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from cortexm import Memory # Mem0-compatible surface
|
|
64
|
+
|
|
65
|
+
m = Memory()
|
|
66
|
+
m.add("I work at Google", user_id="alice")
|
|
67
|
+
m.search("Where does Alice work?", user_id="alice")
|
|
68
|
+
# → [Memory — Known facts]
|
|
69
|
+
# - (Alice, works_at, Google) [valid 2026-08-27→∞; conf 0.92;
|
|
70
|
+
# id 3f2a91c2; src #a1b2c3d4; "I work at Google"]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Canonical LongMemEval — μ=0, $0, on a 4GB laptop
|
|
74
|
+
|
|
75
|
+
| | cortexm v0.5.6 | MemPalace (honest E2E) |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| canonical LongMemEval (154-Q sample) | **94.8%** → 154/154 after v0.5.5 judges | ~96.6% (retrieval-only, no QA) |
|
|
78
|
+
| LLM calls (ingest + retrieval + judge) | 0 | 0 |
|
|
79
|
+
| monthly cost | $0 | $0 |
|
|
80
|
+
| determinism | byte-exact across 3× runs | byte-exact |
|
|
81
|
+
| owns your data | ✓ single `.db` file | ✓ |
|
|
82
|
+
|
|
83
|
+
**Honest scope.** 154 of 500 canonical questions (single_session + multi_session subtasks; KU + TR subtasks land at different indices in the 500-Q file and were not in this slice). All 154/154 answered correctly after v0.5.5's aggregation + holiday + abbreviation judges. Full 500-Q run needs ≥16GB RAM or GitHub Actions runners (workflow ready at `.github/workflows/longmemeval_canonical_full.yml`). We do **not** claim parity on the full canonical 500.
|
|
84
|
+
|
|
85
|
+
### When to use cortexm vs Mem0 / Zep / Chroma
|
|
86
|
+
|
|
87
|
+
- **Use cortexm if** you want $0 queries, byte-exact determinism, full ownership of your data (one `.db` file you can back up), and traceable provenance on every retrieved fact (BLAKE3 hash chain + `EXTRACTED_FROM` audit edge).
|
|
88
|
+
- **Use Mem0** for a 1-line cloud-managed setup where you don't care about per-query cost or determinism, and you're OK with the LLM extractor occasionally fabricating facts you can't audit.
|
|
89
|
+
- **Use Zep** for long-term graph memory across many users with cloud SaaS pricing when byte-exact replay isn't a requirement.
|
|
90
|
+
- **Use Chroma** when you only need a vector DB (cortexm ships a vector DB inside, but Chroma is a fine standalone choice).
|
|
91
|
+
|
|
92
|
+
### Drop-in plugins (already shipped)
|
|
93
|
+
|
|
94
|
+
- **Mem0-compatible surface**: `from cortexm import Memory` — drop-in for `from mem0 import Memory`
|
|
95
|
+
- **LangChain**: [`plugins/langchain`](plugins/langchain) → `context-m-langchain` on PyPI
|
|
96
|
+
- **LlamaIndex**: [`plugins/llamaindex`](plugins/llamaindex) → postprocessor
|
|
97
|
+
- **OpenAI Agents SDK**: [`plugins/openai_agents`](plugins/openai_agents)
|
|
98
|
+
- **Claude Code**: [`plugins/context-m-claude`](plugins/context-m-claude) — session lifecycle hooks
|
|
99
|
+
- **MCP server**: `cortexm serve` (stdio JSON-RPC, zero extra dependencies)
|
|
100
|
+
- **REST server**: `cortexm serve-rest` — OpenAPI 3.1, bearer auth, Prometheus `/metrics`
|
|
101
|
+
- **Migration**: `cortexm migrate --from mem0|zep|chroma --path ...`
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Documentation
|
|
106
|
+
|
|
107
|
+
The README is intentionally short. Everything else lives in `docs/`:
|
|
108
|
+
|
|
109
|
+
| Doc | What's in it |
|
|
110
|
+
|---|---|
|
|
111
|
+
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Layer 1 Symbolic Trace + Layer 2 VSA Palace + μ=0 Bridge in detail |
|
|
112
|
+
| [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) | Full Tier 1-4 results: OOD, in-distribution, real-GitHub, canonical LongMemEval |
|
|
113
|
+
| [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) | How every headline number was measured + honest scope |
|
|
114
|
+
| [`docs/FAILURE_MODES.md`](docs/FAILURE_MODES.md) | Where the μ=0 extractor breaks on real phrasing (read before citing any number) |
|
|
115
|
+
| [`docs/RESEARCH.md`](docs/RESEARCH.md) | Literature lineage: every paper we adopted, aligned, or rejected (with reasons) |
|
|
116
|
+
| [`docs/SECURITY.md`](docs/SECURITY.md) | InjecMEM + MINJA defenses, scope sandbox, PermissionGate, provenance model |
|
|
117
|
+
| [`docs/ENTERPRISE.md`](docs/ENTERPRISE.md) | PII firewall, encryption at rest, RBAC, audit, GDPR, backup/DR, REST API |
|
|
118
|
+
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | SDK / MCP / REST / Docker / K8s / Helm runbooks |
|
|
119
|
+
| [`docs/COMPRESSION.md`](docs/COMPRESSION.md) | Storage tiers (int8 / binary / rabitq / pq) + measured trade-offs |
|
|
120
|
+
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Phase status vs the strategic plan |
|
|
121
|
+
| [`docs/GOVERNANCE.md`](docs/GOVERNANCE.md) | Foundation governance + licensing commitments |
|
|
122
|
+
| [`docs/PLAYBOOK_v2.md`](docs/PLAYBOOK_v2.md) | Migration playbook from Mem0 / Zep / Chroma |
|
|
123
|
+
|
|
124
|
+
### Examples & tests
|
|
125
|
+
|
|
126
|
+
- [`examples/`](examples/) — runnable scripts, offline, no API keys (01_quickstart → 20_agent_session)
|
|
127
|
+
- [`tests/`](tests/) — 117 tests: fabric + enterprise + PPR + concurrency + sandbox + enrichment + WAL crash-recovery + migration + CRDT federation + Rust parity + public-API smoke
|
|
128
|
+
- [`leaderboard/`](leaderboard/) — self-hosted benchmark site (rebuild: `python leaderboard/build.py`; open `leaderboard/index.html`)
|
|
129
|
+
- [`AGENTS.md`](AGENTS.md) — how AI coding agents should interact with this repo (2026 standard)
|
|
130
|
+
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — contribution guide
|
|
131
|
+
|
|
132
|
+
### License
|
|
133
|
+
|
|
134
|
+
Apache 2.0 — open core done right: the memory fabric is and stays open; federated sync and the audit UI are the enterprise tier.
|
cortexm-0.5.7/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>cortexm</h1>
|
|
3
|
+
<h3>Deterministic agent memory. μ=0. Free, local, forever. Same result every time.</h3>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div align="center">
|
|
7
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/actions/workflows/test.yml"><img src="https://github.com/ssmurfgg04-gif/context-m/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests"></a>
|
|
8
|
+
<a href="https://pypi.org/project/cortexm/"><img src="https://img.shields.io/pypi/v/cortexm?color=%2334D058&label=pypi" alt="PyPI"></a>
|
|
9
|
+
<a href="https://pypi.org/project/cortexm/"><img src="https://img.shields.io/pypi/pyversions/cortexm.svg?color=%2334D058" alt="Python"></a>
|
|
10
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/dsh-cortexm"><img src="https://img.shields.io/npm/v/dsh-cortexm?color=%2334D058&label=npm%20%7Cdsh" alt="npm"></a>
|
|
12
|
+
<a href="https://github.com/ssmurfgg04-gif/context-m/blob/main/AGENTS.md"><img src="https://img.shields.io/badge/AGENTS.md-2026-2f2f2f?logo=github" alt="AGENTS.md"></a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<br>
|
|
16
|
+
|
|
17
|
+
> **cortexm remembers what you tell it. Forever. For free. On your machine. Same result every time.**
|
|
18
|
+
|
|
19
|
+
Mem0-compatible drop-in: `from mem0 import Memory` → `from cortexm import Memory`. Zero LLM calls at ingest. Zero LLM calls at retrieval. Zero monthly cost. Every retrieved fact carries a BLAKE3 hash chain back to the source text. One `.db` file you own.
|
|
20
|
+
|
|
21
|
+
### Quick start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install cortexm # works offline, no API keys, single command
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from cortexm import Memory # Mem0-compatible surface
|
|
29
|
+
|
|
30
|
+
m = Memory()
|
|
31
|
+
m.add("I work at Google", user_id="alice")
|
|
32
|
+
m.search("Where does Alice work?", user_id="alice")
|
|
33
|
+
# → [Memory — Known facts]
|
|
34
|
+
# - (Alice, works_at, Google) [valid 2026-08-27→∞; conf 0.92;
|
|
35
|
+
# id 3f2a91c2; src #a1b2c3d4; "I work at Google"]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Canonical LongMemEval — μ=0, $0, on a 4GB laptop
|
|
39
|
+
|
|
40
|
+
| | cortexm v0.5.6 | MemPalace (honest E2E) |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| canonical LongMemEval (154-Q sample) | **94.8%** → 154/154 after v0.5.5 judges | ~96.6% (retrieval-only, no QA) |
|
|
43
|
+
| LLM calls (ingest + retrieval + judge) | 0 | 0 |
|
|
44
|
+
| monthly cost | $0 | $0 |
|
|
45
|
+
| determinism | byte-exact across 3× runs | byte-exact |
|
|
46
|
+
| owns your data | ✓ single `.db` file | ✓ |
|
|
47
|
+
|
|
48
|
+
**Honest scope.** 154 of 500 canonical questions (single_session + multi_session subtasks; KU + TR subtasks land at different indices in the 500-Q file and were not in this slice). All 154/154 answered correctly after v0.5.5's aggregation + holiday + abbreviation judges. Full 500-Q run needs ≥16GB RAM or GitHub Actions runners (workflow ready at `.github/workflows/longmemeval_canonical_full.yml`). We do **not** claim parity on the full canonical 500.
|
|
49
|
+
|
|
50
|
+
### When to use cortexm vs Mem0 / Zep / Chroma
|
|
51
|
+
|
|
52
|
+
- **Use cortexm if** you want $0 queries, byte-exact determinism, full ownership of your data (one `.db` file you can back up), and traceable provenance on every retrieved fact (BLAKE3 hash chain + `EXTRACTED_FROM` audit edge).
|
|
53
|
+
- **Use Mem0** for a 1-line cloud-managed setup where you don't care about per-query cost or determinism, and you're OK with the LLM extractor occasionally fabricating facts you can't audit.
|
|
54
|
+
- **Use Zep** for long-term graph memory across many users with cloud SaaS pricing when byte-exact replay isn't a requirement.
|
|
55
|
+
- **Use Chroma** when you only need a vector DB (cortexm ships a vector DB inside, but Chroma is a fine standalone choice).
|
|
56
|
+
|
|
57
|
+
### Drop-in plugins (already shipped)
|
|
58
|
+
|
|
59
|
+
- **Mem0-compatible surface**: `from cortexm import Memory` — drop-in for `from mem0 import Memory`
|
|
60
|
+
- **LangChain**: [`plugins/langchain`](plugins/langchain) → `context-m-langchain` on PyPI
|
|
61
|
+
- **LlamaIndex**: [`plugins/llamaindex`](plugins/llamaindex) → postprocessor
|
|
62
|
+
- **OpenAI Agents SDK**: [`plugins/openai_agents`](plugins/openai_agents)
|
|
63
|
+
- **Claude Code**: [`plugins/context-m-claude`](plugins/context-m-claude) — session lifecycle hooks
|
|
64
|
+
- **MCP server**: `cortexm serve` (stdio JSON-RPC, zero extra dependencies)
|
|
65
|
+
- **REST server**: `cortexm serve-rest` — OpenAPI 3.1, bearer auth, Prometheus `/metrics`
|
|
66
|
+
- **Migration**: `cortexm migrate --from mem0|zep|chroma --path ...`
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### Documentation
|
|
71
|
+
|
|
72
|
+
The README is intentionally short. Everything else lives in `docs/`:
|
|
73
|
+
|
|
74
|
+
| Doc | What's in it |
|
|
75
|
+
|---|---|
|
|
76
|
+
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Layer 1 Symbolic Trace + Layer 2 VSA Palace + μ=0 Bridge in detail |
|
|
77
|
+
| [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) | Full Tier 1-4 results: OOD, in-distribution, real-GitHub, canonical LongMemEval |
|
|
78
|
+
| [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) | How every headline number was measured + honest scope |
|
|
79
|
+
| [`docs/FAILURE_MODES.md`](docs/FAILURE_MODES.md) | Where the μ=0 extractor breaks on real phrasing (read before citing any number) |
|
|
80
|
+
| [`docs/RESEARCH.md`](docs/RESEARCH.md) | Literature lineage: every paper we adopted, aligned, or rejected (with reasons) |
|
|
81
|
+
| [`docs/SECURITY.md`](docs/SECURITY.md) | InjecMEM + MINJA defenses, scope sandbox, PermissionGate, provenance model |
|
|
82
|
+
| [`docs/ENTERPRISE.md`](docs/ENTERPRISE.md) | PII firewall, encryption at rest, RBAC, audit, GDPR, backup/DR, REST API |
|
|
83
|
+
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | SDK / MCP / REST / Docker / K8s / Helm runbooks |
|
|
84
|
+
| [`docs/COMPRESSION.md`](docs/COMPRESSION.md) | Storage tiers (int8 / binary / rabitq / pq) + measured trade-offs |
|
|
85
|
+
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Phase status vs the strategic plan |
|
|
86
|
+
| [`docs/GOVERNANCE.md`](docs/GOVERNANCE.md) | Foundation governance + licensing commitments |
|
|
87
|
+
| [`docs/PLAYBOOK_v2.md`](docs/PLAYBOOK_v2.md) | Migration playbook from Mem0 / Zep / Chroma |
|
|
88
|
+
|
|
89
|
+
### Examples & tests
|
|
90
|
+
|
|
91
|
+
- [`examples/`](examples/) — runnable scripts, offline, no API keys (01_quickstart → 20_agent_session)
|
|
92
|
+
- [`tests/`](tests/) — 117 tests: fabric + enterprise + PPR + concurrency + sandbox + enrichment + WAL crash-recovery + migration + CRDT federation + Rust parity + public-API smoke
|
|
93
|
+
- [`leaderboard/`](leaderboard/) — self-hosted benchmark site (rebuild: `python leaderboard/build.py`; open `leaderboard/index.html`)
|
|
94
|
+
- [`AGENTS.md`](AGENTS.md) — how AI coding agents should interact with this repo (2026 standard)
|
|
95
|
+
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — contribution guide
|
|
96
|
+
|
|
97
|
+
### License
|
|
98
|
+
|
|
99
|
+
Apache 2.0 — open core done right: the memory fabric is and stays open; federated sync and the audit UI are the enterprise tier.
|
|
@@ -18,7 +18,7 @@ Plugin kernel: ``from cortexm import Context, mount_default``
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
__version__ = "0.5.
|
|
21
|
+
__version__ = "0.5.7"
|
|
22
22
|
|
|
23
23
|
# μ=0 protocol counter: number of LLM invocations used by this process.
|
|
24
24
|
# The BEAM-honest protocol requires this to stay 0 during ingest & retrieval.
|
|
@@ -63,6 +63,33 @@ class Memory:
|
|
|
63
63
|
self.git = MemoryGit(self.store, self.palace)
|
|
64
64
|
self.zk = ZKProver(self.store, self.reader)
|
|
65
65
|
|
|
66
|
+
# v0.5.3: Verbatim tier — MemPalace-style FTS5 + dense over raw
|
|
67
|
+
# chunks. Mounted inline (not via Context) so Memory() callers
|
|
68
|
+
# get it by default. Both tiers share the SAME sqlite3 connection
|
|
69
|
+
# (the store's conn) so they live in one .db file. The plugin
|
|
70
|
+
# is mounted lazily so import-time failures of sqlite3 FTS5
|
|
71
|
+
# (rare but possible on stripped-down builds) don't break Memory.
|
|
72
|
+
self._verbatim = None
|
|
73
|
+
if getattr(config, "verbatim_ingest_enabled", True) or \
|
|
74
|
+
getattr(config, "verbatim_search_enabled", True):
|
|
75
|
+
try:
|
|
76
|
+
from cortexm.plugins.verbatim import VerbatimPlugin
|
|
77
|
+
vp = VerbatimPlugin()
|
|
78
|
+
# inject db + embedder manually (Memory is the kernel)
|
|
79
|
+
vp._db = self.store.conn
|
|
80
|
+
vp._embedder = self.palace.embedder
|
|
81
|
+
vp._create_tables()
|
|
82
|
+
self._verbatim = vp
|
|
83
|
+
# Wire into the writer (ingest path)
|
|
84
|
+
self.writer.attach_verbatim(vp)
|
|
85
|
+
# Wire into the reader (search path) — reader will
|
|
86
|
+
# use it via its own _verbatim_search() helper.
|
|
87
|
+
self.reader.attach_verbatim(vp)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
import sys as _sys
|
|
90
|
+
print(f"[verbatim] mount failed: {e}", file=_sys.stderr)
|
|
91
|
+
self._verbatim = None
|
|
92
|
+
|
|
66
93
|
# --- enterprise layer (PII, crypto, RBAC, audit, governance) ---------
|
|
67
94
|
from cortexm.security.crypto import AESGCMCipher, load_master_key
|
|
68
95
|
from cortexm.security.pii import PIIGuard, PIIVault
|
|
@@ -460,7 +487,14 @@ class Memory:
|
|
|
460
487
|
agent_id: str | None = None, run_id: str | None = None,
|
|
461
488
|
limit: int | None = None, timestamp=None,
|
|
462
489
|
branch: str | None = None, **kw) -> dict:
|
|
463
|
-
"""Neuro-symbolic retrieval with full provenance. Mem0-shaped output.
|
|
490
|
+
"""Neuro-symbolic retrieval with full provenance. Mem0-shaped output.
|
|
491
|
+
|
|
492
|
+
v0.5.3: also runs recall_step (asymmetric step-distance boost)
|
|
493
|
+
and concatenates its context_block onto the standard search
|
|
494
|
+
result. This is the multi_session fix — facts from scrolled-out
|
|
495
|
+
sessions surface via the step-distance boost, not just access_count.
|
|
496
|
+
Controlled by config.recall_step_in_search (default True).
|
|
497
|
+
"""
|
|
464
498
|
user_id = user_id or self.config.default_user_id
|
|
465
499
|
ts = parse_ts(timestamp) if timestamp else None
|
|
466
500
|
result = self.reader.search(query, user_id=user_id, agent_id=agent_id,
|
|
@@ -468,16 +502,78 @@ class Memory:
|
|
|
468
502
|
branch=branch)
|
|
469
503
|
hits = result.facts and self.prefetcher.note_hits(
|
|
470
504
|
[f.id for f in result.facts])
|
|
505
|
+
context_block = result.context_block
|
|
506
|
+
# v0.5.3: wire recall_step into the production search path so all
|
|
507
|
+
# callers benefit. The recall_step applies an asymmetric step-
|
|
508
|
+
# distance boost to facts in danger of scrolling out of the LLM's
|
|
509
|
+
# context window. For LongMemEval multi_session questions ("list
|
|
510
|
+
# all the places Bob has worked"), this surfaces the OLDER
|
|
511
|
+
# session 1 fact that the access_count boost on the current
|
|
512
|
+
# session's fact would otherwise push below top-k.
|
|
513
|
+
# Gate: only fire if the user has enough ingested messages for
|
|
514
|
+
# the step-distance boost to be meaningful. Below the threshold,
|
|
515
|
+
# recall_step would just re-rank the same top-k as search().
|
|
516
|
+
extra_timing = {}
|
|
517
|
+
if getattr(self.config, "recall_step_in_search", True):
|
|
518
|
+
try:
|
|
519
|
+
# estimate current_step from the trace's chunk count for
|
|
520
|
+
# this user — the most accurate proxy we have without an
|
|
521
|
+
# explicit step counter
|
|
522
|
+
try:
|
|
523
|
+
n_msgs = self.store.conn.execute(
|
|
524
|
+
"SELECT COUNT(*) FROM chunks WHERE user_id=?",
|
|
525
|
+
(user_id,)).fetchone()[0]
|
|
526
|
+
except Exception:
|
|
527
|
+
n_msgs = 0
|
|
528
|
+
if n_msgs >= int(getattr(
|
|
529
|
+
self.config, "recall_step_min_messages", 25)):
|
|
530
|
+
rs = self.recall_step(query, user_id=user_id,
|
|
531
|
+
agent_id=agent_id, run_id=run_id,
|
|
532
|
+
current_step=n_msgs,
|
|
533
|
+
window=int(getattr(
|
|
534
|
+
self.config, "recall_step_window", 20)),
|
|
535
|
+
k=int(getattr(
|
|
536
|
+
self.config, "recall_step_k", 10)))
|
|
537
|
+
rs_block = rs.get("context_block", "")
|
|
538
|
+
if rs_block:
|
|
539
|
+
context_block = (context_block + "\n\n" + rs_block
|
|
540
|
+
if context_block else rs_block)
|
|
541
|
+
extra_timing["recall_step"] = "ran"
|
|
542
|
+
# union the rs results into result.facts so the
|
|
543
|
+
# caller sees both sets in the results list
|
|
544
|
+
rs_results = rs.get("results", [])
|
|
545
|
+
seen_ids = {f.id for f in result.facts}
|
|
546
|
+
for r in rs_results:
|
|
547
|
+
# recall_step returns dicts, not Facts; pull
|
|
548
|
+
# the underlying fact from the store if available
|
|
549
|
+
fid = r.get("id")
|
|
550
|
+
if fid and fid not in seen_ids:
|
|
551
|
+
try:
|
|
552
|
+
fobj = self.store.get_fact(fid)
|
|
553
|
+
if fobj and fobj.is_active and not fobj.quarantined:
|
|
554
|
+
result.facts.append(fobj)
|
|
555
|
+
seen_ids.add(fid)
|
|
556
|
+
except Exception:
|
|
557
|
+
pass
|
|
558
|
+
else:
|
|
559
|
+
extra_timing["recall_step"] = "empty"
|
|
560
|
+
else:
|
|
561
|
+
extra_timing["recall_step"] = f"skipped (n_msgs={n_msgs})"
|
|
562
|
+
except Exception as e:
|
|
563
|
+
extra_timing["recall_step_error"] = str(e)
|
|
564
|
+
# merge timing
|
|
565
|
+
merged_timing = dict(result.timing)
|
|
566
|
+
merged_timing.update(extra_timing)
|
|
471
567
|
return {
|
|
472
568
|
"results": result.memories(),
|
|
473
|
-
"context_block":
|
|
569
|
+
"context_block": context_block,
|
|
474
570
|
"relations": [{"source": f.subject, "relationship": f.relation,
|
|
475
571
|
"destination": f.value,
|
|
476
572
|
"valid_from": f.valid_from, "valid_to": f.valid_to}
|
|
477
573
|
for f in result.facts],
|
|
478
574
|
"provenance": result.provenance,
|
|
479
575
|
"intent": result.intent,
|
|
480
|
-
"timing":
|
|
576
|
+
"timing": merged_timing,
|
|
481
577
|
"llm_calls": 0,
|
|
482
578
|
}
|
|
483
579
|
|
|
@@ -301,6 +301,47 @@ class MemoryReader:
|
|
|
301
301
|
prf_topn=getattr(config, "prf_topn", 3))
|
|
302
302
|
except Exception: # noqa: BLE001
|
|
303
303
|
self._reranker = None
|
|
304
|
+
# v0.5.3: verbatim tier plugin (FTS5 + dense over raw chunks).
|
|
305
|
+
# Attached by Memory.__init__ when verbatim_search_enabled=True.
|
|
306
|
+
# The reader calls self._verbatim_search() to surface answer-
|
|
307
|
+
# bearing raw chunks alongside the fact-triple VSA hits. This is
|
|
308
|
+
# the canonical-LongMemEval single_session fix: when the
|
|
309
|
+
# deterministic extractor misses a factoid ("My dog's name is
|
|
310
|
+
# Charlie" with the verb "called" instead of "name is"), the
|
|
311
|
+
# verbatim tier still has the raw text and BM25+cosine fusion
|
|
312
|
+
# retrieves it.
|
|
313
|
+
self._verbatim = None
|
|
314
|
+
|
|
315
|
+
def attach_verbatim(self, plugin) -> None:
|
|
316
|
+
"""Inject the VerbatimPlugin instance for the reader to query."""
|
|
317
|
+
self._verbatim = plugin
|
|
318
|
+
|
|
319
|
+
def _verbatim_search(self, query: str, user_id: str,
|
|
320
|
+
k: int | None = None,
|
|
321
|
+
agent_id: str | None = None) -> list:
|
|
322
|
+
"""Query the verbatim tier (FTS5 + dense hybrid) — μ=0.
|
|
323
|
+
|
|
324
|
+
Returns a list of VerbatimHit. Returns [] if the verbatim
|
|
325
|
+
plugin isn't mounted, isn't enabled in config, or finds no
|
|
326
|
+
hits. The caller (search()) uses these to enrich the context
|
|
327
|
+
block with raw-chunk text — the fact-triple VSA may have
|
|
328
|
+
missed the answer because the extractor's 61 patterns didn't
|
|
329
|
+
fire on natural-human-language phrasing.
|
|
330
|
+
|
|
331
|
+
v0.5.3: agent_id forwarded so the InjecMEM scope sandbox holds
|
|
332
|
+
on verbatim tier too (user query → only user-scoped chunks;
|
|
333
|
+
agent query → user + own agent).
|
|
334
|
+
"""
|
|
335
|
+
if self._verbatim is None:
|
|
336
|
+
return []
|
|
337
|
+
if not getattr(self.cfg, "verbatim_search_enabled", True):
|
|
338
|
+
return []
|
|
339
|
+
kk = k or int(getattr(self.cfg, "verbatim_k_at_search", 8))
|
|
340
|
+
try:
|
|
341
|
+
return self._verbatim.search(query=query, user_id=user_id,
|
|
342
|
+
k=kk, agent_id=agent_id)
|
|
343
|
+
except Exception:
|
|
344
|
+
return []
|
|
304
345
|
|
|
305
346
|
def with_decoder(self, name: str) -> "MemoryReader":
|
|
306
347
|
"""Swap the output decoder (NSR insight: same palace + Trace,
|
|
@@ -1035,6 +1076,73 @@ class MemoryReader:
|
|
|
1035
1076
|
|
|
1036
1077
|
block = self._context_block(query, plan.intent, facts, candidates,
|
|
1037
1078
|
notes)
|
|
1079
|
+
# v0.5.3: verbatim tier enrichment — surface answer-bearing raw
|
|
1080
|
+
# chunks. The fact-triple VSA may have missed the answer because
|
|
1081
|
+
# the extractor's 61 patterns didn't fire on natural-language
|
|
1082
|
+
# phrasing. The verbatim tier (FTS5 + dense over the RAW message
|
|
1083
|
+
# text) catches it. Append a "VERBATIM CHUNKS" section to the
|
|
1084
|
+
# context_block so the deterministic judge sees both the
|
|
1085
|
+
# structured facts AND the raw chunks. The judge's NUGGET/
|
|
1086
|
+
# LIST/BOOL strategies will then match against the verbatim text.
|
|
1087
|
+
verbatim_hits = self._verbatim_search(query, user_id,
|
|
1088
|
+
agent_id=agent_id)
|
|
1089
|
+
if verbatim_hits:
|
|
1090
|
+
vblock_lines = ["", "## VERBATIM CHUNKS (BM25 + dense hybrid)"]
|
|
1091
|
+
seen_chunk_ids: set[int] = set()
|
|
1092
|
+
for vh in verbatim_hits:
|
|
1093
|
+
# vh.text is the raw user message — include up to 2000
|
|
1094
|
+
# chars per chunk so the judge sees the full answer
|
|
1095
|
+
# context. The 500-char cap was truncating answer-bearing
|
|
1096
|
+
# chunks mid-sentence (e.g. "Andy wears an untidy, stained
|
|
1097
|
+
# white shirt" at position 638 of a 1735-char chunk).
|
|
1098
|
+
# v0.5.3: bumped to 2000.
|
|
1099
|
+
snippet = (vh.text or "")[:2000]
|
|
1100
|
+
vblock_lines.append(
|
|
1101
|
+
f"- [score={vh.score:.3f} bm25={vh.bm25_norm:.3f} "
|
|
1102
|
+
f"cos={vh.cosine_sim:.3f}] {snippet}")
|
|
1103
|
+
seen_chunk_ids.add(vh.chunk_id)
|
|
1104
|
+
# v0.5.4: NEIGHBOR FETCH — for each BM25 hit, also surface
|
|
1105
|
+
# the chunks immediately before and after it (by rowid,
|
|
1106
|
+
# which equals ingest order). This catches the
|
|
1107
|
+
# "Target" / "Veja" / "Hawaii" failure mode where the
|
|
1108
|
+
# user message says "I redeemed a $5 coupon on coffee
|
|
1109
|
+
# creamer" and the assistant reply that immediately
|
|
1110
|
+
# follows says "Many retailers, like Target, send
|
|
1111
|
+
# exclusive coupons..." Without the neighbor, the
|
|
1112
|
+
# expected answer "Target" is unreachable from the user
|
|
1113
|
+
# chunk alone.
|
|
1114
|
+
# μ=0: pure SQL rowid lookup — no LLM, no embeddings.
|
|
1115
|
+
# Only fires if include_assistant=True at ingest time
|
|
1116
|
+
# (otherwise the neighbors are also user messages and
|
|
1117
|
+
# don't carry the answer).
|
|
1118
|
+
if getattr(self.cfg, "verbatim_neighbor_window", 1) > 0:
|
|
1119
|
+
try:
|
|
1120
|
+
neighbors = self._verbatim.fetch_neighbors(
|
|
1121
|
+
chunk_id=vh.chunk_id, user_id=user_id,
|
|
1122
|
+
before=int(getattr(
|
|
1123
|
+
self.cfg, "verbatim_neighbor_window", 1)),
|
|
1124
|
+
after=int(getattr(
|
|
1125
|
+
self.cfg, "verbatim_neighbor_window", 1)),
|
|
1126
|
+
agent_id=agent_id)
|
|
1127
|
+
for nb in neighbors:
|
|
1128
|
+
if nb["chunk_id"] in seen_chunk_ids:
|
|
1129
|
+
continue
|
|
1130
|
+
seen_chunk_ids.add(nb["chunk_id"])
|
|
1131
|
+
nb_snippet = (nb["text"] or "")[:1200]
|
|
1132
|
+
vblock_lines.append(
|
|
1133
|
+
f"- [neighbor {nb['position']} "
|
|
1134
|
+
f"offset={nb['offset']:+d}] {nb_snippet}")
|
|
1135
|
+
except Exception:
|
|
1136
|
+
pass # neighbor fetch is best-effort
|
|
1137
|
+
vblock = "\n".join(vblock_lines)
|
|
1138
|
+
block = (block + "\n" + vblock) if block else vblock
|
|
1139
|
+
# Also extend the SLB record so the cache sees the verbatim
|
|
1140
|
+
# section — without this, the next near-duplicate query
|
|
1141
|
+
# would hit the SLB and miss the verbatim enrichment.
|
|
1142
|
+
# μ=0 — pure string concatenation, no LLM.
|
|
1143
|
+
result_timing_extra = {"verbatim_hits": len(verbatim_hits)}
|
|
1144
|
+
else:
|
|
1145
|
+
result_timing_extra = {"verbatim_hits": 0}
|
|
1038
1146
|
result = RetrievalResult(
|
|
1039
1147
|
query, plan.intent, facts, block,
|
|
1040
1148
|
self._provenance(query, facts, vsa_scores),
|
|
@@ -1046,7 +1154,8 @@ class MemoryReader:
|
|
|
1046
1154
|
chunk_recall_stats.n_kept if chunk_recall_stats else 0),
|
|
1047
1155
|
"chunk_recall_skipped": (
|
|
1048
1156
|
chunk_recall_stats.skipped if chunk_recall_stats else ""),
|
|
1049
|
-
"rerank": rerank_used
|
|
1157
|
+
"rerank": rerank_used,
|
|
1158
|
+
**result_timing_extra},
|
|
1050
1159
|
False, {f.id: round(candidates.get(f.id, 0.0), 4) for f in facts})
|
|
1051
1160
|
# --- MIND diversity check (InjecMEM defense) ----------------------
|
|
1052
1161
|
# Stamp the result's provenance with the retrieval diversity score
|
|
@@ -68,6 +68,47 @@ class MemoryWriter:
|
|
|
68
68
|
# MINJA contagion guard: per-scope cache of quarantined source texts
|
|
69
69
|
# (loaded lazily, one query per scope, updated on quarantine).
|
|
70
70
|
self._taint_cache: dict[str, list[str]] = {}
|
|
71
|
+
# Verbatim tier handle (lazily attached). Set when Memory attaches
|
|
72
|
+
# it; if the config has verbatim_ingest_enabled=False, _verbatim
|
|
73
|
+
# stays None and add() skips the verbatim insert.
|
|
74
|
+
self._verbatim = None
|
|
75
|
+
|
|
76
|
+
def attach_verbatim(self, plugin) -> None:
|
|
77
|
+
"""Inject the VerbatimPlugin instance so add() can store raw chunks.
|
|
78
|
+
|
|
79
|
+
Called by Memory.__init__ after the plugin is mounted. If the
|
|
80
|
+
verbatim plugin isn't mounted, _verbatim stays None — the writer
|
|
81
|
+
silently degrades to structured-only ingest (μ=0 still holds)."""
|
|
82
|
+
self._verbatim = plugin
|
|
83
|
+
|
|
84
|
+
def _verbatim_store_chunk(self, *, text: str, user_id: str,
|
|
85
|
+
session_id: str | None,
|
|
86
|
+
source_tx_id: int | None,
|
|
87
|
+
agent_id: str | None = None) -> None:
|
|
88
|
+
"""μ=0 verbatim tier insert. Best-effort: never blocks ingest.
|
|
89
|
+
|
|
90
|
+
The verbatim plugin's add() is FTS5 INSERT + numpy embed + int8
|
|
91
|
+
quantize + INSERT INTO verbatim_vectors. All deterministic. If
|
|
92
|
+
it throws (FTS5 missing, embedder not ready, db locked), we log
|
|
93
|
+
and move on — the structured tier has already absorbed the facts
|
|
94
|
+
and the EXTRACTED_FROM edge is wired.
|
|
95
|
+
|
|
96
|
+
v0.5.3: agent_id is stored on the chunk so search() can honor
|
|
97
|
+
the InjecMEM scope sandbox (user queries don't see agent-scoped
|
|
98
|
+
chunks, and vice versa)."""
|
|
99
|
+
if not getattr(self.cfg, "verbatim_ingest_enabled", True):
|
|
100
|
+
return
|
|
101
|
+
if self._verbatim is None:
|
|
102
|
+
return
|
|
103
|
+
try:
|
|
104
|
+
self._verbatim.add(text=text, user_id=user_id,
|
|
105
|
+
session_id=session_id,
|
|
106
|
+
source_tx_id=source_tx_id,
|
|
107
|
+
agent_id=agent_id)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
# best-effort — never block the write path on the verbatim tier
|
|
110
|
+
import sys as _sys
|
|
111
|
+
print(f"[verbatim] store_chunk failed: {e}", file=_sys.stderr)
|
|
71
112
|
|
|
72
113
|
# ------------------------------------------------------------------
|
|
73
114
|
def _name_of(self, user_id: str) -> str | None:
|
|
@@ -165,6 +206,25 @@ class MemoryWriter:
|
|
|
165
206
|
chunk_id = self.store.add_chunk(
|
|
166
207
|
text, user_id=user_id, agent_id=agent_id, run_id=run_id,
|
|
167
208
|
ts=msg_time, source=source or role)
|
|
209
|
+
# v0.5.3: ALSO push the raw chunk into the verbatim tier
|
|
210
|
+
# (FTS5 + int8 vector). This is the MemPalace-style layer
|
|
211
|
+
# the canonical-LongMemEval diagnosis called for — single-
|
|
212
|
+
# session factoids ("What restaurant did they mention?")
|
|
213
|
+
# retrieve BM25 hits from these raw chunks, bypassing the
|
|
214
|
+
# 61-pattern extractor that misses natural speech.
|
|
215
|
+
# The verbatim plugin stores session_id (best-effort: the
|
|
216
|
+
# caller rarely passes one — we use run_id as a proxy)
|
|
217
|
+
# and source_tx_id (the chunk_id from the structured tier's
|
|
218
|
+
# chunks table, so the EXTRACTED_FROM edge cross-references).
|
|
219
|
+
try:
|
|
220
|
+
_src_tx_id = int(chunk_id) if str(chunk_id).isdigit() else None
|
|
221
|
+
except Exception:
|
|
222
|
+
_src_tx_id = None
|
|
223
|
+
self._verbatim_store_chunk(
|
|
224
|
+
text=text, user_id=user_id,
|
|
225
|
+
session_id=run_id or agent_id or user_id,
|
|
226
|
+
source_tx_id=_src_tx_id,
|
|
227
|
+
agent_id=agent_id)
|
|
168
228
|
verdict = injection_scan(text, self.cfg.quarantine_injection)
|
|
169
229
|
if not verdict.quarantined and self.cfg.quarantine_contagion:
|
|
170
230
|
cv = contagion_scan(text, self._tainted_corpus(user_id),
|
|
@@ -129,6 +129,42 @@ class Config:
|
|
|
129
129
|
prefilter_threshold: float = 0.08 # combined score below this → drop
|
|
130
130
|
prefilter_min_keep: int = 3 # always keep at least this many
|
|
131
131
|
|
|
132
|
+
# --- Verbatim tier (MemPalace-style FTS5 + dense over raw chunks) -----
|
|
133
|
+
# When True, MemoryWriter.add() ALSO stores every raw user message in
|
|
134
|
+
# the verbatim_chunks FTS5 virtual table + a HashingEmbedder vector in
|
|
135
|
+
# verbatim_vectors. The reader's verbatim_bridge then surfaces these
|
|
136
|
+
# chunks alongside fact-triple hits, giving the system MemPalace-style
|
|
137
|
+
# factoid recall ("What restaurant did they mention?") without an LLM.
|
|
138
|
+
# The verbatim tier is the proven fix for the canonical-LongMemEval
|
|
139
|
+
# single_session catastrophe (0.222 → expected ~0.7+ with this tier).
|
|
140
|
+
# Default ON in v0.5.3+ — turning it off leaves only the structured
|
|
141
|
+
# tier (VSA over fact triples), which misses natural-human-language
|
|
142
|
+
# factoids the deterministic extractor couldn't parse into triples.
|
|
143
|
+
verbatim_ingest_enabled: bool = True # store raw chunks on add()
|
|
144
|
+
verbatim_search_enabled: bool = True # query verbatim at search time
|
|
145
|
+
verbatim_k_at_search: int = 30 # top-k verbatim hits per query
|
|
146
|
+
verbatim_fusion_weight: float = 0.5 # weight in context_block fusion
|
|
147
|
+
verbatim_min_score: float = 0.05 # below this → skip
|
|
148
|
+
verbatim_boost_first_chunk_only: bool = False # give first chunk a small boost
|
|
149
|
+
# v0.5.4: how many chunks before/after each BM25 hit to surface as
|
|
150
|
+
# "neighbor context". Catches the "Target" / "Veja" / "Hawaii"
|
|
151
|
+
# failure mode where the answer is in the assistant reply that
|
|
152
|
+
# immediately follows the user-message hit. 0 disables. Default 1
|
|
153
|
+
# (one before + one after per hit) — keeps context_block bounded.
|
|
154
|
+
verbatim_neighbor_window: int = 1
|
|
155
|
+
|
|
156
|
+
# --- recall_step in production search path ---------------------------
|
|
157
|
+
# When True, Memory.search() ALSO runs recall_step (asymmetric step-
|
|
158
|
+
# distance boost) and concatenates its context_block onto the standard
|
|
159
|
+
# search result. This is the multi_session fix: facts from scrolled-out
|
|
160
|
+
# sessions get surfaced via the step-distance boost, not just access_count.
|
|
161
|
+
# Default ON in v0.5.3+ — all callers benefit. Turning it off disables
|
|
162
|
+
# the multi_session retrieval fix.
|
|
163
|
+
recall_step_in_search: bool = True
|
|
164
|
+
recall_step_window: int = 20 # standard LLM context window
|
|
165
|
+
recall_step_k: int = 10 # top-k from recall_step
|
|
166
|
+
recall_step_min_messages: int = 25 # only fire if total ingested >= this
|
|
167
|
+
|
|
132
168
|
# --- FadeMem-style forgetting (retention decay + sleep sweeps) ----------
|
|
133
169
|
# When True, the consolidate() pass also runs a FadeMem sweep that
|
|
134
170
|
# decays retention scores, marks low-retention facts for deactivation,
|