pycontextdb 0.4.5__tar.gz → 0.4.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.
Files changed (151) hide show
  1. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/CHANGELOG.md +56 -0
  2. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/PKG-INFO +44 -51
  3. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/README.md +43 -50
  4. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/__init__.py +1 -1
  5. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/client.py +106 -7
  6. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/config.py +13 -3
  7. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/policy.py +6 -4
  8. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/slots.py +68 -3
  9. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/retrieval.py +50 -1
  10. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/trust.py +85 -31
  11. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/entity.py +118 -12
  12. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/memory/factual.py +20 -5
  13. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/privacy/pii_detector.py +40 -5
  14. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/base.py +19 -0
  15. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/postgres_store.py +21 -1
  16. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/sqlite_store.py +23 -1
  17. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/index.md +8 -4
  18. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/prompts.md +1 -1
  19. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/quickstart.md +3 -1
  20. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/pyproject.toml +1 -1
  21. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/evals/test_trust_model.py +398 -1
  22. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_pii_detector.py +33 -0
  23. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_retrieval.py +12 -0
  24. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.github/CODEOWNERS +0 -0
  25. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.github/copilot-instructions.md +0 -0
  26. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.github/pull_request_template.md +0 -0
  27. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.github/workflows/ci.yml +0 -0
  28. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.github/workflows/publish.yml +0 -0
  29. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.gitignore +0 -0
  30. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/.pre-commit-config.yaml +0 -0
  31. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/COMMERCIAL.md +0 -0
  32. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/CONTRIBUTING.md +0 -0
  33. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/LICENSE +0 -0
  34. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/NOTICE +0 -0
  35. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/OPEN_CORE.md +0 -0
  36. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/ai-tools/.cursorrules +0 -0
  37. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/ai-tools/CLAUDE.md +0 -0
  38. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/ai-tools/README.md +0 -0
  39. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/ai-tools/llms-full.txt +0 -0
  40. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/ai-tools/llms.txt +0 -0
  41. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/README.md +0 -0
  42. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/architecture.png +0 -0
  43. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/benchmarks.png +0 -0
  44. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/hero.png +0 -0
  45. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/performance.png +0 -0
  46. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/problem.png +0 -0
  47. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/assets/quickstart.png +0 -0
  48. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/benchmarks/__init__.py +0 -0
  49. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/benchmarks/run_benchmarks.py +0 -0
  50. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/benchmarks/trust_bakeoff.py +0 -0
  51. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/agents/__init__.py +0 -0
  52. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/agents/memory_bus.py +0 -0
  53. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/agents/rl_manager.py +0 -0
  54. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/cli.py +0 -0
  55. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/connectors/__init__.py +0 -0
  56. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/connectors/base.py +0 -0
  57. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/connectors/postgres.py +0 -0
  58. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/__init__.py +0 -0
  59. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/clock.py +0 -0
  60. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/exceptions.py +0 -0
  61. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/core/models.py +0 -0
  62. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/__init__.py +0 -0
  63. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/evolution.py +0 -0
  64. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/formation.py +0 -0
  65. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/dynamics/salience.py +0 -0
  66. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/__init__.py +0 -0
  67. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/base.py +0 -0
  68. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/causal.py +0 -0
  69. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/semantic.py +0 -0
  70. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/graphs/temporal.py +0 -0
  71. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/__init__.py +0 -0
  72. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/act.py +0 -0
  73. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/autogen.py +0 -0
  74. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/crewai.py +0 -0
  75. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/langchain.py +0 -0
  76. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/livekit.py +0 -0
  77. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/openai_tools.py +0 -0
  78. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/pipecat.py +0 -0
  79. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/integrations/prompting.py +0 -0
  80. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/mcp.py +0 -0
  81. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/memory/__init__.py +0 -0
  82. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/memory/experiential.py +0 -0
  83. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/memory/working.py +0 -0
  84. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/pool.py +0 -0
  85. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/privacy/__init__.py +0 -0
  86. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/privacy/audit.py +0 -0
  87. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/privacy/injection.py +0 -0
  88. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/privacy/retention.py +0 -0
  89. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/py.typed +0 -0
  90. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/serve.py +0 -0
  91. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/__init__.py +0 -0
  92. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/factory.py +0 -0
  93. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/pg_sql.py +0 -0
  94. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/store/vector_index.py +0 -0
  95. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/utils/__init__.py +0 -0
  96. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/utils/embeddings.py +0 -0
  97. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/utils/llm.py +0 -0
  98. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/contextdb/utils/migrations.py +0 -0
  99. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/api.md +0 -0
  100. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/architecture.md +0 -0
  101. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/connectors.md +0 -0
  102. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/memory_evolution.md +0 -0
  103. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/migrations.md +0 -0
  104. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/multi_tenant.md +0 -0
  105. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/postgres.md +0 -0
  106. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/serve.md +0 -0
  107. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/docs/trust_policy.md +0 -0
  108. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/examples/customer_support.py +0 -0
  109. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/examples/multi_agent_team.py +0 -0
  110. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/examples/phone_agent.py +0 -0
  111. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/examples/research_assistant.py +0 -0
  112. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/legal/CLA-entity.md +0 -0
  113. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/legal/CLA-individual.md +0 -0
  114. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/legal/signed/README.md +0 -0
  115. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/mkdocs.yml +0 -0
  116. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/scripts/check_open_core_boundary.py +0 -0
  117. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/scripts/check_release.py +0 -0
  118. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/__init__.py +0 -0
  119. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/conftest.py +0 -0
  120. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/evals/__init__.py +0 -0
  121. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/integration/__init__.py +0 -0
  122. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/integration/test_client.py +0 -0
  123. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/integration/test_langchain_integration.py +0 -0
  124. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/pg_util.py +0 -0
  125. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/test_version.py +0 -0
  126. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/__init__.py +0 -0
  127. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_audit.py +0 -0
  128. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_config.py +0 -0
  129. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_connectors.py +0 -0
  130. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_decision_scope.py +0 -0
  131. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_embeddings.py +0 -0
  132. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_evolution.py +0 -0
  133. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_formation.py +0 -0
  134. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_graphs.py +0 -0
  135. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_host_api.py +0 -0
  136. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_init.py +0 -0
  137. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_memory_bus.py +0 -0
  138. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_memory_evolution.py +0 -0
  139. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_models.py +0 -0
  140. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_open_core_boundary.py +0 -0
  141. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_pg_sql.py +0 -0
  142. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_postgres_concurrency.py +0 -0
  143. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_postgres_connector.py +0 -0
  144. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_postgres_memory_evolution.py +0 -0
  145. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_retention.py +0 -0
  146. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_scoped_id_access.py +0 -0
  147. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_serve.py +0 -0
  148. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_store.py +0 -0
  149. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_trust_policy.py +0 -0
  150. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_vector_index.py +0 -0
  151. {pycontextdb-0.4.5 → pycontextdb-0.4.7}/tests/unit/test_vector_scope.py +0 -0
@@ -2,6 +2,62 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.7 — 2026-10-12
6
+
7
+ - `forget_user` now removes what the user added to shared entities. Entity
8
+ attributes record the memory that supplied them, are rebuilt from the
9
+ remaining sources when a memory is removed, and an entity no memory still
10
+ mentions is deleted. `verify_forgotten` fails if an edge or attribute source
11
+ still points at a deleted memory. Attributes merged before 0.4.7 have no
12
+ recorded source and stay until their entity is unreferenced.
13
+ - In encrypt and redact modes, a slot value that contains a detected PII span
14
+ is stored as a fingerprint, keyed with the PII encryption key when one is
15
+ configured. Slot values were taken from the original
16
+ utterance, so encrypt mode still wrote plaintext emails and phone numbers to
17
+ disk. Comparisons are unchanged. A value written before 0.4.7 compares as
18
+ different from the same value written now, so it is superseded once.
19
+ - `consolidate_pending` no longer drops a new value that reads the same once
20
+ redacted. Two emails saved with `add_fast` both stored as `[EMAIL]`, so the
21
+ second consolidated into a no-op and was lost.
22
+ - Recall adds a lexical ranking for identifiers (tokens of three or more
23
+ characters that contain a digit, such as order numbers and SKUs). An exact
24
+ identifier match passes the relevance floor. Stores gain `search_text`; no
25
+ schema change.
26
+ - `pii_detect_names_and_addresses=True` also redacts street addresses and cued
27
+ names ("my name is", "call me", "this is" followed by two capitalized
28
+ words). Off by default, because a redacted address is hidden from the agent
29
+ too.
30
+ - Appointment kinds get their own slots: "my dentist appointment" and "my
31
+ haircut appointment" no longer replace or merge with each other. Kind
32
+ entities such as `dentist_appointment` use the booking class, and a generic
33
+ `appointment` slot is still accepted where the text names a kind.
34
+ - The relevance floor no longer recommends a fixed 0.15 to 0.25 band. Cosine
35
+ ranges are embedder-specific, and a floor cannot tell whether a memory
36
+ answers a request.
37
+ - README and docs replace v0.1.0 performance numbers and fixed test counts
38
+ with current measurements, and state that no competitor arms ship in the
39
+ bake-off.
40
+
41
+ ## 0.4.6 — 2026-10-08
42
+
43
+ - A lower-trust write no longer replaces a first-party fact in its slot.
44
+ Writes made under one user id share a speaker, so a third-party document or
45
+ an agent inference stored for that user superseded what the user had said,
46
+ and the user would then be asked to confirm the document's value. A
47
+ user-stated or confirmed fact now stays current and the slot is contested
48
+ until `confirm()`. Corrections of equal or higher trust still supersede.
49
+ Advisory: GHSA-vpf9-5xgr-3x8m.
50
+ - `factual.recall_for_action` and `factual.pending_confirmations` accept the
51
+ `entity` and `attribute` an action needs. With them, only current facts in
52
+ that slot count, read exactly rather than by similarity, and the read is
53
+ audited like a recall. Without them nothing changes: any trusted memory the
54
+ query retrieves counts, whether or not it answers the request.
55
+ - The slot vocabulary (version 2) adds `user/billing_email`, `user/work_phone`,
56
+ and `user/work_address`. A user's billing email, work phone, or work address
57
+ no longer replaces their personal one. An email filed as a billing email
58
+ stays there instead of being moved to `user/email` and stored as
59
+ `agent_inferred`.
60
+
5
61
  ## 0.4.5 — 2026-10-05
6
62
 
7
63
  - `forget_user` no longer deletes other users' memories. It added every graph
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pycontextdb
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: The unified context layer for AI agents — memory you can act on without treating wishes as facts
5
5
  Project-URL: Homepage, https://github.com/atomsai/contextdb
6
6
  Project-URL: Documentation, https://contextdb.dev
@@ -74,10 +74,10 @@ Description-Content-Type: text/markdown
74
74
  <p align="center">
75
75
  <a href="https://pypi.org/project/pycontextdb/"><img src="https://img.shields.io/pypi/v/pycontextdb.svg" alt="PyPI version"></a>
76
76
  <a href="https://github.com/atomsai/contextdb/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0"></a>
77
- <a href="https://github.com/atomsai/contextdb/blob/main/tests/"><img src="https://img.shields.io/badge/tests-195%20passing-brightgreen.svg" alt="Tests"></a>
77
+ <a href="https://github.com/atomsai/contextdb/actions/workflows/ci.yml"><img src="https://github.com/atomsai/contextdb/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
78
78
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
79
79
  <a href="https://github.com/atomsai/contextdb/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/mypy-strict-blue.svg" alt="Type Checked"></a>
80
- <a href="https://github.com/atomsai/contextdb/blob/main/benchmarks/run_benchmarks.py"><img src="https://img.shields.io/badge/search_p95-%3C5ms_%40_5K-brightgreen.svg" alt="Search p95"></a>
80
+ <a href="https://github.com/atomsai/contextdb/blob/main/benchmarks/run_benchmarks.py"><img src="https://img.shields.io/badge/search_p95-18ms_%40_5K_(mock_embedder)-blue.svg" alt="Search p95 18 ms at 5K memories with the mock embedder"></a>
81
81
  <a href="https://github.com/atomsai/contextdb/stargazers"><img src="https://img.shields.io/github/stars/atomsai/contextdb?style=social" alt="GitHub stars"></a>
82
82
  </p>
83
83
 
@@ -91,20 +91,18 @@ Description-Content-Type: text/markdown
91
91
 
92
92
  ## At a glance
93
93
 
94
- <p align="center">
95
- <img src="https://raw.githubusercontent.com/atomsai/contextdb/main/assets/performance.png" alt="Performance at a glance — search p50 3.4ms, 1,930 writes/sec, 112K PII/sec, 5.0ms p95 at 5K memories, 800K vectors/sec index build, 82/82 tests" width="100%">
96
- </p>
97
-
98
94
  | | |
99
95
  |---|---|
100
- | **Write throughput** | 1,900+ memories/sec |
101
- | **Search latency (1K memories)** | p50 **3.4ms** · p95 **4.5ms** |
102
- | **Search latency (5K memories)** | p50 **3.9ms** · p95 **5.0ms** |
103
- | **Vector search (10K × 1,536d)** | p50 **0.8ms** · p95 **1.0ms** |
96
+ | **Write throughput** | about 1,600 to 1,800 memories/sec |
97
+ | **Search latency (1K memories)** | p50 **11 ms** · p95 **12 to 14 ms** |
98
+ | **Search latency (5K memories)** | p50 **17 ms** · p95 **18 ms** |
99
+ | **Vector search (10K × 1,536d)** | p50 **0.8 ms** · p95 **1.0 ms** |
104
100
  | **PII detection** | 100,000+ texts/sec |
105
- | **Tests** | 195 passing (incl. 34 trust-model acceptance evals) · ruff clean · mypy `--strict` clean |
101
+ | **Tests** | full suite and trust-model acceptance evals in CI on every pull request · ruff · mypy `--strict` |
106
102
  | **Dependencies** | SQLite + NumPy (FAISS / Postgres optional) |
107
103
 
104
+ Measured in October 2026 with `python benchmarks/run_benchmarks.py` on an Apple M4 Max, Python 3.12, SQLite, and the mock embedder, so no model call is included. These numbers are the SDK's own overhead; a real embedding model adds its own latency.
105
+
108
106
  Those search numbers are **SQLite + the in-repo mock embedder**. A recall
109
107
  that embeds the query with OpenAI is typically **100–300ms** before the
110
108
  store is touched. Use `embedding_cache_size`, `pip install 'pycontextdb[local]'`,
@@ -118,18 +116,18 @@ Hermetic, reproducible — run the suite yourself: `python benchmarks/run_benchm
118
116
 
119
117
  Speed is table stakes. The production failure that gets agents fired is **acting on a wish as if it were a fact**: a caller says *"I'd like to come in Thursday"* and the agent answers *"we penciled you in."* A wish and a confirmed fact are the same object in every memory store on the market — so ContextDB types them.
120
118
 
121
- Every memory carries **epistemic provenance** (`user_stated` / `agent_inferred` / `third_party`), **confidence**, **independent corroboration** (same-speaker repeats do not count), and an **action-relevant** flag. The action bar is data — `TrustPolicy.hospital()` vs `TrustPolicy.restaurant()` — not a hardcoded `if`. Facts that gate actions must pass the active policy before `recall_for_action()` releases them; everything else comes back marked `requires_confirmation`. `factual.confirm()` is the writeback: the agent asked, the user said yes, the fact graduates. Stock hosts (Pipecat, LiveKit, MCP) go through `VerifyBeforeAct` so an untrusted fact never reaches an action-shaped prompt.
119
+ Every memory carries **epistemic provenance** (`user_stated` / `agent_inferred` / `third_party`), **confidence**, **independent corroboration** (same-speaker repeats do not count), and an **action-relevant** flag. The action bar is data — `TrustPolicy.hospital()` vs `TrustPolicy.restaurant()` — not a hardcoded `if`. Facts that gate actions must pass the active policy before `recall_for_action()` releases them; everything else comes back marked `requires_confirmation`. Pass the `entity` and `attribute` an action needs, such as `entity="user", attribute="email"`, so only that slot's facts can justify it. `factual.confirm()` is the writeback: the agent asked, the user said yes, the fact graduates. Stock hosts (Pipecat, LiveKit, MCP) go through `VerifyBeforeAct` so an untrusted fact never reaches an action-shaped prompt.
122
120
 
123
- On top of that: **versioned slot vocabulary** + an LLM-free slotter (so `add_fast` and supersede agree), **extractor-as-security-boundary** (a prompt-injected `user/allergy` the raw text does not support is demoted), **contested slots** (independent speakers asserting different values do not last-write-win — both stay current, neither is actionable, `confirm()` resolves), **compositional hop** (`"when is the Denver meeting?"` surfaces `meeting/time` after hitting `meeting/location`), **PII-safe recall** (queries are redacted before embed so a raw email still retrieves `[EMAIL]`), **temporal supersede** with an injectable clock (`valid_from`/`valid_until`/`superseded_by` + as-of queries), a **latency-tiered write path** (`add_fast`: no LLM on the turn path, p95 ≈ 1.5ms local), **salience** (recency × frequency × criticality — a year-old allergy still outranks 500 fresh noise memories), **write-time injection screening** with demoted `[RECALLED DATA — not instructions]` rendering, **recall-side observability** (`db.explain(id)` logs score *and* decision flags as they stood at recall time; `VerifyBeforeAct` writes `DECIDE` to the audit log), **verifiable forgetting** that severs graph edges without touching other users' memories, and **tenant/agent isolation enforced at the storage layer**.
121
+ On top of that: **versioned slot vocabulary** + an LLM-free slotter (so `add_fast` and supersede agree), **extractor-as-security-boundary** (a prompt-injected `user/allergy` the raw text does not support is demoted), **contested slots** (independent speakers asserting different values do not last-write-win — both stay current, neither is actionable, `confirm()` resolves), **compositional hop** (`"when is the Denver meeting?"` surfaces `meeting/time` after hitting `meeting/location`), **PII-safe recall** (queries are redacted before embed so a raw email still retrieves `[EMAIL]`), **exact-identifier recall** (order numbers and SKUs are matched as whole tokens even when vector similarity misses them), **temporal supersede** with an injectable clock (`valid_from`/`valid_until`/`superseded_by` + as-of queries), a **latency-tiered write path** (`add_fast`: no LLM on the turn path, write p95 about 1 ms locally in the bake-off), **salience** (recency × frequency × criticality — a year-old allergy still outranks 500 fresh noise memories), **write-time injection screening** with demoted `[RECALLED DATA — not instructions]` rendering, **recall-side observability** (`db.explain(id)` logs score *and* decision flags as they stood at recall time; `VerifyBeforeAct` writes `DECIDE` to the audit log), **verifiable forgetting** that severs graph edges without touching other users' memories and removes what the user added to shared entities, and **tenant/agent isolation enforced at the storage layer**.
124
122
 
125
- Don't take our word for it — the fabrication benchmark ships in-repo and runs on every PR. Same store, same embedder, same utterance policy; the only difference is the trust model:
123
+ The fabrication benchmark ships in the repository, and a test checks it on every pull request. Both arms use the same store, the same mock embedder, and the same scripted utterance policy; the only difference is the trust model:
126
124
 
127
125
  | Arm | Fabrication rate (lower=better) | Recall accuracy | Temporal supersede | Over-refusal | Write p95 |
128
126
  | --- | --- | --- | --- | --- | --- |
129
- | `contextdb-trust` | **0%** | **100%** | yes | no | 1.5ms |
130
- | `raw-store-baseline` (untyped control) | 50% | 88% | NO | no | 0.9ms |
127
+ | `contextdb-trust` | **0%** | **100%** | yes | no | 0.99 ms |
128
+ | `raw-store-baseline` (untyped control) | 50% | 88% | NO | no | 0.45 ms |
131
129
 
132
- Traps: wish-vs-fact, temporal move, prompt injection, PII redaction, cross-tenant bleed, negation, quantity update, over-refusal. Run it: `python benchmarks/trust_bakeoff.py` — acceptance evals live in `tests/evals/`. Mem0/Zep/Letta/supermemory arms implement the `MemoryArm` protocol and plug into the same harness.
130
+ Traps: wish-vs-fact, temporal move, prompt injection, PII redaction, cross-tenant bleed, negation, quantity update, over-refusal. Run it with `python benchmarks/trust_bakeoff.py`. These eight synthetic traps are regression evidence inside the harness, not a measure of production behavior, and no other memory product has been run through it. Another system can be compared by implementing the `MemoryArm` protocol. The acceptance evals live in `tests/evals/`.
133
131
 
134
132
  ---
135
133
 
@@ -507,49 +505,44 @@ Five layers, one dependency. Every component is modular, testable, and replaceab
507
505
 
508
506
  ## Benchmarks
509
507
 
510
- <p align="center">
511
- <img src="https://raw.githubusercontent.com/atomsai/contextdb/main/assets/benchmarks.png" alt="ContextDB v0.1.0 performance dashboard — search p50 3.4ms, 1,930 writes/sec, 112K PII/sec, 82/82 tests" width="100%">
512
- </p>
513
-
514
- Six workloads, all hermetic, all reproducible. No API keys, no network, no cached results — just `python benchmarks/run_benchmarks.py`.
508
+ Six workloads, all hermetic and reproducible: no API keys, no network, no cached results. Run `python benchmarks/run_benchmarks.py`. The numbers below are from October 2026 on an Apple M4 Max with Python 3.12, SQLite, the mock embedder, and no FAISS. They measure the SDK's own overhead; a real embedding model adds its own latency.
515
509
 
516
510
  ### 1. Write throughput
517
511
 
518
- 1,000 sequential `add()` calls including Pydantic validation, embedding, SQLite INSERT, and vector-index update.
512
+ 1,000 sequential `add()` calls including Pydantic validation, embedding, trust checks, SQLite INSERT, and vector-index update.
519
513
 
520
514
  ```
521
- Throughput: 1,930 writes/sec
522
- Average per write: 0.52ms
515
+ Throughput: about 1,600 to 1,800 writes/sec
516
+ Average per write: 0.6ms
523
517
  ```
524
518
 
525
- ### 2. Search latency — 100 queries against 1,000 memories
519
+ ### 2. Search latency: 100 queries against 1,000 memories
526
520
 
527
521
  ```
528
- p50: 3.40ms
529
- p95: 4.54ms
530
- p99: 4.75ms
531
- Mean: 3.59ms
522
+ p50: 11.0ms
523
+ p95: 14.3ms
524
+ p99: 21.1ms
525
+ Mean: 11.7ms
532
526
  ```
533
527
 
534
- Target was <100ms p95. We hit it by an order of magnitude with room to spare.
528
+ Search includes PII redaction of the query, vector and graph fusion, salience, the trust filter, and the read audit.
535
529
 
536
530
  ### 3. Search latency vs scale
537
531
 
538
- Latency grows sub-linearly; write cost stays flat until the vector index rebuilds.
539
-
540
532
  | Memories | Add (ms/op) | Search p50 | Search p95 |
541
533
  |---:|---:|---:|---:|
542
- | 100 | 0.47ms | 3.26ms | 4.25ms |
543
- | 500 | 0.45ms | 3.59ms | 4.16ms |
544
- | 1,000 | 0.43ms | 3.50ms | 4.24ms |
545
- | 5,000 | 0.53ms | 3.89ms | 4.96ms |
534
+ | 100 | 0.52ms | 9.2ms | 9.8ms |
535
+ | 500 | 0.55ms | 10.0ms | 11.6ms |
536
+ | 1,000 | 0.55ms | 11.1ms | 14.3ms |
537
+ | 5,000 | 0.55ms | 17.2ms | 17.8ms |
546
538
 
547
- ### 4. PII detection & redaction — 1,000 texts
539
+ ### 4. PII detection & redaction: 1,000 texts
548
540
 
549
541
  ```
550
- Throughput: 111,745 texts/sec
542
+ Throughput: 128,194 texts/sec
551
543
  Per text: 0.01ms
552
544
  Types covered: EMAIL, PHONE, SSN, CREDIT_CARD
545
+ (ADDRESS and cued NAME with pii_detect_names_and_addresses=True)
553
546
  ```
554
547
 
555
548
  Correctness spot-check:
@@ -559,29 +552,29 @@ Input: Contact me at test@example.com or 555-123-4567. SSN: 123-45-6789
559
552
  Redacted: Contact me at [EMAIL] or [PHONE]. SSN: [SSN]
560
553
  ```
561
554
 
562
- PII runs before content reaches storage. It never gets embedded. It never gets logged.
555
+ PII runs before content reaches storage. It never gets embedded. It never gets logged. In encrypt mode, slot values that contain PII are stored as keyed fingerprints, so no plaintext email or phone number is written to disk.
563
556
 
564
- ### 5. Vector index — 10K × 1,536-dim vectors
557
+ ### 5. Vector index: 10K × 1,536-dim vectors
565
558
 
566
559
  Pure-NumPy brute force, no FAISS dependency.
567
560
 
568
561
  ```
569
- Index build: 0.013s (~800K vectors/sec)
570
- Search p50: 0.80ms
571
- Search p95: 1.04ms
562
+ Index build: 0.011 to 0.017s
563
+ Search p50: 0.8ms
564
+ Search p95: 1.0ms
572
565
  Self-retrieval accuracy: PASS
573
566
  ```
574
567
 
575
568
  FAISS is available as an optional backend for datasets beyond ~100K vectors.
576
569
 
577
- ### 6. End-to-end — customer support agent
570
+ ### 6. End-to-end: customer support agent
578
571
 
579
- 50 mixed factual/experiential memories, 20 semantic searches, one PII-laden write with redaction verification.
572
+ 50 mixed factual and experiential memories, 20 semantic searches, one PII-laden write with redaction verification.
580
573
 
581
574
  ```
582
- Add 50 memories: 0.03s (1,849/sec)
583
- Search p50: 1.70ms
584
- PII add + redact: 0.76ms
575
+ Add 50 memories: 0.03s (about 1,540/sec)
576
+ Search p50: 4.9ms
577
+ PII add + redact: 1.4ms
585
578
  PII correctly redacted: PASS
586
579
  ```
587
580
 
@@ -592,7 +585,7 @@ Input: Customer John Smith, email john@acme.com, SSN 123-45-6789 called about
592
585
  Stored: Customer John Smith, email [EMAIL], SSN [SSN] called about billing
593
586
  ```
594
587
 
595
- Numbers above are from a MacBook-class laptop with no FAISS. Rerun `python benchmarks/run_benchmarks.py` on your own hardware — the script prints everything you see here.
588
+ Rerun `python benchmarks/run_benchmarks.py` on your own hardware; the script prints everything shown here.
596
589
 
597
590
  ---
598
591
 
@@ -8,10 +8,10 @@
8
8
  <p align="center">
9
9
  <a href="https://pypi.org/project/pycontextdb/"><img src="https://img.shields.io/pypi/v/pycontextdb.svg" alt="PyPI version"></a>
10
10
  <a href="https://github.com/atomsai/contextdb/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0"></a>
11
- <a href="https://github.com/atomsai/contextdb/blob/main/tests/"><img src="https://img.shields.io/badge/tests-195%20passing-brightgreen.svg" alt="Tests"></a>
11
+ <a href="https://github.com/atomsai/contextdb/actions/workflows/ci.yml"><img src="https://github.com/atomsai/contextdb/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
12
12
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
13
13
  <a href="https://github.com/atomsai/contextdb/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/mypy-strict-blue.svg" alt="Type Checked"></a>
14
- <a href="https://github.com/atomsai/contextdb/blob/main/benchmarks/run_benchmarks.py"><img src="https://img.shields.io/badge/search_p95-%3C5ms_%40_5K-brightgreen.svg" alt="Search p95"></a>
14
+ <a href="https://github.com/atomsai/contextdb/blob/main/benchmarks/run_benchmarks.py"><img src="https://img.shields.io/badge/search_p95-18ms_%40_5K_(mock_embedder)-blue.svg" alt="Search p95 18 ms at 5K memories with the mock embedder"></a>
15
15
  <a href="https://github.com/atomsai/contextdb/stargazers"><img src="https://img.shields.io/github/stars/atomsai/contextdb?style=social" alt="GitHub stars"></a>
16
16
  </p>
17
17
 
@@ -25,20 +25,18 @@
25
25
 
26
26
  ## At a glance
27
27
 
28
- <p align="center">
29
- <img src="https://raw.githubusercontent.com/atomsai/contextdb/main/assets/performance.png" alt="Performance at a glance — search p50 3.4ms, 1,930 writes/sec, 112K PII/sec, 5.0ms p95 at 5K memories, 800K vectors/sec index build, 82/82 tests" width="100%">
30
- </p>
31
-
32
28
  | | |
33
29
  |---|---|
34
- | **Write throughput** | 1,900+ memories/sec |
35
- | **Search latency (1K memories)** | p50 **3.4ms** · p95 **4.5ms** |
36
- | **Search latency (5K memories)** | p50 **3.9ms** · p95 **5.0ms** |
37
- | **Vector search (10K × 1,536d)** | p50 **0.8ms** · p95 **1.0ms** |
30
+ | **Write throughput** | about 1,600 to 1,800 memories/sec |
31
+ | **Search latency (1K memories)** | p50 **11 ms** · p95 **12 to 14 ms** |
32
+ | **Search latency (5K memories)** | p50 **17 ms** · p95 **18 ms** |
33
+ | **Vector search (10K × 1,536d)** | p50 **0.8 ms** · p95 **1.0 ms** |
38
34
  | **PII detection** | 100,000+ texts/sec |
39
- | **Tests** | 195 passing (incl. 34 trust-model acceptance evals) · ruff clean · mypy `--strict` clean |
35
+ | **Tests** | full suite and trust-model acceptance evals in CI on every pull request · ruff · mypy `--strict` |
40
36
  | **Dependencies** | SQLite + NumPy (FAISS / Postgres optional) |
41
37
 
38
+ Measured in October 2026 with `python benchmarks/run_benchmarks.py` on an Apple M4 Max, Python 3.12, SQLite, and the mock embedder, so no model call is included. These numbers are the SDK's own overhead; a real embedding model adds its own latency.
39
+
42
40
  Those search numbers are **SQLite + the in-repo mock embedder**. A recall
43
41
  that embeds the query with OpenAI is typically **100–300ms** before the
44
42
  store is touched. Use `embedding_cache_size`, `pip install 'pycontextdb[local]'`,
@@ -52,18 +50,18 @@ Hermetic, reproducible — run the suite yourself: `python benchmarks/run_benchm
52
50
 
53
51
  Speed is table stakes. The production failure that gets agents fired is **acting on a wish as if it were a fact**: a caller says *"I'd like to come in Thursday"* and the agent answers *"we penciled you in."* A wish and a confirmed fact are the same object in every memory store on the market — so ContextDB types them.
54
52
 
55
- Every memory carries **epistemic provenance** (`user_stated` / `agent_inferred` / `third_party`), **confidence**, **independent corroboration** (same-speaker repeats do not count), and an **action-relevant** flag. The action bar is data — `TrustPolicy.hospital()` vs `TrustPolicy.restaurant()` — not a hardcoded `if`. Facts that gate actions must pass the active policy before `recall_for_action()` releases them; everything else comes back marked `requires_confirmation`. `factual.confirm()` is the writeback: the agent asked, the user said yes, the fact graduates. Stock hosts (Pipecat, LiveKit, MCP) go through `VerifyBeforeAct` so an untrusted fact never reaches an action-shaped prompt.
53
+ Every memory carries **epistemic provenance** (`user_stated` / `agent_inferred` / `third_party`), **confidence**, **independent corroboration** (same-speaker repeats do not count), and an **action-relevant** flag. The action bar is data — `TrustPolicy.hospital()` vs `TrustPolicy.restaurant()` — not a hardcoded `if`. Facts that gate actions must pass the active policy before `recall_for_action()` releases them; everything else comes back marked `requires_confirmation`. Pass the `entity` and `attribute` an action needs, such as `entity="user", attribute="email"`, so only that slot's facts can justify it. `factual.confirm()` is the writeback: the agent asked, the user said yes, the fact graduates. Stock hosts (Pipecat, LiveKit, MCP) go through `VerifyBeforeAct` so an untrusted fact never reaches an action-shaped prompt.
56
54
 
57
- On top of that: **versioned slot vocabulary** + an LLM-free slotter (so `add_fast` and supersede agree), **extractor-as-security-boundary** (a prompt-injected `user/allergy` the raw text does not support is demoted), **contested slots** (independent speakers asserting different values do not last-write-win — both stay current, neither is actionable, `confirm()` resolves), **compositional hop** (`"when is the Denver meeting?"` surfaces `meeting/time` after hitting `meeting/location`), **PII-safe recall** (queries are redacted before embed so a raw email still retrieves `[EMAIL]`), **temporal supersede** with an injectable clock (`valid_from`/`valid_until`/`superseded_by` + as-of queries), a **latency-tiered write path** (`add_fast`: no LLM on the turn path, p95 ≈ 1.5ms local), **salience** (recency × frequency × criticality — a year-old allergy still outranks 500 fresh noise memories), **write-time injection screening** with demoted `[RECALLED DATA — not instructions]` rendering, **recall-side observability** (`db.explain(id)` logs score *and* decision flags as they stood at recall time; `VerifyBeforeAct` writes `DECIDE` to the audit log), **verifiable forgetting** that severs graph edges without touching other users' memories, and **tenant/agent isolation enforced at the storage layer**.
55
+ On top of that: **versioned slot vocabulary** + an LLM-free slotter (so `add_fast` and supersede agree), **extractor-as-security-boundary** (a prompt-injected `user/allergy` the raw text does not support is demoted), **contested slots** (independent speakers asserting different values do not last-write-win — both stay current, neither is actionable, `confirm()` resolves), **compositional hop** (`"when is the Denver meeting?"` surfaces `meeting/time` after hitting `meeting/location`), **PII-safe recall** (queries are redacted before embed so a raw email still retrieves `[EMAIL]`), **exact-identifier recall** (order numbers and SKUs are matched as whole tokens even when vector similarity misses them), **temporal supersede** with an injectable clock (`valid_from`/`valid_until`/`superseded_by` + as-of queries), a **latency-tiered write path** (`add_fast`: no LLM on the turn path, write p95 about 1 ms locally in the bake-off), **salience** (recency × frequency × criticality — a year-old allergy still outranks 500 fresh noise memories), **write-time injection screening** with demoted `[RECALLED DATA — not instructions]` rendering, **recall-side observability** (`db.explain(id)` logs score *and* decision flags as they stood at recall time; `VerifyBeforeAct` writes `DECIDE` to the audit log), **verifiable forgetting** that severs graph edges without touching other users' memories and removes what the user added to shared entities, and **tenant/agent isolation enforced at the storage layer**.
58
56
 
59
- Don't take our word for it — the fabrication benchmark ships in-repo and runs on every PR. Same store, same embedder, same utterance policy; the only difference is the trust model:
57
+ The fabrication benchmark ships in the repository, and a test checks it on every pull request. Both arms use the same store, the same mock embedder, and the same scripted utterance policy; the only difference is the trust model:
60
58
 
61
59
  | Arm | Fabrication rate (lower=better) | Recall accuracy | Temporal supersede | Over-refusal | Write p95 |
62
60
  | --- | --- | --- | --- | --- | --- |
63
- | `contextdb-trust` | **0%** | **100%** | yes | no | 1.5ms |
64
- | `raw-store-baseline` (untyped control) | 50% | 88% | NO | no | 0.9ms |
61
+ | `contextdb-trust` | **0%** | **100%** | yes | no | 0.99 ms |
62
+ | `raw-store-baseline` (untyped control) | 50% | 88% | NO | no | 0.45 ms |
65
63
 
66
- Traps: wish-vs-fact, temporal move, prompt injection, PII redaction, cross-tenant bleed, negation, quantity update, over-refusal. Run it: `python benchmarks/trust_bakeoff.py` — acceptance evals live in `tests/evals/`. Mem0/Zep/Letta/supermemory arms implement the `MemoryArm` protocol and plug into the same harness.
64
+ Traps: wish-vs-fact, temporal move, prompt injection, PII redaction, cross-tenant bleed, negation, quantity update, over-refusal. Run it with `python benchmarks/trust_bakeoff.py`. These eight synthetic traps are regression evidence inside the harness, not a measure of production behavior, and no other memory product has been run through it. Another system can be compared by implementing the `MemoryArm` protocol. The acceptance evals live in `tests/evals/`.
67
65
 
68
66
  ---
69
67
 
@@ -441,49 +439,44 @@ Five layers, one dependency. Every component is modular, testable, and replaceab
441
439
 
442
440
  ## Benchmarks
443
441
 
444
- <p align="center">
445
- <img src="https://raw.githubusercontent.com/atomsai/contextdb/main/assets/benchmarks.png" alt="ContextDB v0.1.0 performance dashboard — search p50 3.4ms, 1,930 writes/sec, 112K PII/sec, 82/82 tests" width="100%">
446
- </p>
447
-
448
- Six workloads, all hermetic, all reproducible. No API keys, no network, no cached results — just `python benchmarks/run_benchmarks.py`.
442
+ Six workloads, all hermetic and reproducible: no API keys, no network, no cached results. Run `python benchmarks/run_benchmarks.py`. The numbers below are from October 2026 on an Apple M4 Max with Python 3.12, SQLite, the mock embedder, and no FAISS. They measure the SDK's own overhead; a real embedding model adds its own latency.
449
443
 
450
444
  ### 1. Write throughput
451
445
 
452
- 1,000 sequential `add()` calls including Pydantic validation, embedding, SQLite INSERT, and vector-index update.
446
+ 1,000 sequential `add()` calls including Pydantic validation, embedding, trust checks, SQLite INSERT, and vector-index update.
453
447
 
454
448
  ```
455
- Throughput: 1,930 writes/sec
456
- Average per write: 0.52ms
449
+ Throughput: about 1,600 to 1,800 writes/sec
450
+ Average per write: 0.6ms
457
451
  ```
458
452
 
459
- ### 2. Search latency — 100 queries against 1,000 memories
453
+ ### 2. Search latency: 100 queries against 1,000 memories
460
454
 
461
455
  ```
462
- p50: 3.40ms
463
- p95: 4.54ms
464
- p99: 4.75ms
465
- Mean: 3.59ms
456
+ p50: 11.0ms
457
+ p95: 14.3ms
458
+ p99: 21.1ms
459
+ Mean: 11.7ms
466
460
  ```
467
461
 
468
- Target was <100ms p95. We hit it by an order of magnitude with room to spare.
462
+ Search includes PII redaction of the query, vector and graph fusion, salience, the trust filter, and the read audit.
469
463
 
470
464
  ### 3. Search latency vs scale
471
465
 
472
- Latency grows sub-linearly; write cost stays flat until the vector index rebuilds.
473
-
474
466
  | Memories | Add (ms/op) | Search p50 | Search p95 |
475
467
  |---:|---:|---:|---:|
476
- | 100 | 0.47ms | 3.26ms | 4.25ms |
477
- | 500 | 0.45ms | 3.59ms | 4.16ms |
478
- | 1,000 | 0.43ms | 3.50ms | 4.24ms |
479
- | 5,000 | 0.53ms | 3.89ms | 4.96ms |
468
+ | 100 | 0.52ms | 9.2ms | 9.8ms |
469
+ | 500 | 0.55ms | 10.0ms | 11.6ms |
470
+ | 1,000 | 0.55ms | 11.1ms | 14.3ms |
471
+ | 5,000 | 0.55ms | 17.2ms | 17.8ms |
480
472
 
481
- ### 4. PII detection & redaction — 1,000 texts
473
+ ### 4. PII detection & redaction: 1,000 texts
482
474
 
483
475
  ```
484
- Throughput: 111,745 texts/sec
476
+ Throughput: 128,194 texts/sec
485
477
  Per text: 0.01ms
486
478
  Types covered: EMAIL, PHONE, SSN, CREDIT_CARD
479
+ (ADDRESS and cued NAME with pii_detect_names_and_addresses=True)
487
480
  ```
488
481
 
489
482
  Correctness spot-check:
@@ -493,29 +486,29 @@ Input: Contact me at test@example.com or 555-123-4567. SSN: 123-45-6789
493
486
  Redacted: Contact me at [EMAIL] or [PHONE]. SSN: [SSN]
494
487
  ```
495
488
 
496
- PII runs before content reaches storage. It never gets embedded. It never gets logged.
489
+ PII runs before content reaches storage. It never gets embedded. It never gets logged. In encrypt mode, slot values that contain PII are stored as keyed fingerprints, so no plaintext email or phone number is written to disk.
497
490
 
498
- ### 5. Vector index — 10K × 1,536-dim vectors
491
+ ### 5. Vector index: 10K × 1,536-dim vectors
499
492
 
500
493
  Pure-NumPy brute force, no FAISS dependency.
501
494
 
502
495
  ```
503
- Index build: 0.013s (~800K vectors/sec)
504
- Search p50: 0.80ms
505
- Search p95: 1.04ms
496
+ Index build: 0.011 to 0.017s
497
+ Search p50: 0.8ms
498
+ Search p95: 1.0ms
506
499
  Self-retrieval accuracy: PASS
507
500
  ```
508
501
 
509
502
  FAISS is available as an optional backend for datasets beyond ~100K vectors.
510
503
 
511
- ### 6. End-to-end — customer support agent
504
+ ### 6. End-to-end: customer support agent
512
505
 
513
- 50 mixed factual/experiential memories, 20 semantic searches, one PII-laden write with redaction verification.
506
+ 50 mixed factual and experiential memories, 20 semantic searches, one PII-laden write with redaction verification.
514
507
 
515
508
  ```
516
- Add 50 memories: 0.03s (1,849/sec)
517
- Search p50: 1.70ms
518
- PII add + redact: 0.76ms
509
+ Add 50 memories: 0.03s (about 1,540/sec)
510
+ Search p50: 4.9ms
511
+ PII add + redact: 1.4ms
519
512
  PII correctly redacted: PASS
520
513
  ```
521
514
 
@@ -526,7 +519,7 @@ Input: Customer John Smith, email john@acme.com, SSN 123-45-6789 called about
526
519
  Stored: Customer John Smith, email [EMAIL], SSN [SSN] called about billing
527
520
  ```
528
521
 
529
- Numbers above are from a MacBook-class laptop with no FAISS. Rerun `python benchmarks/run_benchmarks.py` on your own hardware — the script prints everything you see here.
522
+ Rerun `python benchmarks/run_benchmarks.py` on your own hardware; the script prints everything shown here.
530
523
 
531
524
  ---
532
525
 
@@ -41,7 +41,7 @@ from contextdb.core.policy import TrustPolicy
41
41
  from contextdb.pool import ContextDBPool
42
42
  from contextdb.utils.embeddings import EmbeddingProvider
43
43
 
44
- __version__ = "0.4.5"
44
+ __version__ = "0.4.7"
45
45
 
46
46
  __all__ = [
47
47
  "Clock",