Status:
complete · Version 1.1 · Filed 2026-04-19SPEC-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 viasemantic_recall:
"SPEC-020 tri-graph Canonical Semantic Temporal"— main spec body"SPEC-020 v1.2 git tags migration"— migration strategy addendum"SPEC-020 v1.3 WIP intent"— WIP verbs and intent triggers"SPEC-020 Q1 Q2 Q3 Q4 Q5 Q6 resolved"— settled decisions"ADR-22 tri-graph adopt"— the accepting decision
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_candidatestable (review queue per Q2/Q4) - Alembic migration for
alias_candidatestable (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_OFedge; 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
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.mdandtemplates/prism-application.mdfor 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 :Locationreplaces:Hostper Q5; application’s Location schema =
- System zone (prism-base.md):
- Template parsing produces
:Typenodes withzone,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
:Entitywith UUID - Creates initial
:EntityStatewithvalid_from=now,valid_until=NULL,commit_status="committed"for application’s initial seeding :INSTANCE_OFand:HAS_STATEedges created atomically
- App-layer validation rejects
:Entitywrites carrying mutable props
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 layerprism_alias(surface_form, canonical_uuid, authority_tier=1|2|3)— tier 1 and 2 direct, tier 3 toalias_candidates:REFERENCES/:FORMALIZES/:DEPENDS_ON/ etc. edges constrained by:CAN_REFERENCEdeclarations- Edge creation rejected when source/target type combination not permitted
- Every edge carries
source_memory_idandcreated_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-decisiontag + rules_reminders - Otherwise agent-promotable
- Q4 gating: promotion changing immutable_props →
:EntityState+:HAS_STATEprism_fact(subject_uuid, predicate, value, valid_from=now, source_memory_id)— single-property convenienceprism_transition(subject_uuid, props={...}, event_type, cause_memory_id)— multi-property event per Q3- Atomic: closes prior current state, opens new, creates
:SUPERSEDED_BYwith event_type - One transaction; no NULL-overlaps
- Rejects composite-of-composites event_types
- Atomic: closes prior current state, opens new, creates
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_candidatestier 3 - Q4 identity-change routing: immutable_prop changes →
frank-decision+ rules_reminders
Wave D — Retrieval
Phase 4: as_of and three-phase retrievalsemantic_recallsignature addsas_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, filtervalid_until IS NULLfor 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_visibilityandprovenancefields to response
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
Wave E — Migration
Phase 6: Seed PID-PGR01 and register known aliases Migration runs withdry_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
- For state changes falling between tags, commit is the event
- Lower priority than tag events
- (subject, predicate) pairs with multiple memories → tier 3 candidates in
fact_candidates - Frank promotes selectively
- “PrismGR” → Project (tier 1)
- “personal” → “local” mode (pending tier 1; activates with TODO #74)
- Other renames visible in ADR history
- One-time pass over ADR+SPEC bodies; create
:REFERENCES,:FORMALIZESedges
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
- Only after Phase 7 green and stable through one release cycle
- Drop
:Memory,:Concept,:MENTIONSfrom Neo4j - Remove old graph-leg code
- Postgres memory_embeddings untouched throughout
- 25 smoke tests green
- Live demo on PID-PGR01 per §13:
semantic_recall("what is this project called")→ “Prism”, no historical tagsemantic_recall("PrismGR installation")→ memories tagged[historical]semantic_recall("server1 backend URL")→ current URL- New renames via
prism_factreflect immediately
- Provenance populated
- p50 within budget
Methodology alignment
- Each wave opens with
prism_start, closes with astore_session_deltacall - Non-trivial decisions → ADR if architectural, delta if tactical
- Phase completion → mark corresponding TODO status on TODO #80
- Implementation surprise contradicting spec →
spec-feedbackmemory, don’t pause - Intent signals during implementation (e.g. “let’s noodle on the Cypher query shape”) →
prism_wiponce Temporal ships. Dog-food starting Wave D.
Risks
| Risk | Mitigation |
|---|---|
| Neo4j constraint syntax gotchas | Prototype constraints in throwaway Neo4j before Wave A; pin Neo4j version |
| Git-tag parsing misclassifies event types | Conservative 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 users | Ship 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.

