pycontextdb 0.4.4__tar.gz → 0.4.5__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.
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/CHANGELOG.md +15 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/PKG-INFO +2 -2
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/README.md +1 -1
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/__init__.py +1 -1
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/client.py +5 -13
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/evolution.py +3 -2
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/architecture.md +5 -2
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/pyproject.toml +1 -1
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/evals/test_trust_model.py +36 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_evolution.py +26 -1
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.github/CODEOWNERS +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.github/copilot-instructions.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.github/pull_request_template.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.github/workflows/ci.yml +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.github/workflows/publish.yml +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.gitignore +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/.pre-commit-config.yaml +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/COMMERCIAL.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/CONTRIBUTING.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/LICENSE +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/NOTICE +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/OPEN_CORE.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/ai-tools/.cursorrules +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/ai-tools/CLAUDE.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/ai-tools/README.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/ai-tools/llms-full.txt +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/ai-tools/llms.txt +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/README.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/architecture.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/benchmarks.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/hero.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/performance.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/problem.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/assets/quickstart.png +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/benchmarks/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/benchmarks/run_benchmarks.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/benchmarks/trust_bakeoff.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/agents/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/agents/memory_bus.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/agents/rl_manager.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/cli.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/connectors/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/connectors/base.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/connectors/postgres.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/clock.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/config.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/exceptions.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/models.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/policy.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/core/slots.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/formation.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/retrieval.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/salience.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/dynamics/trust.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/base.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/causal.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/entity.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/semantic.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/graphs/temporal.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/act.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/autogen.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/crewai.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/langchain.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/livekit.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/openai_tools.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/pipecat.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/integrations/prompting.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/mcp.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/memory/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/memory/experiential.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/memory/factual.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/memory/working.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/pool.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/privacy/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/privacy/audit.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/privacy/injection.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/privacy/pii_detector.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/privacy/retention.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/py.typed +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/serve.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/base.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/factory.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/pg_sql.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/postgres_store.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/sqlite_store.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/store/vector_index.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/utils/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/utils/embeddings.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/utils/llm.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/contextdb/utils/migrations.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/api.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/connectors.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/index.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/memory_evolution.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/migrations.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/multi_tenant.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/postgres.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/prompts.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/quickstart.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/serve.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/docs/trust_policy.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/examples/customer_support.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/examples/multi_agent_team.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/examples/phone_agent.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/examples/research_assistant.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/legal/CLA-entity.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/legal/CLA-individual.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/legal/signed/README.md +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/mkdocs.yml +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/scripts/check_open_core_boundary.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/scripts/check_release.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/conftest.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/evals/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/integration/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/integration/test_client.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/integration/test_langchain_integration.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/pg_util.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/test_version.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/__init__.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_audit.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_config.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_connectors.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_decision_scope.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_embeddings.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_formation.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_graphs.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_host_api.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_init.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_memory_bus.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_memory_evolution.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_models.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_open_core_boundary.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_pg_sql.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_pii_detector.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_postgres_concurrency.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_postgres_connector.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_postgres_memory_evolution.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_retention.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_retrieval.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_scoped_id_access.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_serve.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_store.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_trust_policy.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_vector_index.py +0 -0
- {pycontextdb-0.4.4 → pycontextdb-0.4.5}/tests/unit/test_vector_scope.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.4.5 — 2026-10-05
|
|
6
|
+
|
|
7
|
+
- `forget_user` no longer deletes other users' memories. It added every graph
|
|
8
|
+
neighbor of the erased memories to the deletion set, and entity edges join
|
|
9
|
+
any memories that mention the same name, so on a shared client with the
|
|
10
|
+
entity graph and an extraction model, erasing one user could hard-delete
|
|
11
|
+
another user's memories. It now deletes only the user's memories and the
|
|
12
|
+
summaries derived from them, and severs their graph edges.
|
|
13
|
+
- Consolidated summaries keep the weakest corroboration count of their inputs
|
|
14
|
+
instead of the sum. Repeated copies of one third-party claim could
|
|
15
|
+
consolidate into a summary that passed the default trust bar and was
|
|
16
|
+
returned by `recall_for_action`. The summary now stays pending confirmation.
|
|
17
|
+
- Unchanged limitation: entity rows are shared across users, and attributes
|
|
18
|
+
merged into an entity from a user's text are not removed by `forget_user`.
|
|
19
|
+
|
|
5
20
|
## 0.4.4 — 2026-09-20
|
|
6
21
|
|
|
7
22
|
- PostgreSQL sibling composition now uses an ordered user-plus-entity cache
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pycontextdb
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
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
|
|
@@ -120,7 +120,7 @@ Speed is table stakes. The production failure that gets agents fired is **acting
|
|
|
120
120
|
|
|
121
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.
|
|
122
122
|
|
|
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
|
|
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**.
|
|
124
124
|
|
|
125
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:
|
|
126
126
|
|
|
@@ -54,7 +54,7 @@ Speed is table stakes. The production failure that gets agents fired is **acting
|
|
|
54
54
|
|
|
55
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.
|
|
56
56
|
|
|
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
|
|
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**.
|
|
58
58
|
|
|
59
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:
|
|
60
60
|
|
|
@@ -1685,21 +1685,13 @@ class ContextDB:
|
|
|
1685
1685
|
if added == 0:
|
|
1686
1686
|
break
|
|
1687
1687
|
|
|
1688
|
-
#
|
|
1689
|
-
#
|
|
1690
|
-
# the
|
|
1688
|
+
# Sever graph edges so a forgotten memory cannot be reached or
|
|
1689
|
+
# rebuilt through the graph. Neighbors are never added to the
|
|
1690
|
+
# deletion set: entity edges join every memory that mentions the
|
|
1691
|
+
# same name, including other users' memories, and this user's own
|
|
1692
|
+
# and derived memories are already in ``targets``.
|
|
1691
1693
|
for graph in self._graphs.values():
|
|
1692
1694
|
for memory_id in list(targets):
|
|
1693
|
-
try:
|
|
1694
|
-
neighbors = await graph.get_neighbors(memory_id, max_results=50)
|
|
1695
|
-
except Exception: # noqa: BLE001
|
|
1696
|
-
continue
|
|
1697
|
-
for nid, _weight in neighbors:
|
|
1698
|
-
if nid in targets:
|
|
1699
|
-
continue
|
|
1700
|
-
neighbor = await store.get_raw(nid)
|
|
1701
|
-
if neighbor is not None:
|
|
1702
|
-
targets[nid] = neighbor
|
|
1703
1695
|
try:
|
|
1704
1696
|
await graph.remove_node(memory_id)
|
|
1705
1697
|
except Exception: # noqa: BLE001
|
|
@@ -98,7 +98,8 @@ class Consolidator:
|
|
|
98
98
|
# Trust-conservative merge: a summary must never be MORE trusted
|
|
99
99
|
# than its least trusted input. Otherwise consolidation launders
|
|
100
100
|
# epistemic status — five third-party hearsay fragments would
|
|
101
|
-
# emerge as a confident first-party fact.
|
|
101
|
+
# emerge as a confident first-party fact. Corroboration is not
|
|
102
|
+
# summed: copies of one claim are not independent support.
|
|
102
103
|
new_item = MemoryItem(
|
|
103
104
|
content=summary,
|
|
104
105
|
embedding=cluster_items[0].embedding,
|
|
@@ -110,7 +111,7 @@ class Consolidator:
|
|
|
110
111
|
key=lambda s: _SOURCE_RANK[s],
|
|
111
112
|
),
|
|
112
113
|
confidence=min(m.confidence for m in cluster_items),
|
|
113
|
-
corroboration_count=
|
|
114
|
+
corroboration_count=min(m.independent_corroboration for m in cluster_items),
|
|
114
115
|
action_relevant=any(m.action_relevant for m in cluster_items),
|
|
115
116
|
injection_suspect=any(m.injection_suspect for m in cluster_items),
|
|
116
117
|
)
|
|
@@ -41,7 +41,8 @@ Three pipelines operate over the storage layer:
|
|
|
41
41
|
NOOP operations with lineage and consistency results. Separately, the
|
|
42
42
|
auto-linker mirrors each new write into graph indices; the consolidator
|
|
43
43
|
merges dense semantic clusters into summaries that inherit *worst-case*
|
|
44
|
-
trust (no laundering); the
|
|
44
|
+
trust, including the weakest corroboration count (no laundering); the
|
|
45
|
+
pruner drops stale / redundant memories by
|
|
45
46
|
policy.
|
|
46
47
|
3. **Retrieval** — a query becomes an answer. The query is PII-redacted
|
|
47
48
|
before embed. Query classifier picks graph weights; each graph produces
|
|
@@ -78,7 +79,9 @@ search, `DECIDE` (act/ask/abstain), and deletion — and because the chain
|
|
|
78
79
|
is append-only, only the PII-processed form of a query is ever logged.
|
|
79
80
|
`pii_action="encrypt"` fails closed: without a key the client refuses to
|
|
80
81
|
initialize rather than store plaintext annotation originals.
|
|
81
|
-
`forget_user`
|
|
82
|
+
`forget_user` deletes the user's memories and the summaries derived from them,
|
|
83
|
+
severs their graph edges, and signs the deletion set. It never deletes another
|
|
84
|
+
user's memory through a shared edge. The retention
|
|
82
85
|
manager applies typed TTLs and honors right-to-erasure requests. Isolation
|
|
83
86
|
is a store predicate (`user_id` / `tenant_id` / `agent_id`), not a
|
|
84
87
|
convention.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pycontextdb"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.5"
|
|
8
8
|
description = "The unified context layer for AI agents — memory you can act on without treating wishes as facts"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -813,6 +813,42 @@ async def test_eval_7_1_forget_user_leaves_zero_residue(tmp_path: Path) -> None:
|
|
|
813
813
|
await db.close()
|
|
814
814
|
|
|
815
815
|
|
|
816
|
+
async def test_eval_7_2_forget_user_spares_other_users_sharing_an_entity(
|
|
817
|
+
tmp_path: Path,
|
|
818
|
+
) -> None:
|
|
819
|
+
"""EVAL-7.2: on a shared client, erasing one user must not delete another
|
|
820
|
+
user's memory because both mention the same entity."""
|
|
821
|
+
db = contextdb.init(
|
|
822
|
+
tenant_id="org-shared",
|
|
823
|
+
config=make_config(tmp_path, enable_entity_graph=True),
|
|
824
|
+
)
|
|
825
|
+
db._llm = MockLLM(
|
|
826
|
+
responses={"Extract": '{"entities": [{"name": "Acme", "type": "ORGANIZATION"}]}'}
|
|
827
|
+
)
|
|
828
|
+
try:
|
|
829
|
+
mine = await db.factual.add(
|
|
830
|
+
"Prefers Friday calls about the Acme account.",
|
|
831
|
+
source="user_stated",
|
|
832
|
+
user_id="alice",
|
|
833
|
+
)
|
|
834
|
+
theirs = await db.factual.add(
|
|
835
|
+
"Escalation contact for the Acme renewal.",
|
|
836
|
+
source="user_stated",
|
|
837
|
+
user_id="bob",
|
|
838
|
+
)
|
|
839
|
+
graph = db._graphs["entity"]
|
|
840
|
+
assert [nid for nid, _ in await graph.get_neighbors(mine.id)] == [theirs.id]
|
|
841
|
+
|
|
842
|
+
deleted = await db.forget_user("alice")
|
|
843
|
+
|
|
844
|
+
assert deleted == 1
|
|
845
|
+
assert await db.verify_forgotten("alice") is True
|
|
846
|
+
assert [m.id for m in await db.factual.list_facts(user_id="bob")] == [theirs.id]
|
|
847
|
+
assert await graph.get_neighbors(theirs.id) == []
|
|
848
|
+
finally:
|
|
849
|
+
await db.close()
|
|
850
|
+
|
|
851
|
+
|
|
816
852
|
# ---------------------------------------------------------------------------
|
|
817
853
|
# Epic 8 — Realtime + agent-host integrations
|
|
818
854
|
# ---------------------------------------------------------------------------
|
|
@@ -22,9 +22,34 @@ async def test_consolidation_merges_with_worst_case_trust(client: ContextDB) ->
|
|
|
22
22
|
assert summary.source == "consolidator"
|
|
23
23
|
assert summary.epistemic_source == "third_party"
|
|
24
24
|
assert summary.confidence == 0.4
|
|
25
|
-
assert summary.corroboration_count ==
|
|
25
|
+
assert summary.corroboration_count == 1
|
|
26
26
|
assert summary.action_relevant is True
|
|
27
27
|
assert summary.metadata["consolidated_from"]
|
|
28
|
+
assert client.trust_policy.is_trusted(summary) is False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def test_consolidated_copies_of_one_rumor_still_require_confirmation(
|
|
32
|
+
client: ContextDB,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Five copies of one third-party rumor are one source, not five. The
|
|
35
|
+
summary must stay pending confirmation instead of passing the trust bar."""
|
|
36
|
+
copies = [
|
|
37
|
+
await client.factual.add(
|
|
38
|
+
"Forum post says the refund for the annual plan was approved.",
|
|
39
|
+
source="third_party",
|
|
40
|
+
action_relevant=True,
|
|
41
|
+
)
|
|
42
|
+
for _ in range(5)
|
|
43
|
+
]
|
|
44
|
+
assert all(copy.requires_confirmation_under(client.trust_policy) for copy in copies)
|
|
45
|
+
|
|
46
|
+
summaries = await client.consolidate(min_cluster_size=5)
|
|
47
|
+
|
|
48
|
+
assert summaries, "identical copies should have merged"
|
|
49
|
+
summary = summaries[0]
|
|
50
|
+
assert summary.independent_corroboration == 1
|
|
51
|
+
assert summary.requires_confirmation_under(client.trust_policy)
|
|
52
|
+
assert await client.factual.recall_for_action("issue the annual plan refund") == []
|
|
28
53
|
|
|
29
54
|
|
|
30
55
|
async def test_consolidation_screens_summary_for_injection(client: ContextDB) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|