Skip to main content
Status: complete · Version 1.1 · Filed 2026-04-19

SPEC-020 Tri-Graph Execution Plan

Owner: Donna (Claude Code) · Reviewer: Frank · Authoritative sources: SPEC-020 memory body (56e279ac), v1.2 addendum (4c910dd8), v1.3 addendum (0ddd894b), Q1-Q6 resolutions (5cae8334), ADR-22 (966082d0) No time estimates. The plan defines wave boundaries and exit criteria. Time taken is whatever it takes. Frank is running single-operator AI-native workflow with no coordination overhead; corporate-software timelines don’t apply.

Read-set for session open

Before starting, pull these via semantic_recall:
  1. "SPEC-020 tri-graph Canonical Semantic Temporal" — main spec body
  2. "SPEC-020 v1.2 git tags migration" — migration strategy addendum
  3. "SPEC-020 v1.3 WIP intent" — WIP verbs and intent triggers
  4. "SPEC-020 Q1 Q2 Q3 Q4 Q5 Q6 resolved" — settled decisions
  5. "ADR-22 tri-graph adopt" — the accepting decision
Then run prism_start to pick up project context and any rules_reminders.

On prereqs

There are no hard prereqs. Existing TODOs (#67 delta timeout, #72 localhost audit, #76 Lola filesystem mount) are independent work and do not block the tri-graph. If #67 bites during Wave E migration, fix it inline. Don’t clear the board before starting.

Wave A — Foundation

Phase 0: Parallel schema install
  • Alembic migration for fact_candidates table (review queue per Q2/Q4)
  • Alembic migration for alias_candidates table (tier 3 per Q2)
  • Neo4j schema additions: :Type, :Entity, :EntityState, :INSTANCE_OF, :ALIAS_OF, :HAS_STATE, :SUPERSEDED_BY, :CAN_REFERENCE, :CAN_ALIAS, :CAN_SUPERSEDE — all ADDITIONS alongside existing :Memory/:Concept/:MENTIONS
  • Neo4j constraints: unique on (:Entity {uuid}) per tenant+namespace; mandatory :INSTANCE_OF edge; composite index on (:EntityState.entity_uuid, valid_until) for computed current-state lookup (Q1)
  • Nothing reads or writes new schema yet; phase is non-breaking
Exit: docker compose up brings backend up clean; Neo4j shows new constraints; existing retrieval still works unchanged.

Wave B — Canonical layer

Phase 1: Type seeding and Entity creation
  • Parse templates/prism-base.md and templates/prism-application.md for declared types
    • System zone (prism-base.md): :Project, :Persona, :Session
    • Application zone (prism-application.md): :ADR, :SPEC, :Plan, :TODO, :Delta, :Journal, :Retro, :Component, :EnvVar, :Location, :Mode
    • :Location replaces :Host per Q5; application’s Location schema =
  • Template parsing produces :Type nodes with zone, ptype, immutable_props
  • Implement prism_entity(type_name, initial_props, source_memory_id):
    • Validates type exists in current project’s ptype
    • Validates initial_props against immutable_props schema
    • Creates :Entity with UUID
    • Creates initial :EntityState with valid_from=now, valid_until=NULL, commit_status="committed" for application’s initial seeding
    • :INSTANCE_OF and :HAS_STATE edges created atomically
  • App-layer validation rejects :Entity writes carrying mutable props
Exit: prism_entity creates entities correctly; invariant violations surface; smoke tests 1-4 pass.

Wave C — Semantic and Temporal (parallel)

Phases 2 and 3 are independent. Run them in parallel. Phase 2: Semantic layer
  • prism_alias(surface_form, canonical_uuid, authority_tier=1|2|3) — tier 1 and 2 direct, tier 3 to alias_candidates
  • :REFERENCES / :FORMALIZES / :DEPENDS_ON / etc. edges constrained by :CAN_REFERENCE declarations
  • Edge creation rejected when source/target type combination not permitted
  • Every edge carries source_memory_id and created_at
  • Extend ingestion concept extraction: type list as gazetteer (not just regex)
  • Pattern detection for alias candidates → tier 3
  • prism_review_facts(action, candidate_id, edits):
    • Q4 gating: promotion changing immutable_props → frank-decision tag + rules_reminders
    • Otherwise agent-promotable
Exit for Phase 2: alias traversal works; reference type enforcement active; review queue populates; smoke tests 5-8 pass. Phase 3: Temporal layer
  • :EntityState + :HAS_STATE
  • prism_fact(subject_uuid, predicate, value, valid_from=now, source_memory_id) — single-property convenience
  • prism_transition(subject_uuid, props={...}, event_type, cause_memory_id) — multi-property event per Q3
    • Atomic: closes prior current state, opens new, creates :SUPERSEDED_BY with event_type
    • One transaction; no NULL-overlaps
    • Rejects composite-of-composites event_types
  • prism_wip(entity_uuid, props, source, source_ref, supersedes_commit) per v1.3
    • One WIP per entity max
    • Update-in-place if WIP exists
  • prism_seal(wip_state_uuid, sealing_event, props_at_seal) — closes WIP, opens sealed, :SUPERSEDED_BY {event_type="wip_sealed", props_drift=<bool>}
  • State-change pattern extraction → fact_candidates tier 3
  • Q4 identity-change routing: immutable_prop changes → frank-decision + rules_reminders
Exit for Phase 3: transitions ACID-correct; WIP+sealed coexist per v1.3; chains acyclic; smoke tests 9-13 + 21-25 pass.

Wave D — Retrieval

Phase 4: as_of and three-phase retrieval
  • semantic_recall signature adds as_of: datetime | None = None
  • Phase 1 (query resolution): entity-mention detection → Canonical+Semantic traversal → anchor UUIDs
  • Phase 2 (temporal): computed traversal per Q1 — walk :HAS_STATE, filter valid_until IS NULL for current OR range-check for as_of; WIP-aware per v1.3
  • Phase 3 (memory recall): existing 3-leg RRF unchanged; post-process with truth anchors → boost current, tag [historical] for prior states, tag [wip] when truth anchor is WIP
  • Add wip_visibility and provenance fields to response
Exit for Phase 4: as_of=NULL returns current (sealed or WIP); as_of=<past> returns state valid at that point; historical memories tagged not filtered; smoke tests 14-18 pass; p50 regression < 10% of baseline 305ms. Phase 5: Graph leg rewiring
  • Replace (:Memory)-[:MENTIONS]->(:Concept) traversal with Canonical/Semantic/Temporal-aware walk
  • Start from query entities → walk Semantic references → find relevant states → find memories via source_memory_id chain
  • Backward compat: old graph leg behind feature flag during transition
Exit for Phase 5: graph leg score reflects full tri-graph relevance; retrieval quality measurably improved on known-answer probe set.

Wave E — Migration

Phase 6: Seed PID-PGR01 and register known aliases Migration runs with dry_run=True by default. Step 1 — Tier 0 (git tags, v1.2):
  • Parse annotated git tags
  • For each consecutive tag pair, git-diff for changed files
  • Map file paths to entity types per §9 mapping
  • Write supersessions: valid_from=T_n+1.date, event_type=classify(tag_message), cause_commit=T_n+1.sha
  • Lightweight tags skipped, counted in report
Step 2 — Tier 1 (between-tag commits):
  • For state changes falling between tags, commit is the event
  • Lower priority than tag events
Step 3 — Tier 2 (memory drift):
  • (subject, predicate) pairs with multiple memories → tier 3 candidates in fact_candidates
  • Frank promotes selectively
Step 4 — Known aliases:
  • “PrismGR” → Project (tier 1)
  • “personal” → “local” mode (pending tier 1; activates with TODO #74)
  • Other renames visible in ADR history
Step 5 — Reference extraction from ADR/SPEC bodies:
  • One-time pass over ADR+SPEC bodies; create :REFERENCES, :FORMALIZES edges
Report-first per Q6: migration emits Tier counts + coverage estimate; operator reviews; re-runs with dry_run=False. Exit for Phase 6: all PID-PGR01 entities have at least one sealed state; “PrismGR” alias resolves to Project; migration report looks reasonable.

Wave F — Validation and deprecation

Phase 7: Smoke battery
  • 25 tests in mcp/smoke_trigraph.py (20 from v1.0 §10 + 5 from v1.3)
  • Run on PID-PGR01 post-migration
  • Run on synthetic project for invariant testing
  • Performance regression: existing perf-probe p50 ≤ 335ms
Phase 8: Old schema deprecation
  • Only after Phase 7 green and stable through one release cycle
  • Drop :Memory, :Concept, :MENTIONS from Neo4j
  • Remove old graph-leg code
  • Postgres memory_embeddings untouched throughout
Exit for Wave F:
  • 25 smoke tests green
  • Live demo on PID-PGR01 per §13:
    • semantic_recall("what is this project called") → “Prism”, no historical tag
    • semantic_recall("PrismGR installation") → memories tagged [historical]
    • semantic_recall("server1 backend URL") → current URL
    • New renames via prism_fact reflect immediately
  • Provenance populated
  • p50 within budget

Methodology alignment

  • Each wave opens with prism_start, closes with a store_session_delta call
  • Non-trivial decisions → ADR if architectural, delta if tactical
  • Phase completion → mark corresponding TODO status on TODO #80
  • Implementation surprise contradicting spec → spec-feedback memory, don’t pause
  • Intent signals during implementation (e.g. “let’s noodle on the Cypher query shape”) → prism_wip once Temporal ships. Dog-food starting Wave D.

Risks

RiskMitigation
Neo4j constraint syntax gotchasPrototype constraints in throwaway Neo4j before Wave A; pin Neo4j version
Git-tag parsing misclassifies event typesConservative classifier + manual override per Q6
Tier 0 coverage on PID-PGR01 < 50%Acceptable; Tier 1 + review-queue cover gaps; document coverage in migration delta
WIP verb semantics surprise later usersShip Phase 3.5 methodology doc in prism-base.md before first non-PID-PGR01 project uses tri-graph
Retrieval p50 regression > 10%Profile graph-leg traversal first (likeliest culprit); consider stored current-state pointer per Q1 fallback

Explicitly NOT in this plan

  • Cross-ptype retrieval (“Paris” disambiguation)
  • Multi-tenant fact conflict resolution
  • SPEC-021 (git as continuous temporal feed via hooks) — follow-up
  • Backfill of all 192 existing memory_embeddings with extracted facts — scope creep
  • Neo4j-to-Neo4j replication for LAN mode — orthogonal

Completion trigger for TODO #80

TODO #80 closes when Wave F exits green. Also closes TODOs #77 (rejected), #78 (folded), #79 (superseded).

Changelog

  • v1.1 (2026-04-19): Removed all day-estimates. Removed prereq-clearing guidance — Frank flagged that #67/#72/#76 are independent work and don’t block this plan. Wave boundaries and exit criteria unchanged.
  • v1.0 (2026-04-19): Initial plan.
Last modified on April 22, 2026