diff --git a/pyproject.toml b/pyproject.toml index 8b61939..070cf67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "opencortex" -version = "0.7.0" +version = "0.8.0" description = "Memory and context management system for AI Agents" readme = "README.md" requires-python = ">=3.10" diff --git a/src/opencortex/__init__.py b/src/opencortex/__init__.py index db4c92c..3766bdd 100644 --- a/src/opencortex/__init__.py +++ b/src/opencortex/__init__.py @@ -5,7 +5,7 @@ Tenant-based multi-user memory and context management system. """ -__version__ = "0.7.0" +__version__ = "0.8.0" from opencortex.config import CortexConfig, get_config, init_config diff --git a/src/opencortex/insights/collector.py b/src/opencortex/insights/collector.py index aa919e3..2458a84 100644 --- a/src/opencortex/insights/collector.py +++ b/src/opencortex/insights/collector.py @@ -46,11 +46,21 @@ async def fetch_traces( Returns: List of Trace objects """ + # Filter DSL fix: the canonical shape for storage.filter is + # ``{"op": "and", "conds": [...]}`` at the outer level and + # ``{"op": "must", "field": "X", "conds": [val]}`` at the leaf + # level (see ``SessionRecordsRepository._build_session_filter`` + # for the reference pattern). The previous shape used + # ``conditions`` instead of ``conds`` and ``op="="`` instead of + # ``op="must"`` — the storage adapter's filter translator + # silently treated the whole expression as no-op, returning + # all traces in the collection regardless of tenant/user + # scope. Cross-tenant insights leaked. filter_expr = { "op": "and", - "conditions": [ - {"field": "tenant_id", "op": "=", "value": tenant_id}, - {"field": "user_id", "op": "=", "value": user_id}, + "conds": [ + {"op": "must", "field": "tenant_id", "conds": [tenant_id]}, + {"op": "must", "field": "user_id", "conds": [user_id]}, ], } all_records = await self._trace_store._storage.filter( diff --git a/src/opencortex/orchestrator.py b/src/opencortex/orchestrator.py index 86c5652..2d2e5ac 100644 --- a/src/opencortex/orchestrator.py +++ b/src/opencortex/orchestrator.py @@ -2482,6 +2482,19 @@ async def _sync_anchor_projection_records( all_new_records = anchor_records + fp_records + # REVIEW closure tracker R3-P-06 — short-circuit when the new + # projection is empty AND there's no abstract_json input. + # The defer_derive initial leaf write hits this path with both + # inputs empty and would otherwise pay 2 stale-filter scans + # over Qdrant prefixes that are guaranteed to be empty (the + # leaf is brand new). The ``not abstract_json`` guard keeps + # the cleanup path on legitimate update flows where + # abstract_json is present but happens to contribute no + # anchors or fact_points — those updates DO need stale + # cleanup. + if not all_new_records and not abstract_json: + return + # Embed all texts in a single batch call if all_new_records and self._embedder: texts = [r["overview"] for r in all_new_records]