Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: kidjustinchoi@gmail.com |
|
I have read the CLA Document and I hereby sign the CLA |
Graphiti's bi-temporal model invalidates facts instead of deleting them. The MCP tool exposed only invalid_at date-range parameters, preventing clients from requesting only currently true facts. Core already supports this via ComparisonOperator.is_null. On a production graph, 52.7% of the top 24 fact-search candidates were invalidated facts crowding out live ones. temporal_mode='current' exposes the existing core capability while preserving default behavior.
cff7ebf to
857deb9
Compare
linhongyu510
left a comment
There was a problem hiding this comment.
Blocking temporal-semantics gap: temporal_mode="current" is documented as returning facts that are currently true, but the implementation requires both invalid_at IS NULL and expired_at IS NULL and does not bound valid_at. Graphiti itself describes facts as valid between valid_at and invalid_at (graphiti_core/search/search_helpers.py), so a fact whose invalid_at is tomorrow is true now yet is dropped, while a fact whose valid_at is tomorrow and invalid_at is null is admitted. The helper tests only compare the constructed SearchFilters, so they lock in this mismatch rather than exercise boundary behavior. Please implement as-of-now interval semantics (including null endpoints), or rename/document the mode as active/open-ended history if that narrower behavior is intentional, and add regressions for future valid_at and future invalid_at. Independent validation at 857deb9c: mcp_server/tests/test_core_parity.py 32 passed; Ruff check/format and git diff --check passed.
Use interval-aware current fact filtering, preserve explicit valid-time bounds, and prevent date filter parameter collisions across OR groups.
|
Thanks @linhongyu510 — the interval-semantics gap is addressed in
Explicit While adding the boundary regressions, I also found and fixed an existing core query-construction issue: dated filters in separate outer OR groups reused the same parameter name and overwrote one another. Parameters are now unique across OR groups for Regression coverage now includes future Local verification:
|
Summary
Graphiti's bi-temporal model invalidates facts instead of deleting them ("Query what's true now, or what was true at any point in time"). The MCP
search_memory_factstool exposed date ranges but had no way to request facts that are true now.This PR adds an opt-in
temporal_modeparameter:temporal_mode="current"applies as-of-now interval semantics:(valid_at <= now OR valid_at IS NULL)(invalid_at > now OR invalid_at IS NULL)expired_at IS NULLvalid_at_after/valid_at_beforebounds further restrict the current interval; a future upper bound is capped at the internally captured UTCnowtemporal_mode="all"or omitted preserves the existing full-history behavior"current"combined withinvalid_at_after/invalid_at_beforeraisesValueErrorThe reference instant is captured internally with timezone-aware UTC; no additional MCP argument is exposed.
Motivation (measured)
On a production graph (78,515 edges, 60 real user queries, top-24 candidates each): 52.7% of fact-search candidates were invalidated facts (per-turn median 54%, p90 71%). Clients that only want currently true facts can otherwise have historical facts crowd live ones out of the candidate window.
Changes
mcp_server/src/utils/type_config.py: build the as-of-now valid-time and transaction-time filters fortemporal_mode="current"mcp_server/src/graphiti_mcp_server.py: expose and accurately document the modegraphiti_core/search/search_filters.py: give every dated condition a unique parameter across outer OR groups forvalid_at,invalid_at,created_at, andexpired_atmcp_server/tests/test_core_parity.py: cover futurevalid_at, futureinvalid_at, null endpoints, explicit range intersection, and UTC cappingtests/utils/search/test_search_filters.py: cover the parameter-collision regression across all four date fieldsDefault result semantics remain unchanged for callers that omit
temporal_mode. The core query parameter names are internal and now remain unique for multi-group filters.Testing
uv run pytest tests/test_core_parity.py -q→ 33 passedpyright ./graphiti_core→ 0 errorsgit diff --check→ passed