mneme-core 2.0.0__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.
- mneme_core-2.0.0/.gitignore +88 -0
- mneme_core-2.0.0/PKG-INFO +75 -0
- mneme_core-2.0.0/README.md +32 -0
- mneme_core-2.0.0/pyproject.toml +125 -0
- mneme_core-2.0.0/src/mneme_core/__init__.py +4 -0
- mneme_core-2.0.0/src/mneme_core/__main__.py +8 -0
- mneme_core-2.0.0/src/mneme_core/approval.py +183 -0
- mneme_core-2.0.0/src/mneme_core/audit.py +82 -0
- mneme_core-2.0.0/src/mneme_core/bench/__init__.py +40 -0
- mneme_core-2.0.0/src/mneme_core/bench/hardware.py +110 -0
- mneme_core-2.0.0/src/mneme_core/bench/harness.py +258 -0
- mneme_core-2.0.0/src/mneme_core/bench/metrics.py +142 -0
- mneme_core-2.0.0/src/mneme_core/bench/synth.py +207 -0
- mneme_core-2.0.0/src/mneme_core/capability.py +131 -0
- mneme_core-2.0.0/src/mneme_core/cli.py +1437 -0
- mneme_core-2.0.0/src/mneme_core/compression/__init__.py +106 -0
- mneme_core-2.0.0/src/mneme_core/compression/config.py +100 -0
- mneme_core-2.0.0/src/mneme_core/compression/ledger.py +447 -0
- mneme_core-2.0.0/src/mneme_core/compression/llm.py +155 -0
- mneme_core-2.0.0/src/mneme_core/compression/pipeline.py +543 -0
- mneme_core-2.0.0/src/mneme_core/compression/prompts/compress-en.md +108 -0
- mneme_core-2.0.0/src/mneme_core/compression/staging.py +363 -0
- mneme_core-2.0.0/src/mneme_core/connectors.py +158 -0
- mneme_core-2.0.0/src/mneme_core/connectors_net.py +231 -0
- mneme_core-2.0.0/src/mneme_core/console.py +278 -0
- mneme_core-2.0.0/src/mneme_core/distill/__init__.py +74 -0
- mneme_core-2.0.0/src/mneme_core/distill/adaptive_topk.py +65 -0
- mneme_core-2.0.0/src/mneme_core/distill/audit.py +248 -0
- mneme_core-2.0.0/src/mneme_core/distill/compressed_format.py +90 -0
- mneme_core-2.0.0/src/mneme_core/distill/injection_dedup.py +101 -0
- mneme_core-2.0.0/src/mneme_core/distill/shell_compress.py +207 -0
- mneme_core-2.0.0/src/mneme_core/fts5/__init__.py +5 -0
- mneme_core-2.0.0/src/mneme_core/fts5/indexer.py +675 -0
- mneme_core-2.0.0/src/mneme_core/fts5/locale/__init__.py +6 -0
- mneme_core-2.0.0/src/mneme_core/fts5/locale/tr.py +120 -0
- mneme_core-2.0.0/src/mneme_core/injection.py +76 -0
- mneme_core-2.0.0/src/mneme_core/kg/__init__.py +42 -0
- mneme_core-2.0.0/src/mneme_core/kg/client.py +97 -0
- mneme_core-2.0.0/src/mneme_core/kg/episode_stage.py +190 -0
- mneme_core-2.0.0/src/mneme_core/kg/flush.py +40 -0
- mneme_core-2.0.0/src/mneme_core/kg/worker.py +326 -0
- mneme_core-2.0.0/src/mneme_core/modes.py +375 -0
- mneme_core-2.0.0/src/mneme_core/modes_cli.py +151 -0
- mneme_core-2.0.0/src/mneme_core/patterns.py +265 -0
- mneme_core-2.0.0/src/mneme_core/privacy.py +90 -0
- mneme_core-2.0.0/src/mneme_core/py.typed +1 -0
- mneme_core-2.0.0/src/mneme_core/retrieval/__init__.py +54 -0
- mneme_core-2.0.0/src/mneme_core/retrieval/dense.py +425 -0
- mneme_core-2.0.0/src/mneme_core/retrieval/planner.py +73 -0
- mneme_core-2.0.0/src/mneme_core/retrieval/rrf.py +412 -0
- mneme_core-2.0.0/src/mneme_core/retrieval/telemetry.py +83 -0
- mneme_core-2.0.0/src/mneme_core/security.py +181 -0
- mneme_core-2.0.0/src/mneme_core/security_bench.py +241 -0
- mneme_core-2.0.0/src/mneme_core/taint.py +115 -0
- mneme_core-2.0.0/src/mneme_core/telemetry/__init__.py +27 -0
- mneme_core-2.0.0/src/mneme_core/telemetry/writer.py +237 -0
- mneme_core-2.0.0/src/mneme_core/temporal/__init__.py +58 -0
- mneme_core-2.0.0/src/mneme_core/temporal/backend.py +173 -0
- mneme_core-2.0.0/src/mneme_core/temporal/claim.py +237 -0
- mneme_core-2.0.0/src/mneme_core/temporal/extract.py +279 -0
- mneme_core-2.0.0/src/mneme_core/temporal/graphiti_export.py +160 -0
- mneme_core-2.0.0/src/mneme_core/temporal/index.py +249 -0
- mneme_core-2.0.0/src/mneme_core/temporal/query.py +217 -0
- mneme_core-2.0.0/src/mneme_core/trajectory.py +287 -0
- mneme_core-2.0.0/src/mneme_core/vault/__init__.py +19 -0
- mneme_core-2.0.0/src/mneme_core/vault/atomic_write.py +73 -0
- mneme_core-2.0.0/src/mneme_core/vault/config.py +177 -0
- mneme_core-2.0.0/src/mneme_core/vault/file_lock.py +110 -0
- mneme_core-2.0.0/src/mneme_core/vault/frontmatter.py +344 -0
- mneme_core-2.0.0/tests/__init__.py +1 -0
- mneme_core-2.0.0/tests/fixtures/canonical_memory_types.json +46 -0
- mneme_core-2.0.0/tests/fixtures/tr_locale_vectors.json +146 -0
- mneme_core-2.0.0/tests/integration/__init__.py +1 -0
- mneme_core-2.0.0/tests/integration/test_cli_doctor.py +406 -0
- mneme_core-2.0.0/tests/integration/test_compression_pipeline.py +280 -0
- mneme_core-2.0.0/tests/integration/test_concurrent_writes.py +122 -0
- mneme_core-2.0.0/tests/integration/test_dense_rrf_integration.py +190 -0
- mneme_core-2.0.0/tests/integration/test_distill_audit.py +108 -0
- mneme_core-2.0.0/tests/integration/test_doctor_command.py +404 -0
- mneme_core-2.0.0/tests/integration/test_fts5_indexer.py +1004 -0
- mneme_core-2.0.0/tests/integration/test_index_meta_and_prune.py +313 -0
- mneme_core-2.0.0/tests/integration/test_kg_episode_stage.py +154 -0
- mneme_core-2.0.0/tests/integration/test_kg_flush.py +67 -0
- mneme_core-2.0.0/tests/integration/test_kg_neo4j_integration.py +60 -0
- mneme_core-2.0.0/tests/integration/test_kg_worker.py +210 -0
- mneme_core-2.0.0/tests/integration/test_patterns.py +195 -0
- mneme_core-2.0.0/tests/integration/test_privacy_redaction.py +345 -0
- mneme_core-2.0.0/tests/integration/test_retrieval.py +393 -0
- mneme_core-2.0.0/tests/integration/test_staging.py +263 -0
- mneme_core-2.0.0/tests/integration/test_telemetry.py +240 -0
- mneme_core-2.0.0/tests/integration/test_temporal_datetime_roundtrip.py +390 -0
- mneme_core-2.0.0/tests/integration/test_temporal_integration.py +309 -0
- mneme_core-2.0.0/tests/integration/test_trajectory.py +119 -0
- mneme_core-2.0.0/tests/unit/__init__.py +1 -0
- mneme_core-2.0.0/tests/unit/test_approval.py +345 -0
- mneme_core-2.0.0/tests/unit/test_atomic_write.py +98 -0
- mneme_core-2.0.0/tests/unit/test_audit.py +69 -0
- mneme_core-2.0.0/tests/unit/test_bench_hardware.py +64 -0
- mneme_core-2.0.0/tests/unit/test_bench_harness.py +391 -0
- mneme_core-2.0.0/tests/unit/test_bench_metrics.py +120 -0
- mneme_core-2.0.0/tests/unit/test_bench_synth.py +82 -0
- mneme_core-2.0.0/tests/unit/test_canonical_memory_types_parity.py +62 -0
- mneme_core-2.0.0/tests/unit/test_capability.py +272 -0
- mneme_core-2.0.0/tests/unit/test_compression_config.py +91 -0
- mneme_core-2.0.0/tests/unit/test_compression_ledger.py +250 -0
- mneme_core-2.0.0/tests/unit/test_compression_ledger_reservation.py +200 -0
- mneme_core-2.0.0/tests/unit/test_compression_llm.py +166 -0
- mneme_core-2.0.0/tests/unit/test_connectors.py +100 -0
- mneme_core-2.0.0/tests/unit/test_connectors_net.py +435 -0
- mneme_core-2.0.0/tests/unit/test_console.py +273 -0
- mneme_core-2.0.0/tests/unit/test_credential_perms.py +100 -0
- mneme_core-2.0.0/tests/unit/test_dense.py +498 -0
- mneme_core-2.0.0/tests/unit/test_distill_adaptive_topk.py +64 -0
- mneme_core-2.0.0/tests/unit/test_distill_compressed_format.py +85 -0
- mneme_core-2.0.0/tests/unit/test_distill_injection_dedup.py +86 -0
- mneme_core-2.0.0/tests/unit/test_distill_shell_compress.py +111 -0
- mneme_core-2.0.0/tests/unit/test_file_lock.py +434 -0
- mneme_core-2.0.0/tests/unit/test_frontmatter.py +356 -0
- mneme_core-2.0.0/tests/unit/test_graphiti_export.py +270 -0
- mneme_core-2.0.0/tests/unit/test_injection.py +92 -0
- mneme_core-2.0.0/tests/unit/test_keypoints_extraction.py +104 -0
- mneme_core-2.0.0/tests/unit/test_kg_client.py +51 -0
- mneme_core-2.0.0/tests/unit/test_ledger_lost_write.py +232 -0
- mneme_core-2.0.0/tests/unit/test_migrate_schema.py +137 -0
- mneme_core-2.0.0/tests/unit/test_modes.py +296 -0
- mneme_core-2.0.0/tests/unit/test_modes_cli.py +184 -0
- mneme_core-2.0.0/tests/unit/test_retrieval_planner.py +126 -0
- mneme_core-2.0.0/tests/unit/test_retrieval_telemetry.py +265 -0
- mneme_core-2.0.0/tests/unit/test_retrieve_dense_seam.py +59 -0
- mneme_core-2.0.0/tests/unit/test_review_fixes.py +650 -0
- mneme_core-2.0.0/tests/unit/test_rrf_conn_leak.py +100 -0
- mneme_core-2.0.0/tests/unit/test_security.py +100 -0
- mneme_core-2.0.0/tests/unit/test_security_bench.py +246 -0
- mneme_core-2.0.0/tests/unit/test_staging_size_cap_perf.py +208 -0
- mneme_core-2.0.0/tests/unit/test_taint.py +162 -0
- mneme_core-2.0.0/tests/unit/test_temporal_claim.py +284 -0
- mneme_core-2.0.0/tests/unit/test_temporal_extract.py +346 -0
- mneme_core-2.0.0/tests/unit/test_temporal_query.py +245 -0
- mneme_core-2.0.0/tests/unit/test_tr_normalize.py +204 -0
- mneme_core-2.0.0/tests/unit/test_vault_config.py +109 -0
- mneme_core-2.0.0/tests/unit/test_vault_config_compression.py +35 -0
- mneme_core-2.0.0/tests/unit/test_vault_config_kg.py +45 -0
- mneme_core-2.0.0/tests/unit/test_vault_config_patterns_trajectory.py +29 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
MANIFEST
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.coverage
|
|
26
|
+
.coverage.*
|
|
27
|
+
.cache
|
|
28
|
+
nosetests.xml
|
|
29
|
+
coverage.xml
|
|
30
|
+
*.cover
|
|
31
|
+
.hypothesis/
|
|
32
|
+
htmlcov/
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
|
|
36
|
+
# Virtual envs
|
|
37
|
+
.env
|
|
38
|
+
.venv
|
|
39
|
+
env/
|
|
40
|
+
venv/
|
|
41
|
+
ENV/
|
|
42
|
+
env.bak/
|
|
43
|
+
venv.bak/
|
|
44
|
+
|
|
45
|
+
# Node
|
|
46
|
+
node_modules/
|
|
47
|
+
npm-debug.log*
|
|
48
|
+
yarn-debug.log*
|
|
49
|
+
yarn-error.log*
|
|
50
|
+
pnpm-debug.log*
|
|
51
|
+
.pnpm-store/
|
|
52
|
+
*.tsbuildinfo
|
|
53
|
+
|
|
54
|
+
# TypeScript
|
|
55
|
+
*.js.map
|
|
56
|
+
*.d.ts.map
|
|
57
|
+
|
|
58
|
+
# IDE
|
|
59
|
+
.vscode/
|
|
60
|
+
.idea/
|
|
61
|
+
*.swp
|
|
62
|
+
*.swo
|
|
63
|
+
.DS_Store
|
|
64
|
+
|
|
65
|
+
# OS
|
|
66
|
+
Thumbs.db
|
|
67
|
+
desktop.ini
|
|
68
|
+
|
|
69
|
+
# mneme local
|
|
70
|
+
.mneme/
|
|
71
|
+
*.local.json
|
|
72
|
+
vault-test/
|
|
73
|
+
benchmarks/results/
|
|
74
|
+
benchmarks/_runs/
|
|
75
|
+
benchmarks/*/output/
|
|
76
|
+
benchmarks/*/result.json
|
|
77
|
+
benchmarks/*/hardware.json
|
|
78
|
+
|
|
79
|
+
# Secrets
|
|
80
|
+
.env.local
|
|
81
|
+
.env.*.local
|
|
82
|
+
secrets/
|
|
83
|
+
*.key
|
|
84
|
+
*.pem
|
|
85
|
+
|
|
86
|
+
# Build artifacts
|
|
87
|
+
*.tgz
|
|
88
|
+
*.whl
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mneme-core
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Vault-native memory engine for Claude Code. FTS5 BM25, RRF-ready retrieval, gated Graphiti KG, and adaptive context tooling.
|
|
5
|
+
Project-URL: Homepage, https://github.com/TheGoatPsy/mneme
|
|
6
|
+
Project-URL: Documentation, https://github.com/TheGoatPsy/mneme/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/TheGoatPsy/mneme.git
|
|
8
|
+
Project-URL: Issues, https://github.com/TheGoatPsy/mneme/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/TheGoatPsy/mneme/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Onour Impram
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: claude-code,fts5,graphiti,knowledge-graph,markdown,mcp,memory,rrf
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: click<9,>=8.1
|
|
23
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
24
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
25
|
+
Requires-Dist: structlog<25,>=24.1
|
|
26
|
+
Provides-Extra: compression
|
|
27
|
+
Requires-Dist: anthropic<1,>=0.28; extra == 'compression'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.4.7; extra == 'dev'
|
|
33
|
+
Requires-Dist: testcontainers>=4.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
35
|
+
Provides-Extra: full
|
|
36
|
+
Requires-Dist: anthropic<1,>=0.28; extra == 'full'
|
|
37
|
+
Requires-Dist: graphiti-core<1,>=0.3; extra == 'full'
|
|
38
|
+
Requires-Dist: neo4j<6,>=5.21; extra == 'full'
|
|
39
|
+
Requires-Dist: onnxruntime<2,>=1.18; extra == 'full'
|
|
40
|
+
Provides-Extra: standard
|
|
41
|
+
Requires-Dist: onnxruntime<2,>=1.18; extra == 'standard'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# mneme-core
|
|
45
|
+
|
|
46
|
+
Python core for [mneme](https://github.com/TheGoatPsy/mneme): vault-native memory for Claude Code, also usable from Codex and any MCP client. mneme is Claude-Code-native by origin and client-neutral at the core.
|
|
47
|
+
|
|
48
|
+
This package provides:
|
|
49
|
+
|
|
50
|
+
- FTS5 BM25 full-text indexer with language-aware normalization (Turkish casefold module included as utility).
|
|
51
|
+
- RRF-ready retrieval pipeline at `k=60`, with FTS5 shipped and optional backends injectable by callers.
|
|
52
|
+
- Graphiti bi-temporal knowledge graph adapter, gated by the full profile and local Neo4j.
|
|
53
|
+
- Roadmap dense embedding adapter. The v1.0 standard profile reserves the runtime slot but does not ship packaged LEANN retrieval.
|
|
54
|
+
- Background compression pipeline with 4-D rubric (Accuracy, Depth, Context, Continuity) and cost cap ledger.
|
|
55
|
+
- Adaptive Context Layer: `distill.shell_compress`, `distill.injection_dedup`, `distill.adaptive_topk`, `distill.compressed_format`, and the `mneme audit` CLI.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install mneme-core
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For development:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install -e ".[dev]"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quality Gates
|
|
70
|
+
|
|
71
|
+
This package targets Python 3.11+ and passes `ruff` lint plus `mypy --strict`. Test coverage minimum is 80 percent for business logic.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT. See LICENSE in the repository root.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# mneme-core
|
|
2
|
+
|
|
3
|
+
Python core for [mneme](https://github.com/TheGoatPsy/mneme): vault-native memory for Claude Code, also usable from Codex and any MCP client. mneme is Claude-Code-native by origin and client-neutral at the core.
|
|
4
|
+
|
|
5
|
+
This package provides:
|
|
6
|
+
|
|
7
|
+
- FTS5 BM25 full-text indexer with language-aware normalization (Turkish casefold module included as utility).
|
|
8
|
+
- RRF-ready retrieval pipeline at `k=60`, with FTS5 shipped and optional backends injectable by callers.
|
|
9
|
+
- Graphiti bi-temporal knowledge graph adapter, gated by the full profile and local Neo4j.
|
|
10
|
+
- Roadmap dense embedding adapter. The v1.0 standard profile reserves the runtime slot but does not ship packaged LEANN retrieval.
|
|
11
|
+
- Background compression pipeline with 4-D rubric (Accuracy, Depth, Context, Continuity) and cost cap ledger.
|
|
12
|
+
- Adaptive Context Layer: `distill.shell_compress`, `distill.injection_dedup`, `distill.adaptive_topk`, `distill.compressed_format`, and the `mneme audit` CLI.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install mneme-core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For development:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quality Gates
|
|
27
|
+
|
|
28
|
+
This package targets Python 3.11+ and passes `ruff` lint plus `mypy --strict`. Test coverage minimum is 80 percent for business logic.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT. See LICENSE in the repository root.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mneme-core"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "Vault-native memory engine for Claude Code. FTS5 BM25, RRF-ready retrieval, gated Graphiti KG, and adaptive context tooling."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Onour Impram" }]
|
|
13
|
+
keywords = ["claude-code", "memory", "fts5", "rrf", "graphiti", "knowledge-graph", "markdown", "mcp"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Topic :: Text Processing :: Indexing"
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pydantic>=2.7,<3",
|
|
26
|
+
"structlog>=24.1,<25",
|
|
27
|
+
"click>=8.1,<9",
|
|
28
|
+
"pyyaml>=6.0,<7"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
# Standard profile: reserved runtime slot for the roadmap dense adapter.
|
|
33
|
+
# v1.0 ships the RRF protocol and FTS5 leg; the packaged LEANN adapter
|
|
34
|
+
# is intentionally not advertised as shipped.
|
|
35
|
+
standard = [
|
|
36
|
+
"onnxruntime>=1.18,<2"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Full profile: temporal knowledge graph. Adds Neo4j driver and the
|
|
40
|
+
# Graphiti episode/community engine. Lite/standard profiles never
|
|
41
|
+
# import these, and ``mneme_core.kg`` modules lazy-import graphiti_core
|
|
42
|
+
# so installing without this extra is a clean no-op.
|
|
43
|
+
full = [
|
|
44
|
+
"onnxruntime>=1.18,<2",
|
|
45
|
+
"neo4j>=5.21,<6",
|
|
46
|
+
"graphiti-core>=0.3,<1",
|
|
47
|
+
"anthropic>=0.28,<1"
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# Background AI compression (Phase F) lives behind an opt-in flag and
|
|
51
|
+
# needs an Anthropic client. The compression code path is feature-
|
|
52
|
+
# gated on the operator setting ``compression_enabled = true`` plus
|
|
53
|
+
# the API key being present in env, so the dep itself is cheap.
|
|
54
|
+
compression = [
|
|
55
|
+
"anthropic>=0.28,<1"
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
dev = [
|
|
59
|
+
"pytest>=8.2",
|
|
60
|
+
"pytest-cov>=5.0",
|
|
61
|
+
"ruff>=0.4.7",
|
|
62
|
+
"mypy>=1.10",
|
|
63
|
+
"types-PyYAML>=6.0",
|
|
64
|
+
"testcontainers>=4.0"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[project.scripts]
|
|
68
|
+
# Standalone core exposes its CLI as `mneme-core`. The user-facing
|
|
69
|
+
# `mneme` command is owned solely by mneme-cc-plugin, which re-registers
|
|
70
|
+
# these core command groups under it. Both packages previously declared a
|
|
71
|
+
# `mneme` script, so a co-install resolved to whichever pip wrote last.
|
|
72
|
+
mneme-core = "mneme_core.cli:main"
|
|
73
|
+
mneme-audit = "mneme_core.distill.audit:main"
|
|
74
|
+
mneme-modes = "mneme_core.modes_cli:main"
|
|
75
|
+
mneme-console = "mneme_core.console:main"
|
|
76
|
+
|
|
77
|
+
[project.urls]
|
|
78
|
+
Homepage = "https://github.com/TheGoatPsy/mneme"
|
|
79
|
+
Documentation = "https://github.com/TheGoatPsy/mneme/tree/main/docs"
|
|
80
|
+
Repository = "https://github.com/TheGoatPsy/mneme.git"
|
|
81
|
+
Issues = "https://github.com/TheGoatPsy/mneme/issues"
|
|
82
|
+
Changelog = "https://github.com/TheGoatPsy/mneme/blob/main/CHANGELOG.md"
|
|
83
|
+
|
|
84
|
+
[tool.hatch.build.targets.wheel]
|
|
85
|
+
packages = ["src/mneme_core"]
|
|
86
|
+
|
|
87
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
88
|
+
"src/mneme_core/compression/prompts/compress-en.md" = "mneme_core/compression/prompts/compress-en.md"
|
|
89
|
+
|
|
90
|
+
[tool.ruff]
|
|
91
|
+
line-length = 100
|
|
92
|
+
target-version = "py311"
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint]
|
|
95
|
+
select = ["E", "F", "W", "I", "B", "UP", "C4", "SIM"]
|
|
96
|
+
|
|
97
|
+
[tool.mypy]
|
|
98
|
+
strict = true
|
|
99
|
+
python_version = "3.11"
|
|
100
|
+
files = ["src/mneme_core"]
|
|
101
|
+
|
|
102
|
+
[[tool.mypy.overrides]]
|
|
103
|
+
# Optional-profile dependencies. lite/standard installs (and the CI
|
|
104
|
+
# type-check env, which installs only [dev]) do not have these; the code
|
|
105
|
+
# lazy-imports them behind runtime feature gates, so missing stubs here
|
|
106
|
+
# are expected rather than a type error.
|
|
107
|
+
module = ["graphiti_core.*", "neo4j.*", "anthropic.*", "onnxruntime.*"]
|
|
108
|
+
ignore_missing_imports = true
|
|
109
|
+
|
|
110
|
+
[tool.pytest.ini_options]
|
|
111
|
+
testpaths = ["tests"]
|
|
112
|
+
python_files = "test_*.py"
|
|
113
|
+
addopts = "-v --strict-markers --cov=mneme_core --cov-report=term-missing --cov-fail-under=80"
|
|
114
|
+
|
|
115
|
+
[tool.coverage.run]
|
|
116
|
+
branch = true
|
|
117
|
+
source = ["mneme_core"]
|
|
118
|
+
|
|
119
|
+
[tool.coverage.report]
|
|
120
|
+
exclude_lines = [
|
|
121
|
+
"pragma: no cover",
|
|
122
|
+
"raise NotImplementedError",
|
|
123
|
+
"if __name__ == .__main__.:",
|
|
124
|
+
"if TYPE_CHECKING:",
|
|
125
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Human-approval gate for memory edits (conflict-resolution #4).
|
|
2
|
+
|
|
3
|
+
An agent *proposes* a memory edit; the proposal starts in ``PENDING`` status.
|
|
4
|
+
Edits in *durable* categories (IDENTITY, PREFERENCE, CLINICAL, LEGAL,
|
|
5
|
+
FINANCIAL) must receive explicit human approval before they may be applied.
|
|
6
|
+
Edits in the EPHEMERAL category (session notes, observations) may be applied
|
|
7
|
+
while still PENDING. A REJECTED proposal can never be applied.
|
|
8
|
+
|
|
9
|
+
All user-supplied content is passed through :func:`mneme_core.privacy.redact`
|
|
10
|
+
before being stored in the proposal. Proposal IDs are deterministic
|
|
11
|
+
(``uuid.uuid5``) so re-proposing identical inputs yields the same ID.
|
|
12
|
+
|
|
13
|
+
Pure, deterministic, no IO, no network, no clock.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import dataclasses
|
|
19
|
+
import uuid
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import Enum
|
|
22
|
+
|
|
23
|
+
from .privacy import redact
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Enumerations
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
_NAMESPACE = uuid.UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8") # NAMESPACE_URL
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ProposalStatus(str, Enum): # noqa: UP042
|
|
33
|
+
"""Lifecycle status of a :class:`MemoryProposal`.
|
|
34
|
+
|
|
35
|
+
Inherits ``str`` so instances serialise directly as JSON strings and
|
|
36
|
+
compare equal to their string values without an extra ``.value`` call.
|
|
37
|
+
``UP042`` suppresses the Ruff suggestion to use ``StrEnum`` (Python 3.11+)
|
|
38
|
+
to stay consistent with the ``str`` + ``Enum`` pattern used elsewhere.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
PENDING = "PENDING"
|
|
42
|
+
APPROVED = "APPROVED"
|
|
43
|
+
REJECTED = "REJECTED"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EditCategory(str, Enum): # noqa: UP042
|
|
47
|
+
"""Semantic category of the memory edit being proposed.
|
|
48
|
+
|
|
49
|
+
* **EPHEMERAL** — low-stakes session/topic/observation; may be applied
|
|
50
|
+
without explicit approval.
|
|
51
|
+
* All other categories are *durable* and require human approval.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
EPHEMERAL = "EPHEMERAL"
|
|
55
|
+
IDENTITY = "IDENTITY"
|
|
56
|
+
PREFERENCE = "PREFERENCE"
|
|
57
|
+
CLINICAL = "CLINICAL"
|
|
58
|
+
LEGAL = "LEGAL"
|
|
59
|
+
FINANCIAL = "FINANCIAL"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
#: Categories that require explicit human approval before an edit may be applied.
|
|
63
|
+
DURABLE_CATEGORIES: frozenset[EditCategory] = frozenset(
|
|
64
|
+
{
|
|
65
|
+
EditCategory.IDENTITY,
|
|
66
|
+
EditCategory.PREFERENCE,
|
|
67
|
+
EditCategory.CLINICAL,
|
|
68
|
+
EditCategory.LEGAL,
|
|
69
|
+
EditCategory.FINANCIAL,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Proposal dataclass
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class MemoryProposal:
|
|
80
|
+
"""An immutable record of a proposed memory edit.
|
|
81
|
+
|
|
82
|
+
Attributes
|
|
83
|
+
----------
|
|
84
|
+
proposal_id:
|
|
85
|
+
Deterministic ``uuid5`` derived from ``action``, ``target_path``, and
|
|
86
|
+
the *redacted* content. Identical inputs always produce the same ID.
|
|
87
|
+
action:
|
|
88
|
+
``"create"`` | ``"update"`` | ``"delete"``.
|
|
89
|
+
target_path:
|
|
90
|
+
Vault-relative path of the note being created/modified/deleted.
|
|
91
|
+
content:
|
|
92
|
+
Proposed note content **after** redaction via
|
|
93
|
+
:func:`mneme_core.privacy.redact`.
|
|
94
|
+
category:
|
|
95
|
+
Semantic category governing approval requirements.
|
|
96
|
+
status:
|
|
97
|
+
Current lifecycle status (PENDING / APPROVED / REJECTED).
|
|
98
|
+
trust:
|
|
99
|
+
Proposer identity string; defaults to ``"agent"``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
proposal_id: str
|
|
103
|
+
action: str
|
|
104
|
+
target_path: str
|
|
105
|
+
content: str
|
|
106
|
+
category: EditCategory
|
|
107
|
+
status: ProposalStatus
|
|
108
|
+
trust: str
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Pure functions
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def requires_human_approval(category: EditCategory) -> bool:
|
|
117
|
+
"""Return ``True`` iff *category* is in :data:`DURABLE_CATEGORIES`."""
|
|
118
|
+
return category in DURABLE_CATEGORIES
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def propose(
|
|
122
|
+
*,
|
|
123
|
+
action: str,
|
|
124
|
+
target_path: str,
|
|
125
|
+
content: str,
|
|
126
|
+
category: EditCategory,
|
|
127
|
+
trust: str = "agent",
|
|
128
|
+
) -> MemoryProposal:
|
|
129
|
+
"""Create a new :class:`MemoryProposal` in PENDING status.
|
|
130
|
+
|
|
131
|
+
Content is redacted via :func:`~mneme_core.privacy.redact` before being
|
|
132
|
+
stored. The ``proposal_id`` is a deterministic ``uuid5`` derived from
|
|
133
|
+
``action + NUL + target_path + NUL + redacted_content`` so that identical
|
|
134
|
+
inputs always yield the same proposal ID.
|
|
135
|
+
"""
|
|
136
|
+
redacted = redact(content)
|
|
137
|
+
# category and trust are part of proposal identity: an EPHEMERAL and a
|
|
138
|
+
# CLINICAL proposal for the same path+content must NOT share a proposal_id,
|
|
139
|
+
# else a downstream store keyed on it could alias the durable edit to an
|
|
140
|
+
# already-applied ephemeral one and bypass the human-approval gate.
|
|
141
|
+
seed = f"{action}\x00{target_path}\x00{category.value}\x00{trust}\x00{redacted}"
|
|
142
|
+
proposal_id = str(uuid.uuid5(_NAMESPACE, seed))
|
|
143
|
+
return MemoryProposal(
|
|
144
|
+
proposal_id=proposal_id,
|
|
145
|
+
action=action,
|
|
146
|
+
target_path=target_path,
|
|
147
|
+
content=redacted,
|
|
148
|
+
category=category,
|
|
149
|
+
status=ProposalStatus.PENDING,
|
|
150
|
+
trust=trust,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def approve(proposal: MemoryProposal) -> MemoryProposal:
|
|
155
|
+
"""Return a copy of *proposal* with ``status`` set to APPROVED.
|
|
156
|
+
|
|
157
|
+
Idempotent: approving an already-approved proposal returns an equivalent
|
|
158
|
+
object unchanged.
|
|
159
|
+
"""
|
|
160
|
+
return dataclasses.replace(proposal, status=ProposalStatus.APPROVED)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def reject(proposal: MemoryProposal) -> MemoryProposal:
|
|
164
|
+
"""Return a copy of *proposal* with ``status`` set to REJECTED."""
|
|
165
|
+
return dataclasses.replace(proposal, status=ProposalStatus.REJECTED)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def can_apply(proposal: MemoryProposal) -> bool:
|
|
169
|
+
"""Return ``True`` iff the proposal may be applied to the vault.
|
|
170
|
+
|
|
171
|
+
Rules (deterministic, pure):
|
|
172
|
+
|
|
173
|
+
* A REJECTED proposal can **never** be applied.
|
|
174
|
+
* An APPROVED proposal can always be applied.
|
|
175
|
+
* A PENDING proposal may be applied only when its category is EPHEMERAL
|
|
176
|
+
(i.e. ``requires_human_approval`` is ``False``).
|
|
177
|
+
"""
|
|
178
|
+
if proposal.status == ProposalStatus.REJECTED:
|
|
179
|
+
return False
|
|
180
|
+
if proposal.status == ProposalStatus.APPROVED:
|
|
181
|
+
return True
|
|
182
|
+
# PENDING: allowed only for non-durable (ephemeral) categories.
|
|
183
|
+
return not requires_human_approval(proposal.category)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Read-only vault audit aggregator (the console surface, v1).
|
|
2
|
+
|
|
3
|
+
Produces a deterministic, read-only snapshot of the vault: note counts by
|
|
4
|
+
memory type plus a security-scan summary. This is the programmatic/text console
|
|
5
|
+
surface; a browser audit UI (timeline, graph explorer, token-ROI dashboard) is
|
|
6
|
+
deferred. The aggregator never writes and never raises on a single bad file.
|
|
7
|
+
|
|
8
|
+
No LLM, no network.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from .security import scan_vault, summarize
|
|
18
|
+
from .vault.frontmatter import parse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class AuditReport:
|
|
23
|
+
"""A read-only vault health + safety snapshot."""
|
|
24
|
+
|
|
25
|
+
note_count: int
|
|
26
|
+
type_counts: dict[str, int]
|
|
27
|
+
security: dict[str, Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_audit(vault_root: Path) -> AuditReport:
|
|
31
|
+
"""Walk the vault markdown and build a read-only audit snapshot.
|
|
32
|
+
|
|
33
|
+
Counts notes by frontmatter ``type`` (notes without parseable frontmatter
|
|
34
|
+
are bucketed as ``"(none)"``) and folds in a security-scan summary. Symlinks
|
|
35
|
+
that escape the vault are skipped; unreadable or malformed files never crash
|
|
36
|
+
the audit.
|
|
37
|
+
"""
|
|
38
|
+
root_resolved = vault_root.resolve()
|
|
39
|
+
type_counts: dict[str, int] = {}
|
|
40
|
+
note_count = 0
|
|
41
|
+
for md_path in sorted(vault_root.rglob("*.md")):
|
|
42
|
+
try:
|
|
43
|
+
resolved = md_path.resolve()
|
|
44
|
+
except OSError:
|
|
45
|
+
continue
|
|
46
|
+
if not resolved.is_relative_to(root_resolved):
|
|
47
|
+
continue
|
|
48
|
+
try:
|
|
49
|
+
text = md_path.read_text(encoding="utf-8", errors="replace")
|
|
50
|
+
except OSError:
|
|
51
|
+
continue
|
|
52
|
+
note_count += 1
|
|
53
|
+
type_name = "(none)"
|
|
54
|
+
try:
|
|
55
|
+
front, _ = parse(text)
|
|
56
|
+
if front is not None and front.type:
|
|
57
|
+
type_name = front.type
|
|
58
|
+
except Exception: # noqa: BLE001 - audit must never crash on a bad note
|
|
59
|
+
type_name = "(unparseable)"
|
|
60
|
+
type_counts[type_name] = type_counts.get(type_name, 0) + 1
|
|
61
|
+
|
|
62
|
+
security = summarize(scan_vault(vault_root))
|
|
63
|
+
return AuditReport(note_count=note_count, type_counts=type_counts, security=security)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def render_audit(report: AuditReport) -> str:
|
|
67
|
+
"""Render an :class:`AuditReport` as a plain-text report."""
|
|
68
|
+
lines = ["# Vault audit", "", f"Notes: {report.note_count}", "", "## By type"]
|
|
69
|
+
if report.type_counts:
|
|
70
|
+
for type_name, count in sorted(report.type_counts.items()):
|
|
71
|
+
lines.append(f"- {type_name}: {count}")
|
|
72
|
+
else:
|
|
73
|
+
lines.append("- (no notes)")
|
|
74
|
+
lines.extend(["", "## Security"])
|
|
75
|
+
sec = report.security
|
|
76
|
+
lines.append(f"- files flagged: {sec.get('files_flagged', 0)}")
|
|
77
|
+
lines.append(f"- total findings: {sec.get('total_findings', 0)}")
|
|
78
|
+
by_kind = sec.get("by_kind", {})
|
|
79
|
+
if isinstance(by_kind, dict):
|
|
80
|
+
for kind, count in sorted(by_kind.items()):
|
|
81
|
+
lines.append(f" - {kind}: {count}")
|
|
82
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Benchmark helpers shared by the ``benchmarks/`` runner scripts.
|
|
2
|
+
|
|
3
|
+
Three focused submodules:
|
|
4
|
+
|
|
5
|
+
* :mod:`mneme_core.bench.metrics` - information-retrieval metric primitives
|
|
6
|
+
(nDCG@k, Recall@k, MRR) plus quantile helpers for latency distributions.
|
|
7
|
+
* :mod:`mneme_core.bench.synth` - deterministic synthetic corpus and query
|
|
8
|
+
generator. Tests and benchmarks both lean on this so they exercise the
|
|
9
|
+
same shape of data.
|
|
10
|
+
* :mod:`mneme_core.bench.hardware` - hardware/runtime probe that emits
|
|
11
|
+
``hardware.json`` next to every benchmark result for reproducibility.
|
|
12
|
+
|
|
13
|
+
The submodules are intentionally small and side-effect-free. Bench scripts
|
|
14
|
+
own their own argparse, output, and CI guards.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .hardware import HardwareSnapshot, capture_hardware
|
|
18
|
+
from .metrics import (
|
|
19
|
+
mean_reciprocal_rank,
|
|
20
|
+
ndcg_at_k,
|
|
21
|
+
percentiles,
|
|
22
|
+
recall_at_k,
|
|
23
|
+
)
|
|
24
|
+
from .synth import (
|
|
25
|
+
SyntheticCorpus,
|
|
26
|
+
SyntheticQuery,
|
|
27
|
+
build_synthetic_corpus,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"HardwareSnapshot",
|
|
32
|
+
"SyntheticCorpus",
|
|
33
|
+
"SyntheticQuery",
|
|
34
|
+
"build_synthetic_corpus",
|
|
35
|
+
"capture_hardware",
|
|
36
|
+
"mean_reciprocal_rank",
|
|
37
|
+
"ndcg_at_k",
|
|
38
|
+
"percentiles",
|
|
39
|
+
"recall_at_k",
|
|
40
|
+
]
|