Skip to main content

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

  1. 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.

  2. Known bugs: Custom database names, edge attribute handling, and label management have documented issues as of March 2026.

  3. 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

  1. Already ~80% implemented: graph_service.py already has valid_at/invalid_at on HAS_DIRECTOR and HAS_UBO relationships, get_entity_state_at_date() for point-in-time queries, and get_node_timeline()/get_entity_timeline() for event streams.

  2. ~200-300 lines to complete: Add created_at/expired_at system-time to all nodes and relationships, implement deterministic invalidation logic, and ensure all ETL steps populate temporal properties.

  3. 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.

  4. 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_at is 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:

PropertyTypeMeaning
valid_atISO datetimeWhen fact became true in reality (from source data)
invalid_atISO datetimeWhen fact stopped being true in reality (from source data)
created_atISO datetimeWhen our system first recorded this fact
expired_atISO datetimeWhen our system superseded or retracted this fact

Two timestamps per node:

PropertyTypeMeaning
created_atISO datetimeWhen node was first created in our system
updated_atISO datetimeWhen node was last modified (already implemented)