memd-engine 0.5.0__tar.gz → 0.5.2__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.
Files changed (163) hide show
  1. memd_engine-0.5.2/PKG-INFO +319 -0
  2. memd_engine-0.5.2/README.md +267 -0
  3. {memd_engine-0.5.0 → memd_engine-0.5.2}/pyproject.toml +5 -2
  4. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/__init__.py +1 -1
  5. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/engine/memory.py +7 -1
  6. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/metering.py +16 -5
  7. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/index/ann_usearch.py +1 -1
  8. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/index/sqlite_index.py +25 -2
  9. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/index/tantivy_lexical.py +1 -1
  10. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/pipeline/extractor.py +13 -3
  11. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/rerank.py +2 -2
  12. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/server/http.py +29 -0
  13. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/engine.py +16 -7
  14. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/replica.py +9 -4
  15. memd_engine-0.5.2/src/memd_engine.egg-info/PKG-INFO +319 -0
  16. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd_engine.egg-info/SOURCES.txt +2 -1
  17. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_http.py +33 -0
  18. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_llm_extractor.py +56 -4
  19. memd_engine-0.5.2/tests/test_pass19_sigkill.py +147 -0
  20. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass27_scale_perf.py +6 -1
  21. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pending_fixes.py +4 -1
  22. memd_engine-0.5.2/tests/test_pypi_readme.py +31 -0
  23. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_read_replicas.py +3 -1
  24. memd_engine-0.5.2/tests/test_ste_check.py +66 -0
  25. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_write_forwarding.py +42 -7
  26. memd_engine-0.5.0/PKG-INFO +0 -1230
  27. memd_engine-0.5.0/README-engine.md +0 -1181
  28. memd_engine-0.5.0/README.md +0 -172
  29. memd_engine-0.5.0/src/memd_engine.egg-info/PKG-INFO +0 -1230
  30. memd_engine-0.5.0/tests/test_pass19_sigkill.py +0 -111
  31. {memd_engine-0.5.0 → memd_engine-0.5.2}/LICENSE +0 -0
  32. {memd_engine-0.5.0 → memd_engine-0.5.2}/setup.cfg +0 -0
  33. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/cli.py +0 -0
  34. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/core/__init__.py +0 -0
  35. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/core/schema.py +0 -0
  36. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/engine/__init__.py +0 -0
  37. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/engine/forward.py +0 -0
  38. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/core.py +0 -0
  39. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/run.py +0 -0
  40. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/suites/__init__.py +0 -0
  41. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/suites/adversarial.py +0 -0
  42. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/suites/halumem_ops.py +0 -0
  43. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/harness/suites/longmemeval_synthetic.py +0 -0
  44. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/__init__.py +0 -0
  45. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/app.py +0 -0
  46. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/billing.py +0 -0
  47. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/plans.py +0 -0
  48. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/hosted/store.py +0 -0
  49. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/index/__init__.py +0 -0
  50. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/metrics.py +0 -0
  51. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/pipeline/__init__.py +0 -0
  52. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/pipeline/consolidation.py +0 -0
  53. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/pipeline/embedder.py +0 -0
  54. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/__init__.py +0 -0
  55. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/dates.py +0 -0
  56. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/fusion.py +0 -0
  57. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/packing.py +0 -0
  58. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/query/planner.py +0 -0
  59. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/sdk/__init__.py +0 -0
  60. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/sdk/client.py +0 -0
  61. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/server/__init__.py +0 -0
  62. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/server/auth.py +0 -0
  63. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/server/cluster.py +0 -0
  64. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/server/mcp_server.py +0 -0
  65. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/__init__.py +0 -0
  66. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/audit.py +0 -0
  67. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/crypto.py +0 -0
  68. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/objectstore.py +0 -0
  69. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd/storage/s3store.py +0 -0
  70. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd_engine.egg-info/dependency_links.txt +0 -0
  71. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd_engine.egg-info/entry_points.txt +0 -0
  72. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd_engine.egg-info/requires.txt +0 -0
  73. {memd_engine-0.5.0 → memd_engine-0.5.2}/src/memd_engine.egg-info/top_level.txt +0 -0
  74. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_adversarial_probes.py +0 -0
  75. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_ann_usearch.py +0 -0
  76. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_billing_hardening.py +0 -0
  77. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_billing_reverify.py +0 -0
  78. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_billing_stripe.py +0 -0
  79. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_cluster_handoff.py +0 -0
  80. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_cluster_router.py +0 -0
  81. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_cluster_unit.py +0 -0
  82. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_concurrency.py +0 -0
  83. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_dates.py +0 -0
  84. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_embedder_lifecycle.py +0 -0
  85. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_engine.py +0 -0
  86. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_evidence_packing.py +0 -0
  87. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_evidence_packing_api.py +0 -0
  88. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_examples.py +0 -0
  89. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_forward_packing.py +0 -0
  90. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_gate2.py +0 -0
  91. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_hosted_tenancy.py +0 -0
  92. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_index_rowids.py +0 -0
  93. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_key_custody.py +0 -0
  94. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_key_providers.py +0 -0
  95. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_key_shred_crash.py +0 -0
  96. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_loop_fixes.py +0 -0
  97. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_mcp.py +0 -0
  98. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_metrics.py +0 -0
  99. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_objectstore_contract.py +0 -0
  100. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_optimizations.py +0 -0
  101. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass10_egress_io.py +0 -0
  102. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass11_boundary_attacks.py +0 -0
  103. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass12_extractor_eviction.py +0 -0
  104. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass13_cache_bytes.py +0 -0
  105. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass14_soak_fuzz.py +0 -0
  106. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass15_entity_tokens.py +0 -0
  107. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass16_time_lane.py +0 -0
  108. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass17_gate_integrity.py +0 -0
  109. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass18_harness_scale.py +0 -0
  110. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass21_segment_replay.py +0 -0
  111. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass22_metrics_fidelity.py +0 -0
  112. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass23_audit_ledger.py +0 -0
  113. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass24_metrics_tenancy.py +0 -0
  114. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass25_data_integrity.py +0 -0
  115. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass26_authz_abuse.py +0 -0
  116. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass28_maintenance_cost.py +0 -0
  117. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass29_write_amplification.py +0 -0
  118. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass2_durability.py +0 -0
  119. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass30_cold_start.py +0 -0
  120. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass31_reaudit.py +0 -0
  121. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass32_s3_hardening.py +0 -0
  122. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass33_acked_delete_replay.py +0 -0
  123. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass34_upgrade_integrity.py +0 -0
  124. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass35_snapshot_migration_gc.py +0 -0
  125. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass36_legacy_ledger_deletes.py +0 -0
  126. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass37_ambiguous_ledger_deletes.py +0 -0
  127. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass38_rest_contract.py +0 -0
  128. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass39_acked_op_reaches_index.py +0 -0
  129. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass3_boundaries.py +0 -0
  130. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass40_damaged_frame_liveness.py +0 -0
  131. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass41_scrub_foreign_reader.py +0 -0
  132. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass42_scrub_eviction_and_open.py +0 -0
  133. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass43_export_incomplete_signal.py +0 -0
  134. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass44_stale_node_cache.py +0 -0
  135. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass45_settle_supersede_quarantine.py +0 -0
  136. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass46_concurrent_first_key.py +0 -0
  137. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass47_set_vector_is_derived.py +0 -0
  138. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass48_error_without_response.py +0 -0
  139. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass49_crashed_key_creation.py +0 -0
  140. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass4_multiproc_metrics.py +0 -0
  141. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass50_shared_cache_sweep.py +0 -0
  142. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass6_purge.py +0 -0
  143. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass7_doors.py +0 -0
  144. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass8_lanes.py +0 -0
  145. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass9_ns_lifecycle.py +0 -0
  146. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_pass_fixes.py +0 -0
  147. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_read_replicas_cluster.py +0 -0
  148. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_read_replicas_http.py +0 -0
  149. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_rerank_pipeline.py +0 -0
  150. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_retrieval_v2.py +0 -0
  151. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_robustness.py +0 -0
  152. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_roundtrip.py +0 -0
  153. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_s3_backend.py +0 -0
  154. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_schema.py +0 -0
  155. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_sdk.py +0 -0
  156. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_security_deep.py +0 -0
  157. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_session_pack_scale.py +0 -0
  158. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_storage.py +0 -0
  159. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_sweep_io_embed_backlog.py +0 -0
  160. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_tantivy_lexical.py +0 -0
  161. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_trust_boundary.py +0 -0
  162. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_vector_eligibility.py +0 -0
  163. {memd_engine-0.5.0 → memd_engine-0.5.2}/tests/test_vector_followups_regressions.py +0 -0
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.4
2
+ Name: memd-engine
3
+ Version: 0.5.2
4
+ Summary: memd - embedded-first agent memory engine: raw+fact lanes, bitemporal supersedence, provenance/trust tiers, hybrid retrieval
5
+ Author: memd contributors
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/siinghd/memd
8
+ Project-URL: Documentation, https://siinghd.github.io/memd/
9
+ Project-URL: Issues, https://github.com/siinghd/memd/issues
10
+ Keywords: agent,memory,llm,retrieval,mcp
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=1.26
23
+ Requires-Dist: fastapi>=0.110
24
+ Requires-Dist: uvicorn>=0.29
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: cryptography>=42
27
+ Provides-Extra: mcp
28
+ Requires-Dist: mcp>=2.0; extra == "mcp"
29
+ Provides-Extra: s3
30
+ Requires-Dist: boto3>=1.34; extra == "s3"
31
+ Provides-Extra: local-embeddings
32
+ Requires-Dist: fastembed>=0.4; extra == "local-embeddings"
33
+ Provides-Extra: fast
34
+ Requires-Dist: tantivy>=0.26; extra == "fast"
35
+ Provides-Extra: ann
36
+ Requires-Dist: usearch>=2.25; extra == "ann"
37
+ Provides-Extra: jev
38
+ Requires-Dist: typesafe-sdk>=0.7; extra == "jev"
39
+ Provides-Extra: billing
40
+ Requires-Dist: stripe>=15; extra == "billing"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8; extra == "dev"
43
+ Requires-Dist: pytest-timeout>=2; extra == "dev"
44
+ Requires-Dist: stripe>=15; extra == "dev"
45
+ Requires-Dist: moto[server]>=5; extra == "dev"
46
+ Provides-Extra: docs
47
+ Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
48
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
49
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
50
+ Requires-Dist: mkdocs-include-markdown-plugin>=7; extra == "docs"
51
+ Dynamic: license-file
52
+
53
+ # memd
54
+
55
+ [![PyPI](https://img.shields.io/pypi/v/memd-engine?label=PyPI)](https://pypi.org/project/memd-engine/)
56
+ [![npm](https://img.shields.io/npm/v/memd-engine?label=npm)](https://www.npmjs.com/package/memd-engine)
57
+ [![CI](https://github.com/siinghd/memd/actions/workflows/ci.yml/badge.svg)](https://github.com/siinghd/memd/actions/workflows/ci.yml)
58
+ [![Docs](https://img.shields.io/badge/docs-siinghd.github.io%2Fmemd-blue)](https://siinghd.github.io/memd/)
59
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/siinghd/memd/blob/master/LICENSE)
60
+
61
+ **The SQLite of agent memory.** memd is a memory engine for AI agents. It
62
+ runs in your process and keeps its data in one directory. It needs no
63
+ database, no server and no account. Apache-2.0.
64
+
65
+ ```python
66
+ mem = Memory("./my-data") # a directory, not a service
67
+ ```
68
+
69
+ Status: alpha. The badges show the current release. The [limits](#limits)
70
+ section tells what memd does not do.
71
+
72
+ ## Features
73
+
74
+ - **Embedded, with no keys necessary.** Without API keys, memd uses local
75
+ ONNX embeddings (BAAI/bge-small-en-v1.5, with the `local-embeddings`
76
+ extra) or hash embeddings (without the extra). It extracts facts with
77
+ patterns. `stats()` shows the active mode. An OpenAI-compatible key adds
78
+ API embeddings or LLM fact extraction.
79
+ - **No model call on the write path.** memd acknowledges a write when the
80
+ write is durable in the log of the namespace. memd makes the embeddings
81
+ in the background. BM25 search finds the record as soon as the call
82
+ returns.
83
+ - **Raw and fact lanes.** memd keeps the text of each turn, word for word
84
+ (the raw lane). Facts go in a second lane. You save a fact with `remember`, or
85
+ memd extracts facts when a session closes. memd keeps the raw lane, thus
86
+ you can run the extraction again.
87
+ - **Facts that change.** A new fact on an entity key replaces the old fact,
88
+ but memd does not delete the old fact. `get(id, history=True)` shows the
89
+ chain. `search(as_of=...)` shows what was true at a past time.
90
+ - **Provenance and trust.** Each record has a source tier: user > agent >
91
+ tool > web > import. memd puts lower-trust content in a data fence in
92
+ the packed context. An explicit save in a session gets the lowest tier
93
+ that the session saw. memd quarantines bursts of writes and repeated
94
+ near-identical content.
95
+ - **Hybrid retrieval, packed as evidence.** A rules-based planner sends the
96
+ query to the BM25, entity, time and vector lanes. Reciprocal rank fusion
97
+ merges the lanes, and an optional reranker can change the order. memd
98
+ packs the result into a token budget (12,000 by default) as dated
99
+ excerpts of past sessions. Each hit comes with the turns around it. A
100
+ fact shows under the turn that it came from.
101
+ - **Deletion that holds.** memd has hard delete with a physical-purge
102
+ deadline, forget-by-query with a preview, crypto-shred for each
103
+ namespace, and a hash-chained audit log.
104
+
105
+ ## Measured quality
106
+
107
+ Session retrieval on LongMemEval_S, through the public `Memory.search`.
108
+ The values are dev-fold means ± fold std, from
109
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md). Each row names the measured release.
110
+
111
+ | memd configuration | ndcg@5 | recall_all@5 | mean search time |
112
+ |---|---|---|---|
113
+ | v0.1.0 as shipped | 0.727 ± 0.027 | 0.697 | ~130 ms |
114
+ | v0.2.0, zero-key default (hash embedder, no reranker) | 0.866 ± 0.017 | 0.835 | ~16 ms |
115
+ | v0.2.0 + Jev reranker (`TYPESAFE_API_KEY` set) | 0.955 ± 0.026 | 0.928 | ~1 s (network) |
116
+
117
+ End-to-end QA on LongMemEval_S: 160 questions, stratified by type, one run.
118
+ The reader is DeepSeek V4.1 Flash. The judge is gpt-6-luna-pro.
119
+
120
+ | context given to the reader | accuracy |
121
+ |---|---|
122
+ | previous defaults: hash embedder, no reranker, 2K tokens, flat | 0.779 [0.718, 0.838] |
123
+ | bge-small + local cross-encoder reranker, 12K tokens, flat | 0.823 |
124
+ | bge-small + local cross-encoder reranker, 12K tokens, session layout, relative dates on | 0.875 [0.823, 0.920] |
125
+ | the whole history in the prompt (~105K tokens; exploratory) | 0.906 |
126
+
127
+ The session layout and the 12K budget are now the defaults. The 0.875 run
128
+ also used bge-small embeddings, a reranker and relative-date annotations.
129
+ The defaults use bge-small only with the `local-embeddings` extra, and no
130
+ reranker. The defaults ship the annotations off. Nobody measured the
131
+ defaults as shipped, end to end. The previous-defaults row and the
132
+ whole-history row use the answers of an earlier run.
133
+
134
+ These results come from one public dataset, one reader and one judge. For
135
+ the setup and the limits, see
136
+ [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md#packing-and-the-budget) and
137
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md).
138
+
139
+ ## Install
140
+
141
+ Install from PyPI. memd needs Python 3.11 or later. The package is
142
+ `memd-engine`. The import name and the command are `memd`.
143
+
144
+ ```bash
145
+ pip install "memd-engine[local-embeddings]"
146
+ ```
147
+
148
+ The `local-embeddings` extra runs BAAI/bge-small-en-v1.5 on the CPU,
149
+ through fastembed. The model downloads on first use. memd fuses it with
150
+ BM25. The extra gives much better recall. On LongMemEval_S session
151
+ retrieval, 97.5% of questions had all their evidence sessions in the top
152
+ 10 (recall_all@10 0.975). With BM25 alone, the result was 92.0%. memd ranks
153
+ by BM25 alone without the extra. These are lane-level measurements on 153
154
+ questions. The hash embedder's own vector lane scores 0.640, and memd does
155
+ not use it for ranking.
156
+
157
+ `pip install memd-engine` also works, offline and with no model. memd then
158
+ uses hash embeddings, and it logs one line about it.
159
+
160
+ | extra | adds |
161
+ |---|---|
162
+ | `local-embeddings` | local ONNX embeddings (BAAI/bge-small-en-v1.5 through fastembed), fused with BM25. Recommended. |
163
+ | `mcp` | `memd serve --mcp`, the MCP server |
164
+ | `s3` | `s3://` data roots and the `aws-kms` key provider (boto3) |
165
+ | `fast` | the tantivy accelerator for the BM25 lane |
166
+ | `ann` | the usearch HNSW sidecar for the vector lane |
167
+ | `jev` | the Jev reranker (active only with `TYPESAFE_API_KEY`) |
168
+ | `billing` | Stripe billing for hosted mode |
169
+
170
+ For TypeScript, install the REST client from npm. It needs a memd server.
171
+
172
+ ```bash
173
+ npm install memd-engine
174
+ ```
175
+
176
+ To get the latest code from this repository, use one of these commands:
177
+
178
+ ```bash
179
+ pip install "memd-engine[local-embeddings] @ git+https://github.com/siinghd/memd.git"
180
+
181
+ git clone https://github.com/siinghd/memd.git && cd memd
182
+ pip install -e ".[local-embeddings,mcp,s3]"
183
+ ```
184
+
185
+ ## Quickstart
186
+
187
+ ```python
188
+ from memd import Memory
189
+
190
+ mem = Memory("./my-data")
191
+ mem.add("We deploy with `make ship`, never CI", user_id="u1", session_id="s1")
192
+ mem.remember("The user prefers dark mode", user_id="u1", entity_keys=["user.theme"])
193
+
194
+ hits = mem.search("how do we deploy?", user_id="u1", budget_tokens=500)
195
+ print(hits.items[0].content) # We deploy with `make ship`, never CI
196
+ print(hits.packed_context) # dated session excerpts, ready to put in a prompt
197
+ mem.close()
198
+ ```
199
+
200
+ In an agent loop, use two calls. Before your LLM call, call
201
+ `messages = mem.pack(messages, user_id="u1")`. After it, call
202
+ `mem.observe(messages, response, user_id="u1")`. If you start the process
203
+ again, the memory is still in `./my-data`. The
204
+ [`examples/`](https://github.com/siinghd/memd/blob/master/examples/README.md) directory has more scripts that you can run.
205
+
206
+ ## One engine, four doors
207
+
208
+ - **Python:** `from memd import Memory`. `Memory(path)` runs the engine in
209
+ your process. `Memory(api_key=..., base_url=...)` gives the same API
210
+ over REST.
211
+ - **HTTP:** `memd serve --http` serves the REST API on port 8700. Each
212
+ namespace gets its own API keys (`memd key create --namespace acme`).
213
+ - **MCP:** `memd serve --mcp` gives four tools to an MCP client:
214
+ `memory_search`, `memory_save`, `memory_forget` and `memory_status`
215
+ ([setup](https://github.com/siinghd/memd/blob/master/examples/mcp/README.md)).
216
+ - **TypeScript:** `npm install memd-engine` gives a typed REST client for
217
+ Node 18 or later, Bun, Deno and edge runtimes
218
+ ([SDK guide](https://github.com/siinghd/memd/blob/master/sdk-ts/README.md)).
219
+
220
+ ## How memd grows
221
+
222
+ The same `Memory` API works at each step. Your code does not change.
223
+
224
+ - **Namespaces.** A namespace is the unit of isolation and of scale. Each
225
+ namespace has its own log, index, data key and audit log. Use one
226
+ namespace for each user, agent or tenant.
227
+ - **Several processes on one data root.** One process at a time writes a
228
+ namespace. The other processes send their writes and strong reads to it
229
+ (write forwarding, on by default). If that process stops, another
230
+ process becomes the writer. Thus `uvicorn --workers N`, one
231
+ `memd serve --mcp` for each MCP client, and a script next to a server can
232
+ share one directory. `forwarding="off"` turns this off.
233
+ - **Object storage.** The same `Memory` runs on an `s3://` root: AWS S3,
234
+ Cloudflare R2 or another S3-compatible store. CI runs the S3 tests
235
+ against RustFS. A durable write is one PUT. A warm search reads no object.
236
+ - **Read replicas.** Reads are strong by default. A search or a get can
237
+ ask for eventual consistency. Then a read replica serves it, within a
238
+ staleness limit.
239
+ - **Multi-node.** Several `memd serve --http` processes on one `s3://` root
240
+ share the namespaces. A lease in the bucket gives each namespace one
241
+ writer. A node sends each request to the node that holds the lease. AWS
242
+ KMS or Vault transit wraps the data keys, thus each node can open each
243
+ namespace.
244
+ - **Search throughput.** The default search (a 12K session pack) is CPU
245
+ work in Python. In one process, more threads do not give more searches
246
+ per second, because of the GIL. To serve more searches, run more
247
+ processes: `uvicorn --workers N`, more processes on one data root, read
248
+ replicas or more nodes. Retrieval alone (a 2K flat pack) scales with
249
+ threads, because SQLite releases the GIL.
250
+
251
+ [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md#several-processes-on-one-data-root)
252
+ gives the details and the measurements.
253
+
254
+ ## Security and deletion
255
+
256
+ - memd encrypts data at rest by default, with one data key for each
257
+ namespace. A `local` key file, AWS KMS or Vault transit holds the root key.
258
+ - Key custody fails closed. If a data key is missing or wrong, memd raises
259
+ `KeyCustodyError` and changes nothing. memd never reads such a namespace
260
+ as empty.
261
+ - `delete(id, hard=True)` purges the record from all files within a
262
+ deadline (72 hours by default).
263
+ - `find_ids(query)` shows what `forget(query)` will delete. With the
264
+ fingerprint of that preview, `forget` deletes nothing if the matches
265
+ changed.
266
+ - `destroy_namespace()` destroys the data key of the namespace
267
+ (crypto-shred).
268
+ - A hash-chained audit log records each delete and each forget.
269
+ - The fences around lower-trust content mark it as data. They cannot force
270
+ a model to obey that mark.
271
+
272
+ [SECURITY.md](https://github.com/siinghd/memd/blob/master/SECURITY.md) gives the threat model, what it does not cover,
273
+ and how to report a vulnerability.
274
+
275
+ ## Limits
276
+
277
+ - **Many processes can use one namespace. One of them writes.** Each
278
+ process can open the same namespace and write to it. One process (the
279
+ holder) writes the log. The other processes send their writes and their
280
+ strong reads to the holder automatically. If the holder stops, another
281
+ process becomes the holder. Thus, the write capacity of one namespace is
282
+ the capacity of one process. To get more write capacity, use more
283
+ namespaces (for example, one namespace for each user or agent). To get
284
+ more read capacity, use read replicas (opt-in eventual reads, with a
285
+ staleness limit). memd does not let several processes write one
286
+ namespace's log at the same time.
287
+ - **Fact extraction uses patterns by default.** An optional LLM extractor
288
+ uses your own API key. On 30 LongMemEval_S questions it gave no
289
+ measurable accuracy gain: 0.667 against 0.700 for the pattern extractor
290
+ (difference -0.033, 95% CI [-0.167, +0.067]). It also makes a session
291
+ close slower (1.5 s against 0.08 s at the median). A larger evaluation
292
+ is necessary before we recommend it.
293
+ - **The quality evidence comes from one public dataset (LongMemEval_S).**
294
+ Nobody measured the current defaults end to end.
295
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md) tells what each run shows and what it
296
+ does not show.
297
+ - **Alpha software.** What changed in each release is in the
298
+ [changelog](https://github.com/siinghd/memd/blob/master/CHANGELOG.md).
299
+ - **Out of scope:** an agent framework or runtime, a platform for RAG over
300
+ documents, and a graph database.
301
+
302
+ ## Documentation
303
+
304
+ - Docs site: <https://siinghd.github.io/memd/> (quickstart, concepts, API
305
+ reference, operations). The source is in [`docs/`](https://github.com/siinghd/memd/tree/master/docs). To build it
306
+ locally, run `pip install -e ".[docs]" && mkdocs serve`.
307
+ - [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md): the full engine guide (how to run
308
+ it, several processes, S3, key custody, multi-node, read replicas, hosted
309
+ mode, retrieval options, operations)
310
+ - [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md): how we measured quality and latency
311
+ - [SECURITY.md](https://github.com/siinghd/memd/blob/master/SECURITY.md): the threat model, what it does not cover,
312
+ and how to report a vulnerability
313
+ - [CHANGELOG.md](https://github.com/siinghd/memd/blob/master/CHANGELOG.md): each release
314
+ - [CONTRIBUTING.md](https://github.com/siinghd/memd/blob/master/CONTRIBUTING.md): setup, tests, and what a change needs
315
+ - [RELEASING.md](https://github.com/siinghd/memd/blob/master/RELEASING.md): how to publish a release
316
+
317
+ ## License
318
+
319
+ [Apache-2.0](https://github.com/siinghd/memd/blob/master/LICENSE).
@@ -0,0 +1,267 @@
1
+ # memd
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/memd-engine?label=PyPI)](https://pypi.org/project/memd-engine/)
4
+ [![npm](https://img.shields.io/npm/v/memd-engine?label=npm)](https://www.npmjs.com/package/memd-engine)
5
+ [![CI](https://github.com/siinghd/memd/actions/workflows/ci.yml/badge.svg)](https://github.com/siinghd/memd/actions/workflows/ci.yml)
6
+ [![Docs](https://img.shields.io/badge/docs-siinghd.github.io%2Fmemd-blue)](https://siinghd.github.io/memd/)
7
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/siinghd/memd/blob/master/LICENSE)
8
+
9
+ **The SQLite of agent memory.** memd is a memory engine for AI agents. It
10
+ runs in your process and keeps its data in one directory. It needs no
11
+ database, no server and no account. Apache-2.0.
12
+
13
+ ```python
14
+ mem = Memory("./my-data") # a directory, not a service
15
+ ```
16
+
17
+ Status: alpha. The badges show the current release. The [limits](#limits)
18
+ section tells what memd does not do.
19
+
20
+ ## Features
21
+
22
+ - **Embedded, with no keys necessary.** Without API keys, memd uses local
23
+ ONNX embeddings (BAAI/bge-small-en-v1.5, with the `local-embeddings`
24
+ extra) or hash embeddings (without the extra). It extracts facts with
25
+ patterns. `stats()` shows the active mode. An OpenAI-compatible key adds
26
+ API embeddings or LLM fact extraction.
27
+ - **No model call on the write path.** memd acknowledges a write when the
28
+ write is durable in the log of the namespace. memd makes the embeddings
29
+ in the background. BM25 search finds the record as soon as the call
30
+ returns.
31
+ - **Raw and fact lanes.** memd keeps the text of each turn, word for word
32
+ (the raw lane). Facts go in a second lane. You save a fact with `remember`, or
33
+ memd extracts facts when a session closes. memd keeps the raw lane, thus
34
+ you can run the extraction again.
35
+ - **Facts that change.** A new fact on an entity key replaces the old fact,
36
+ but memd does not delete the old fact. `get(id, history=True)` shows the
37
+ chain. `search(as_of=...)` shows what was true at a past time.
38
+ - **Provenance and trust.** Each record has a source tier: user > agent >
39
+ tool > web > import. memd puts lower-trust content in a data fence in
40
+ the packed context. An explicit save in a session gets the lowest tier
41
+ that the session saw. memd quarantines bursts of writes and repeated
42
+ near-identical content.
43
+ - **Hybrid retrieval, packed as evidence.** A rules-based planner sends the
44
+ query to the BM25, entity, time and vector lanes. Reciprocal rank fusion
45
+ merges the lanes, and an optional reranker can change the order. memd
46
+ packs the result into a token budget (12,000 by default) as dated
47
+ excerpts of past sessions. Each hit comes with the turns around it. A
48
+ fact shows under the turn that it came from.
49
+ - **Deletion that holds.** memd has hard delete with a physical-purge
50
+ deadline, forget-by-query with a preview, crypto-shred for each
51
+ namespace, and a hash-chained audit log.
52
+
53
+ ## Measured quality
54
+
55
+ Session retrieval on LongMemEval_S, through the public `Memory.search`.
56
+ The values are dev-fold means ± fold std, from
57
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md). Each row names the measured release.
58
+
59
+ | memd configuration | ndcg@5 | recall_all@5 | mean search time |
60
+ |---|---|---|---|
61
+ | v0.1.0 as shipped | 0.727 ± 0.027 | 0.697 | ~130 ms |
62
+ | v0.2.0, zero-key default (hash embedder, no reranker) | 0.866 ± 0.017 | 0.835 | ~16 ms |
63
+ | v0.2.0 + Jev reranker (`TYPESAFE_API_KEY` set) | 0.955 ± 0.026 | 0.928 | ~1 s (network) |
64
+
65
+ End-to-end QA on LongMemEval_S: 160 questions, stratified by type, one run.
66
+ The reader is DeepSeek V4.1 Flash. The judge is gpt-6-luna-pro.
67
+
68
+ | context given to the reader | accuracy |
69
+ |---|---|
70
+ | previous defaults: hash embedder, no reranker, 2K tokens, flat | 0.779 [0.718, 0.838] |
71
+ | bge-small + local cross-encoder reranker, 12K tokens, flat | 0.823 |
72
+ | bge-small + local cross-encoder reranker, 12K tokens, session layout, relative dates on | 0.875 [0.823, 0.920] |
73
+ | the whole history in the prompt (~105K tokens; exploratory) | 0.906 |
74
+
75
+ The session layout and the 12K budget are now the defaults. The 0.875 run
76
+ also used bge-small embeddings, a reranker and relative-date annotations.
77
+ The defaults use bge-small only with the `local-embeddings` extra, and no
78
+ reranker. The defaults ship the annotations off. Nobody measured the
79
+ defaults as shipped, end to end. The previous-defaults row and the
80
+ whole-history row use the answers of an earlier run.
81
+
82
+ These results come from one public dataset, one reader and one judge. For
83
+ the setup and the limits, see
84
+ [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md#packing-and-the-budget) and
85
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md).
86
+
87
+ ## Install
88
+
89
+ Install from PyPI. memd needs Python 3.11 or later. The package is
90
+ `memd-engine`. The import name and the command are `memd`.
91
+
92
+ ```bash
93
+ pip install "memd-engine[local-embeddings]"
94
+ ```
95
+
96
+ The `local-embeddings` extra runs BAAI/bge-small-en-v1.5 on the CPU,
97
+ through fastembed. The model downloads on first use. memd fuses it with
98
+ BM25. The extra gives much better recall. On LongMemEval_S session
99
+ retrieval, 97.5% of questions had all their evidence sessions in the top
100
+ 10 (recall_all@10 0.975). With BM25 alone, the result was 92.0%. memd ranks
101
+ by BM25 alone without the extra. These are lane-level measurements on 153
102
+ questions. The hash embedder's own vector lane scores 0.640, and memd does
103
+ not use it for ranking.
104
+
105
+ `pip install memd-engine` also works, offline and with no model. memd then
106
+ uses hash embeddings, and it logs one line about it.
107
+
108
+ | extra | adds |
109
+ |---|---|
110
+ | `local-embeddings` | local ONNX embeddings (BAAI/bge-small-en-v1.5 through fastembed), fused with BM25. Recommended. |
111
+ | `mcp` | `memd serve --mcp`, the MCP server |
112
+ | `s3` | `s3://` data roots and the `aws-kms` key provider (boto3) |
113
+ | `fast` | the tantivy accelerator for the BM25 lane |
114
+ | `ann` | the usearch HNSW sidecar for the vector lane |
115
+ | `jev` | the Jev reranker (active only with `TYPESAFE_API_KEY`) |
116
+ | `billing` | Stripe billing for hosted mode |
117
+
118
+ For TypeScript, install the REST client from npm. It needs a memd server.
119
+
120
+ ```bash
121
+ npm install memd-engine
122
+ ```
123
+
124
+ To get the latest code from this repository, use one of these commands:
125
+
126
+ ```bash
127
+ pip install "memd-engine[local-embeddings] @ git+https://github.com/siinghd/memd.git"
128
+
129
+ git clone https://github.com/siinghd/memd.git && cd memd
130
+ pip install -e ".[local-embeddings,mcp,s3]"
131
+ ```
132
+
133
+ ## Quickstart
134
+
135
+ ```python
136
+ from memd import Memory
137
+
138
+ mem = Memory("./my-data")
139
+ mem.add("We deploy with `make ship`, never CI", user_id="u1", session_id="s1")
140
+ mem.remember("The user prefers dark mode", user_id="u1", entity_keys=["user.theme"])
141
+
142
+ hits = mem.search("how do we deploy?", user_id="u1", budget_tokens=500)
143
+ print(hits.items[0].content) # We deploy with `make ship`, never CI
144
+ print(hits.packed_context) # dated session excerpts, ready to put in a prompt
145
+ mem.close()
146
+ ```
147
+
148
+ In an agent loop, use two calls. Before your LLM call, call
149
+ `messages = mem.pack(messages, user_id="u1")`. After it, call
150
+ `mem.observe(messages, response, user_id="u1")`. If you start the process
151
+ again, the memory is still in `./my-data`. The
152
+ [`examples/`](https://github.com/siinghd/memd/blob/master/examples/README.md) directory has more scripts that you can run.
153
+
154
+ ## One engine, four doors
155
+
156
+ - **Python:** `from memd import Memory`. `Memory(path)` runs the engine in
157
+ your process. `Memory(api_key=..., base_url=...)` gives the same API
158
+ over REST.
159
+ - **HTTP:** `memd serve --http` serves the REST API on port 8700. Each
160
+ namespace gets its own API keys (`memd key create --namespace acme`).
161
+ - **MCP:** `memd serve --mcp` gives four tools to an MCP client:
162
+ `memory_search`, `memory_save`, `memory_forget` and `memory_status`
163
+ ([setup](https://github.com/siinghd/memd/blob/master/examples/mcp/README.md)).
164
+ - **TypeScript:** `npm install memd-engine` gives a typed REST client for
165
+ Node 18 or later, Bun, Deno and edge runtimes
166
+ ([SDK guide](https://github.com/siinghd/memd/blob/master/sdk-ts/README.md)).
167
+
168
+ ## How memd grows
169
+
170
+ The same `Memory` API works at each step. Your code does not change.
171
+
172
+ - **Namespaces.** A namespace is the unit of isolation and of scale. Each
173
+ namespace has its own log, index, data key and audit log. Use one
174
+ namespace for each user, agent or tenant.
175
+ - **Several processes on one data root.** One process at a time writes a
176
+ namespace. The other processes send their writes and strong reads to it
177
+ (write forwarding, on by default). If that process stops, another
178
+ process becomes the writer. Thus `uvicorn --workers N`, one
179
+ `memd serve --mcp` for each MCP client, and a script next to a server can
180
+ share one directory. `forwarding="off"` turns this off.
181
+ - **Object storage.** The same `Memory` runs on an `s3://` root: AWS S3,
182
+ Cloudflare R2 or another S3-compatible store. CI runs the S3 tests
183
+ against RustFS. A durable write is one PUT. A warm search reads no object.
184
+ - **Read replicas.** Reads are strong by default. A search or a get can
185
+ ask for eventual consistency. Then a read replica serves it, within a
186
+ staleness limit.
187
+ - **Multi-node.** Several `memd serve --http` processes on one `s3://` root
188
+ share the namespaces. A lease in the bucket gives each namespace one
189
+ writer. A node sends each request to the node that holds the lease. AWS
190
+ KMS or Vault transit wraps the data keys, thus each node can open each
191
+ namespace.
192
+ - **Search throughput.** The default search (a 12K session pack) is CPU
193
+ work in Python. In one process, more threads do not give more searches
194
+ per second, because of the GIL. To serve more searches, run more
195
+ processes: `uvicorn --workers N`, more processes on one data root, read
196
+ replicas or more nodes. Retrieval alone (a 2K flat pack) scales with
197
+ threads, because SQLite releases the GIL.
198
+
199
+ [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md#several-processes-on-one-data-root)
200
+ gives the details and the measurements.
201
+
202
+ ## Security and deletion
203
+
204
+ - memd encrypts data at rest by default, with one data key for each
205
+ namespace. A `local` key file, AWS KMS or Vault transit holds the root key.
206
+ - Key custody fails closed. If a data key is missing or wrong, memd raises
207
+ `KeyCustodyError` and changes nothing. memd never reads such a namespace
208
+ as empty.
209
+ - `delete(id, hard=True)` purges the record from all files within a
210
+ deadline (72 hours by default).
211
+ - `find_ids(query)` shows what `forget(query)` will delete. With the
212
+ fingerprint of that preview, `forget` deletes nothing if the matches
213
+ changed.
214
+ - `destroy_namespace()` destroys the data key of the namespace
215
+ (crypto-shred).
216
+ - A hash-chained audit log records each delete and each forget.
217
+ - The fences around lower-trust content mark it as data. They cannot force
218
+ a model to obey that mark.
219
+
220
+ [SECURITY.md](https://github.com/siinghd/memd/blob/master/SECURITY.md) gives the threat model, what it does not cover,
221
+ and how to report a vulnerability.
222
+
223
+ ## Limits
224
+
225
+ - **Many processes can use one namespace. One of them writes.** Each
226
+ process can open the same namespace and write to it. One process (the
227
+ holder) writes the log. The other processes send their writes and their
228
+ strong reads to the holder automatically. If the holder stops, another
229
+ process becomes the holder. Thus, the write capacity of one namespace is
230
+ the capacity of one process. To get more write capacity, use more
231
+ namespaces (for example, one namespace for each user or agent). To get
232
+ more read capacity, use read replicas (opt-in eventual reads, with a
233
+ staleness limit). memd does not let several processes write one
234
+ namespace's log at the same time.
235
+ - **Fact extraction uses patterns by default.** An optional LLM extractor
236
+ uses your own API key. On 30 LongMemEval_S questions it gave no
237
+ measurable accuracy gain: 0.667 against 0.700 for the pattern extractor
238
+ (difference -0.033, 95% CI [-0.167, +0.067]). It also makes a session
239
+ close slower (1.5 s against 0.08 s at the median). A larger evaluation
240
+ is necessary before we recommend it.
241
+ - **The quality evidence comes from one public dataset (LongMemEval_S).**
242
+ Nobody measured the current defaults end to end.
243
+ [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md) tells what each run shows and what it
244
+ does not show.
245
+ - **Alpha software.** What changed in each release is in the
246
+ [changelog](https://github.com/siinghd/memd/blob/master/CHANGELOG.md).
247
+ - **Out of scope:** an agent framework or runtime, a platform for RAG over
248
+ documents, and a graph database.
249
+
250
+ ## Documentation
251
+
252
+ - Docs site: <https://siinghd.github.io/memd/> (quickstart, concepts, API
253
+ reference, operations). The source is in [`docs/`](https://github.com/siinghd/memd/tree/master/docs). To build it
254
+ locally, run `pip install -e ".[docs]" && mkdocs serve`.
255
+ - [README-engine.md](https://github.com/siinghd/memd/blob/master/README-engine.md): the full engine guide (how to run
256
+ it, several processes, S3, key custody, multi-node, read replicas, hosted
257
+ mode, retrieval options, operations)
258
+ - [BENCHMARKS.md](https://github.com/siinghd/memd/blob/master/BENCHMARKS.md): how we measured quality and latency
259
+ - [SECURITY.md](https://github.com/siinghd/memd/blob/master/SECURITY.md): the threat model, what it does not cover,
260
+ and how to report a vulnerability
261
+ - [CHANGELOG.md](https://github.com/siinghd/memd/blob/master/CHANGELOG.md): each release
262
+ - [CONTRIBUTING.md](https://github.com/siinghd/memd/blob/master/CONTRIBUTING.md): setup, tests, and what a change needs
263
+ - [RELEASING.md](https://github.com/siinghd/memd/blob/master/RELEASING.md): how to publish a release
264
+
265
+ ## License
266
+
267
+ [Apache-2.0](https://github.com/siinghd/memd/blob/master/LICENSE).
@@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memd-engine"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "memd - embedded-first agent memory engine: raw+fact lanes, bitemporal supersedence, provenance/trust tiers, hybrid retrieval"
9
- readme = "README-engine.md"
9
+ readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  license = { text = "Apache-2.0" }
12
12
  authors = [{ name = "memd contributors" }]
@@ -16,6 +16,9 @@ classifiers = [
16
16
  "Intended Audience :: Developers",
17
17
  "License :: OSI Approved :: Apache Software License",
18
18
  "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
19
22
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
23
  ]
21
24
  dependencies = [
@@ -2,6 +2,6 @@
2
2
  from memd.core.schema import Kind, MemoryRecord, Scope, Source
3
3
  from memd.engine.memory import Memory
4
4
 
5
- __version__ = "0.5.0"
5
+ __version__ = "0.5.2"
6
6
 
7
7
  __all__ = ["Memory", "MemoryRecord", "Scope", "Source", "Kind", "__version__"]
@@ -2024,7 +2024,10 @@ class Memory:
2024
2024
  `extraction_errors` counts the extraction calls that failed and
2025
2025
  `raw_failed` their turns: the LLM extractor's turns of a failed call
2026
2026
  go through the pattern extractor instead; an extractor that raised
2027
- outright counts 1 call, all its turns, and adds no facts."""
2027
+ outright counts 1 call, all its turns, and adds no facts.
2028
+ `raw_failed_by_reason` gives those turns by failure reason (the
2029
+ reason labels of memd_extraction_chunks_failed_total; "error" for an
2030
+ extractor that raised)."""
2028
2031
  impl = self._hosted()
2029
2032
  if impl is not None:
2030
2033
  return impl.close_session(session_id, user_id=user_id, namespace=namespace)
@@ -2054,6 +2057,7 @@ class Memory:
2054
2057
  # through the pattern extractor instead - reported, not silent
2055
2058
  extraction_errors = list(getattr(extracted, "errors", None) or [])
2056
2059
  raw_failed = int(getattr(extracted, "failed_records", 0) or 0)
2060
+ raw_failed_by_reason = dict(getattr(extracted, "failed_by_reason", None) or {})
2057
2061
  if extraction_errors:
2058
2062
  self._audit_for(ns.namespace).append(
2059
2063
  actor="system", action="extraction_degraded", target=session_id,
@@ -2068,6 +2072,7 @@ class Memory:
2068
2072
  extracted = []
2069
2073
  extraction_errors = ["error"]
2070
2074
  raw_failed = len(to_extract)
2075
+ raw_failed_by_reason = {"error": raw_failed} if raw_failed else {}
2071
2076
  facts_capped = 0
2072
2077
  if callable(max_facts):
2073
2078
  max_facts = max_facts(len(extracted))
@@ -2100,6 +2105,7 @@ class Memory:
2100
2105
  "raw_considered": len(to_extract),
2101
2106
  "raw_skipped": len(seg_records) - len(to_extract),
2102
2107
  "raw_failed": raw_failed,
2108
+ "raw_failed_by_reason": raw_failed_by_reason,
2103
2109
  "facts_extracted": len(extracted),
2104
2110
  "extraction_errors": len(extraction_errors),
2105
2111
  "facts_capped": facts_capped,