ADR-0014: Native Bi-Temporal Graph (Drop Graphiti)
Date: 2026-03-02
Status: Accepted
Supersedes: docs/temporal-queries-prd.md (Graphiti proposal)
Context
The temporal-queries PRD proposed using Zep's Graphiti engine as a temporal knowledge graph layer for tracking ontology deltas over time. After comprehensive research into Graphiti's architecture, API, and known issues, we identified critical blockers for compliance use.
Decision
Do not adopt Graphiti. Extend existing graph_service.py with native bi-temporal properties on Neo4j.
Rationale
Why Graphiti is unsuitable for compliance data
-
LLM dependency on ALL writes:
graph.add_fact_triple(),graph.add_episode(), and all mutation operations use LLM for entity extraction, contradiction detection, and edge creation. This is non-deterministic — the same input can produce different graph structures on different runs. Audit-grade compliance data requires deterministic writes. -
Known bugs: Custom database names, edge attribute handling, and label management have documented issues as of March 2026.
-
Architectural mismatch: The PRD frames Graphiti as tracking "ontology snapshot deltas" (comparing JSON snapshots over time). Trust Relay's architecture is investigation-driven — facts come from multiple sources (KBO, NBB, Gazette, OSINT) with temporal metadata from the sources themselves, not from re-running queries and diffing results.
Why native bi-temporal is the right approach
-
Already ~80% implemented:
graph_service.pyalready hasvalid_at/invalid_atonHAS_DIRECTORandHAS_UBOrelationships,get_entity_state_at_date()for point-in-time queries, andget_node_timeline()/get_entity_timeline()for event streams. -
~200-300 lines to complete: Add
created_at/expired_atsystem-time to all nodes and relationships, implement deterministic invalidation logic, and ensure all ETL steps populate temporal properties. -
Full determinism: All temporal mutations are rule-based (e.g., "director not in KBO active list → set
invalid_at"). No LLM involvement in fact storage. -
No new dependency: Stays on existing Neo4j infrastructure (already in docker-compose with APOC).
Consequences
- No Graphiti dependency added to docker-compose or Python requirements
- Temporal queries served by existing Neo4j Cypher with existing methods
- All temporal data writes are deterministic and auditable
- Phoenix company detection (existing Cypher) becomes functional once
invalid_atis populated - Future: if Graphiti adds a deterministic write mode, it could be reconsidered for non-compliance use cases (e.g., agent conversational memory)
Bi-Temporal Model
Four timestamps per relationship:
| Property | Type | Meaning |
|---|---|---|
valid_at | ISO datetime | When fact became true in reality (from source data) |
invalid_at | ISO datetime | When fact stopped being true in reality (from source data) |
created_at | ISO datetime | When our system first recorded this fact |
expired_at | ISO datetime | When our system superseded or retracted this fact |
Two timestamps per node:
| Property | Type | Meaning |
|---|---|---|
created_at | ISO datetime | When node was first created in our system |
updated_at | ISO datetime | When node was last modified (already implemented) |