Skip to main content
Status: accepted · Version 1.0 · Filed 2026-04-21

Title: Tri-Graph Knowledge Representation (Canonical / Semantic / Temporal)

Status

accepted (shipped via Plan #6, 2026-04-19/20) — retroactive formal filing per governance discipline

Context

This spec is being filed retroactively. SPEC-020 was drafted, reviewed, and shipped entirely through memory deltas (56e279ac body, 4c910dd8 v1.2 addendum, 0ddd894b v1.3 addendum, 5cae8334 Q1-Q6 resolutions) and an accepting ADR (ADR-23, id ccab2b8d). It was implemented across Plan #6’s six waves and retroed in Retro #5. At no point was it filed through prism_spec(action=create) to the formal specs table, so it doesn’t appear in docs/specs/ or the Mintlify export. This filing closes that gap. Authoritative source-of-truth for spec bodies is the memory delta chain above; this filing consolidates the key content for queryability via prism_spec(action=list) and for export pipeline discovery.

Problem

Before the tri-graph, Prism’s Neo4j graph was a single-layer (:Memory)-[:MENTIONS]->(:Concept) co-occurrence index. That structure:
  • Conflated identity with current-state (a project “is” its name, so renaming breaks identity).
  • Conflated aliases (surface-form variants: “PrismGR” = old name for “Prism”) with references (typed relationships: ADR-19 FORMALIZES SPEC-019).
  • Had no notion of time — no way to ask “what was true as of last month” or distinguish a stale fact from a current one.
Observable failure: semantic_recall("PrismGR installation") returned historical memories with the same confidence as current-state memories, with no way to tell which was which.

Decision

Replace the single-layer graph with a three-layer architecture:

Layer 1 — Canonical (identity + type ontology)

Stable facts about what kinds of things exist and who each thing is. Nodes:
  • (:Type {zone, name, ptype, immutable_props, mutable_props}) — type ontology. System-zone types (Project, Persona, Session) apply to every Prism project. Project-zone types depend on ptype (application declares ADR, SPEC, Plan, TODO, Delta, Journal, Retro, Component, EnvVar, Location, Mode).
  • (:Entity {uuid, type_name, tenant_id, namespace, created_at, source_memory_id}) — identity registry. Carries only metadata. Domain properties live on :EntityState.
Edges:
  • (:Entity)-[:INSTANCE_OF]->(:Type) — every entity has exactly one.
Invariants:
  • :Entity.uuid unique per (tenant_id, namespace).
  • :Entity MUST NOT carry any domain property (enforced at application layer).

Layer 2 — Semantic (aliases + typed references)

Which surface forms refer to the same entity, and how distinct entities relate. Edges:
  • (:Entity)-[:ALIAS_OF {surface_form, authority_tier}]->(:Entity) — alias resolution chain. Authority tiers: 1 (human-asserted), 2 (system-derived), 3 (pattern-extracted candidate, requires review).
  • (:Entity)-[:REFERENCES|:FORMALIZES|:DEPENDS_ON|etc.]->(:Entity) — typed reference edges, constrained by :CAN_REFERENCE declarations on Types.

Layer 3 — Temporal (state versions + supersession events)

Every change in state is a new :EntityState node linked to the prior one via a typed :SUPERSEDED_BY event. Nodes:
  • (:EntityState {valid_from, valid_until, commit_status, props...}) — state version. valid_until=NULL means current. commit_status ∈ {wip, committed} per v1.3.
Edges:
  • (:Entity)-[:HAS_STATE]->(:EntityState) — state pointer.
  • (:EntityState)-[:SUPERSEDED_BY {event_type, cause_memory_id, cause_commit}]->(:EntityState) — supersession event with typed causality.
Invariants:
  • Current-state pointer is computed by traversal (valid_until IS NULL), never stored.
  • At most one WIP state per entity.
  • Supersession chains are acyclic (enforced at write time).

Verbs introduced

  • prism_entity(type_name, initial_props, source_memory_id) — create canonical entity + initial state.
  • prism_alias(surface_form, canonical_uuid, authority_tier) — register alias.
  • prism_reference(source_uuid, target_uuid, reference_type, source_memory_id) — typed reference edge.
  • prism_fact(subject_uuid, predicate, value, valid_from, source_memory_id) — single-property state change.
  • prism_transition(subject_uuid, props, event_type, cause_memory_id) — multi-property ACID state transition.
  • prism_wip(entity_uuid, props, source, source_ref, supersedes_commit) — declare WIP state.
  • prism_seal(wip_state_uuid, sealing_event, props_at_seal) — close WIP, open sealed state.
  • prism_state_as_of(entity_uuid, as_of=None) — query state at a point in time.
  • prism_review_fact_candidate(action, candidate_id, edits) — promote / reject tier-3 candidate.
  • semantic_recall gains as_of: datetime | None parameter.

Rationale

Three distinct reasoning modes require three distinct graph structures. Conflating them corrupts each:
  • Identity reasoning (“are these two references to the same thing?”) wants stable UUIDs and alias chains — the Canonical + Semantic layers.
  • Structural reasoning (“what formalizes what, what depends on what?”) wants typed references — the Semantic layer.
  • Temporal reasoning (“what was true as of T, what changed, why?”) wants versioned state with typed supersession — the Temporal layer.
The separation rule: canonical identity never carries mutable props; mutable state lives on temporal version nodes; the pointer from canonical to “current state” is computed by traversal, never stored. This delivers entity identity surviving renames, aliases resolving at retrieval time, point-in-time queries as first-class, queryable causality, and a graph leg in hybrid RAG that finally earns its keep.

Alternatives considered

  • Recency decay on RRF fusion. Theater on short-horizon projects — discriminates nothing when all facts are within one week. Rejected (TODO #77).
  • Postgres facts table (bitemporal rows). Works but confines event causality to joins, loses graph-native traversal. SQL-shaped solution to graph-shaped problem. Rejected (TODO #79).
  • Ad-hoc :SUPERSEDES edges on existing Concept graph. Treats symptom (renames) not disease (no temporal model). Subset of tri-graph, not replacement. Rejected (TODO #78).
  • Per-ptype hardcoded ontologies. Would work for application ptype but fail for research / travel. Tri-graph’s ptype-declarative Canonical mechanism generalizes.
  • Keep current graph + smarter ingestion heuristics. Does not address identity stability across renames, no point-in-time queries, no alias / reference separation.

Migration and exit

Shipped 2026-04-19/20 across Plan #6 Waves A-F:
  • Wave A — parallel schema install (non-breaking; old and new coexist).
  • Wave B — Canonical verbs (prism_entity, Type seeding from templates).
  • Wave C — Semantic (prism_alias, prism_reference) + Temporal (prism_fact, prism_transition, prism_wip, prism_seal).
  • Wave D — Retrieval: as_of on semantic_recall, three-phase query resolution.
  • Wave E — Migration: Tier 0 (git tags), Tier 1 (between-tag commits), Tier 2 (memory drift → candidates), known aliases (“PrismGR” → Project).
  • Wave F — Validation (25/25 smoke tests green, zero aggregate perf regression) + old schema deprecation gated on one release-cycle soak.
Final state on PID-PGR01 server1: 199 :Entity nodes, 46 typed reference edges, all 25 smoke tests green, p50 within budget.

Open follow-ups

  • SPEC-018 (git as continuous temporal feed via hooks) — deferred follow-up.
  • Backfill of pre-tri-graph memory_embeddings with extracted facts — explicit scope creep, deferred.
  • Cross-ptype retrieval (“Paris” disambiguation across travel + research ptypes) — future work.
  • Multi-tenant fact conflict resolution — future work.

References

  • ADR-23 (accepting decision, id ccab2b8d-97d2-4c5e-8c05-e59a93c0ae85)
  • Plan #6 (execution plan, status complete)
  • Retro #5 (Plan #6 retrospective, 8 global + 4 project lessons)
  • Memory deltas: 56e279ac (main body), 4c910dd8 (v1.2 migration strategy), 0ddd894b (v1.3 WIP/intent), 5cae8334 (Q1-Q6 resolutions)
  • docs/TRIGRAPH.md — external-facing architecture overview
Status: accepted
Last modified on April 22, 2026