Status:
accepted · Version 1.0 · Filed 2026-04-21Title: Tri-Graph Knowledge Representation (Canonical / Semantic / Temporal)
Status
accepted (shipped via Plan #6, 2026-04-19/20) — retroactive formal filing per governance disciplineContext
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 throughprism_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.
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 (applicationdeclaresADR,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.
(:Entity)-[:INSTANCE_OF]->(:Type)— every entity has exactly one.
:Entity.uuidunique per(tenant_id, namespace).:EntityMUST 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_REFERENCEdeclarations 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=NULLmeans current.commit_status ∈ {wip, committed}per v1.3.
(:Entity)-[:HAS_STATE]->(:EntityState)— state pointer.(:EntityState)-[:SUPERSEDED_BY {event_type, cause_memory_id, cause_commit}]->(:EntityState)— supersession event with typed causality.
- 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_recallgainsas_of: datetime | Noneparameter.
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.
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
:SUPERSEDESedges 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_ofonsemantic_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.
: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_embeddingswith 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

