Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/contextweaver/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ def jaccard(a: set[str], b: set[str]) -> float:
if not a and not b:
return 0.0
union = a | b
if not union:
return 0.0
return len(a & b) / len(union)


Expand Down
4 changes: 2 additions & 2 deletions src/contextweaver/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any
from typing import Any, Literal

from contextweaver.types import ItemKind, Phase, Sensitivity

Expand Down Expand Up @@ -114,7 +114,7 @@ class ContextPolicy:
max_items_per_kind: dict[ItemKind, int] = field(
default_factory=lambda: {k: 50 for k in ItemKind}
)
ttl_behavior: str = "drop"
ttl_behavior: Literal["drop", "warn"] = "drop"
sensitivity_floor: Sensitivity = Sensitivity.confidential
redaction_hooks: list[str] = field(default_factory=list)
extra: dict[str, Any] = field(default_factory=dict)
4 changes: 3 additions & 1 deletion src/contextweaver/context/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(
self._scoring = scoring_config or ScoringConfig()
self._estimator: TokenEstimator = estimator or CharDivFourEstimator()
self._hook: EventHook = hook or NoOpHook()
self._fact_counter: int = 0
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializing _fact_counter to 0 can reintroduce fact_id collisions when a ContextManager is created with a pre-populated fact_store (e.g., restored from InMemoryFactStore.from_dict() or injected via StoreBundle). In that case, subsequent add_fact() calls can generate IDs that already exist and silently overwrite facts (since InMemoryFactStore.put() replaces by fact_id). Consider seeding _fact_counter from the existing store (e.g., max numeric suffix seen) or switching to a collision-resistant ID scheme (uuid/monotonic timestamp).

Copilot uses AI. Check for mistakes.

# ------------------------------------------------------------------
# Properties
Expand Down Expand Up @@ -290,7 +291,8 @@ def add_fact(self, key: str, value: str, metadata: dict[str, Any] | None = None)
value: Fact value.
metadata: Optional metadata dict.
"""
fact_id = f"fact:{key}:{len(self._fact_store.all())}"
self._fact_counter += 1
fact_id = f"fact:{key}:{self._fact_counter}"
Comment on lines +294 to +295
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fact_id = f"fact:{key}:{self._fact_counter}" relies on an in-memory counter that currently resets on new ContextManager instances. If the manager is constructed with an existing fact_store, this can generate duplicate fact_ids and overwrite existing facts in put(). Either derive the next counter value from the store on init, or generate IDs independently of process lifetime (e.g., uuid) to make restores safe.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (nice-to-have): This fix changes runtime ID generation behavior. A small regression test in test_manager.py that does add → delete → add and asserts no fact_id collision would document the intent and prevent regressions.

Also worth noting: existing Copilot review comments on L92 and L295 correctly flag the rehydration edge case — the counter should ideally be seeded from the store's existing max suffix when constructed with a pre-populated InMemoryFactStore.

self._fact_store.put(
Fact(
fact_id=fact_id,
Expand Down
4 changes: 0 additions & 4 deletions src/contextweaver/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ def exists(self, handle: str) -> bool:
"""Return ``True`` if *handle* is in the store."""
...

def metadata(self, handle: str) -> ArtifactRef:
"""Return the :class:`~contextweaver.types.ArtifactRef` for *handle*."""
...

def drilldown(self, handle: str, selector: dict[str, Any]) -> str:
"""Return a subset of the artifact's content according to *selector*."""
...
Expand Down
3 changes: 2 additions & 1 deletion src/contextweaver/routing/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(
self._items: dict[str, SelectableItem] = {}
self._scorer = scorer
self._indexed = False
self._doc_id_to_idx: dict[str, int] = {}
if items is not None:
self.set_items(items)

Expand Down Expand Up @@ -124,7 +125,7 @@ def _ensure_index(self) -> None:
doc_ids.append(node_id)

self._scorer.fit(docs)
self._doc_id_to_idx: dict[str, int] = {did: i for i, did in enumerate(doc_ids)}
self._doc_id_to_idx = {did: i for i, did in enumerate(doc_ids)}
self._indexed = True

def _score_node(self, query: str, node_id: str) -> float:
Expand Down