Prism Tri-Graph Knowledge Representation
Architectural overview of the Canonical / Semantic / Temporal graph layers introduced by SPEC-020 (Plan #6, shipped 2026-04-19/20). Audience: external collaborators + future agents working on Prism.
1. Why
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. Frank’s directive: “close to
perfection, perform like a bat out of hell.” That’s a precision target
the old graph couldn’t hit structurally.
SPEC-020 adopts Frank’s Self-Discovering Ontology (SDO) principle from a
prior domain-discovery project: three distinct reasoning modes require
three distinct graph structures, and conflating them corrupts each.
2. The Three Layers
Each layer answers a different question:- Canonical — “what is this and what kind of thing is it?”
- Semantic — “what is this similar to?”
- Temporal — “what did this look like at time T?”
2.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})— the type ontology. System-zone types (Project,Persona,Session) apply to every Prism project. Project-zone types depend on the 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 (name, title, status, etc.) live on:EntityState, not here.
(:Entity)-[:INSTANCE_OF]->(:Type)— every entity has exactly one.
:Entity.uuidunique per(tenant_id, namespace).:EntityMUST NOT carry any property outside the whitelist (enforced at the application layer; Neo4j Community can’t enforce this directly).
2.2 Semantic — Aliases + Typed References
Meaning. Which surface forms refer to the same entity, and how distinct entities relate. Edges:(:Entity)-[:ALIAS_OF {surface_form, authority_tier, source_memory_id}]->(:Entity)— the target is the preferred/canonical form. Directional: retrieval always resolves toward the preferred form. “PrismGR” aliases to the Prism Project entity.(:Entity)-[:REFERENCES|:FORMALIZES|:DEPENDS_ON|:SPECIFIES|...]->(:Entity)— typed cross-entity relationships. The allowed set is declared by the Canonical layer:(:Type)-[:CAN_REFERENCE]->(:Type)edges constrain which type pairs can participate.
- Tier 1: human-direct (Frank files via
prism_alias). Writes directly. - Tier 2: agent-with-artifact (an ADR’s content implies an alias; the agent files it as part of authoring). Writes directly.
- Tier 3: pattern extractor (ambient memory scan detects a likely
alias). Lands in
alias_candidatesqueue for review.
2.3 Temporal — State Versions + Events
Change. How an entity’s mutable state evolves and what caused each change. Nodes:(:EntityState {uuid, entity_uuid, valid_from, valid_until, commit_status, source_memory_id, <domain props>})— one state per entity per time interval. Domain props (name, title, status, etc.) live here.valid_until=NULLmeans “still current.”
(:Entity)-[:HAS_STATE]->(:EntityState)— entity to each of its state versions.(:EntityState)-[:SUPERSEDED_BY {event_type, at, cause_memory_id, cause_commit, event_description, props_drift}]->(:EntityState)— state transitions as first-class events.event_typedescribes the nature (“rename”, “status_transition”, “mode_change”, “wip_sealed”),cause_memory_idpins the supersession to a memory row for provenance.
- At most one
:EntityStateper entity withvalid_until=NULLandcommit_status='committed'(the sealed current state). - At most one
:EntityStateper entity withvalid_until=NULLandcommit_status='wip'(the WIP current state per v1.3). - New state’s
valid_fromequals prior state’svalid_until— no gaps, no overlaps. - Supersession chain is acyclic.
3. Computed Current-State (not stored)
Per Q1 resolution, the current state of an entity is computed by walking:HAS_STATE filtered by valid_until IS NULL, not stored as
a direct :HAS_CURRENT_STATE edge.
Rationale: the invariant “at most one sealed current state” is enforced
by the data, not by careful code maintenance. An index on
(entity_uuid, valid_until) makes the lookup O(1).
4. WIP (Work-In-Progress) States per v1.3
A conversational/authorial state for exploration that hasn’t crystallized yet. An agent noticing intent signals (“let’s noodle on the cypher shape”, “draft a response”, extended open-ended reasoning without convergence) SHOULD callprism_wip proactively — asymmetric cost:
- Missing an intent signal loses exploration.
- False-positive WIP seals cheaply when the work converges.
prism_wip(entity_uuid, props, source={session_intent|working_tree|explicit})
— creates or updates the single WIP state for the entity. Sealed + WIP
coexist; retrieval returns the WIP as current (with tag [wip]) when
prefer_wip=True.
Sealing: prism_seal(wip_state_uuid, sealing_event, props_at_seal)
closes the WIP, opens a sealed state, and creates a
:SUPERSEDED_BY {event_type="wip_sealed", props_drift: bool} edge.
props_drift=true means the final sealed props differed from the
in-flight WIP props — useful for auditing where reasoning changed the
final decision.
Stale-WIP detection: WIP states with touched_at older than 30 days
surface in prism_start.rules_reminders with category stale_wip.
5. Retrieval — Three-Phase Annotation
semantic_recall gains an optional as_of: datetime | None = None
parameter plus two response fields per hit:
-
Phase 1 — Query entity resolution. Scan query text for entity
mentions via two paths:
:ALIAS_OFsurface-form matching (walks the transitive chain to the canonical entity) and state-prop direct matching (hits entities whose current state’s name/title/pid/fqdn appears in the query). Produces a set of anchor entity UUIDs. -
Phase 2 — Temporal resolution. For each anchor, fetch the state
valid at
as_of(or current, WIP-preferred per v1.3 flag). Include full state history and alias surface_forms for annotation context. -
Phase 3 — Memory recall + truth-anchor annotation. Run the
4-leg RRF (vector + lexical + graph + temporal) and post-process each
hit: match its content against the anchor’s identifier surface forms
(longest match wins). Tag the hit
current/historical/wipaccordingly. Alias surface forms are historical by construction (an alias exists because the form is non-canonical). The graph leg traverses the Semantic-layer typed edges (:REFERENCESand the subtypes:IMPLEMENTS/:EXTENDS/:RELATES_TO/:SUPERSEDES/:DEPENDS_ON/:SPECIFIES/:FORMALIZES/:RESOLVES/:DOCUMENTS) when the query mentionsSPEC-NorADR-Ntokens, and falls back to the legacy:MENTIONSco-occurrence path otherwise. The temporal leg scores each candidate byexp(-age_days / half_life)withhalf_life=180d— a default-on recency-decay re-weight over the existing fused candidate set.
state_flag=None. Existing callers see
no behavior change.
6. New MCP Verbs
Canonical / Types / Entities
Semantic / Aliases / References / Review Queue
Temporal / State Changes / WIP
Retrieval
semantic_recall gains as_of: str | None = None + prefer_wip: bool = True.
7. How Plan #6 Shipped
Six waves, all non-breaking, all deployed on server1:- Wave A — Foundation: Postgres review-queue tables + Neo4j schema constraints / indexes. Nothing reads or writes new schema yet.
- Wave B — Canonical: Type seeding from JSON data source,
prism_entityverb, invariant validation. - Wave C — Semantic + Temporal: aliases, references, state transitions, WIP/seal. The biggest wave.
- Wave D — Retrieval:
as_ofparameter, three-phase pipeline, truth-anchor annotation. Fail-soft so existing callers are unaffected. - Wave E — Migration: host-side
mcp/trigraph_migrate.pyseeds every PID-PGR01 artifact as a tri-graph entity withvalid_from=row.created_at. Also registers the known “PrismGR” / “prismgr” aliases (tier 1) and extracts:REFERENCESedges from ADR/SPEC/Plan bodies. Idempotent. - Wave F — Validation: complete 25-test smoke battery, stale-WIP
detection surfaces in
prism_start.rules_reminders, live demo per SPEC-020 §13 passing on server1.
8. What’s Deferred
- Phase 8 — drop old schema: remove
:Memory/:Concept/:MENTIONSlabels and their associated code. Gated on one release-cycle of Phase 7 green.
8.1 Phase 5 graph-leg rewire — shipped 2026-05-03
The graph leg now traverses the Semantic layer for entity-anchored queries (PR #1026771f46, Plan #8 Phase 4): when the query mentions
SPEC-N or ADR-N tokens it 1-hop bidirectional-walks
:REFERENCES + :IMPLEMENTS + :EXTENDS + :RELATES_TO +
:SUPERSEDES + :DEPENDS_ON + :SPECIFIES + :FORMALIZES +
:RESOLVES + :DOCUMENTS edges and sums per-target edge counts.
Queries without entity anchors fall back to the legacy :MENTIONS
co-occurrence path so existing callers see no behavior change. Edge
auto-extraction (PR #101 c9bb9b4) populates the typed edges from
spec / ADR / plan / retro / journal markdown headings on every write.
The temporal leg activated as a fourth RRF fuse leg the same day
(PR #103 99561fb, Plan #8 Phase 5): exp(-age_days / half_life) with
half_life=180d re-weights the existing fused candidate set rather
than adding new candidates, so recall composition is unchanged but
recent-but-relevant memories rank higher on historical-context queries.
9. Performance
Plan #6 shipped zero user-visible regression across all six waves:
Annotation overhead on recall is ~0 when no tri-graph anchors match the
query (the most common case pre-Wave-E). Post-Wave-E, server1 carries
199 entities and annotation adds one Cypher round-trip (~5ms) per call.
10. Related Artifacts
- SPEC-020 — frozen architecture spec (filed as memory deltas
56e279acv1.0 +4c910dd8v1.2 +0ddd894bv1.3, Q1-Q6 resolutions5cae8334, formally re-filed via prism_spec asfbb9cb51-e535-43b1-903d-af56f4e9aac2on 2026-04-21). - SPEC-021 (Ring 0) — builds on the tri-graph + ring chain with a bootstrap-enforcement layer that sits outside Ring 1 BIOS. The tri-graph’s identity-stability guarantee (a project UUID survives renames) is what lets Ring 0’s L4 hygiene layer safely rewrite cached summaries when a project renames or advances phase — the identity the summary refers to is stable even if the label changes. See SPEC-021 §4.4 for the wrap-time update payload shape.
- ADR-23 — formal “Adopt tri-graph knowledge representation” ADR. Promotes the original tri-graph acceptance memory (delta 966082d0, initially filed as “ADR-22” in-memory) to the formal ADR table.
- Plan #6 — the six-wave execution plan (filed in plans table). The six-wave pattern — old schema and new schema coexist through every intermediate wave, never a hard cutover — graduated from this plan to a global methodology lesson via retro-005: any architectural refactor that touches a queryable surface should use a coexistence-not-replacement rollout when the old surface has live consumers. Applies to SPEC-024’s retirement migration and any future schema-shape changes.
- Retro #5 —
retros/retro-005-plan-6-spec-020-tri-graph-knowledge-representation. Source of the six-wave-coexistence global lesson; also captures the authority-tier model for agent-written aliases (tier 1 human-direct, tier 2 agent-with-artifact, tier 3 pattern-extractor-candidate). - Commits:
eaad16e→4c66446→ef3f502→bd52dcf→d33b75b→df8e50e.

