Stage 3: Expand + Master Plan -- AI-Generated Graph Topology
-- Step 1: Add updated_at column ALTER TABLE knowledge_graph ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
Full Public Reader
Stage 3: Expand + Master Plan -- AI-Generated Graph Topology
---
3a. Risk Audit
CRITICAL RISKS
#### Risk 1: Confidence Decay Destroys Valuable Low-Frequency Triples
- Failure scenario: The 3 `evolved_from` triples (accessed rarely but semantically
critical) decay below threshold and disappear from default queries within 90 days.
- Probability: HIGH (guaranteed by the decay formula if not addressed)
- Impact: HIGH (evolutionary lineage lost)
- Mitigation: Tier 1 predicates (including evolved_from) are EXEMPT from decay. Only
Tier 2 and Tier 3 predicates decay. This is enforced in the decay SQL:
`WHERE predicate NOT IN (SELECT name FROM predicate_registry WHERE tier = 1)`
- Validation: After first decay run, verify all Tier 1 triples retain original confidence.
Query: `SELECT count(*) FROM knowledge_graph WHERE predicate = 'evolved_from'` must = 3.
#### Risk 2: Pruning Breaks RAG++ Retrieval Quality
- Failure scenario: RAG++ searches that previously returned relevant results now return
empty because the connecting triples were pruned.
- Probability: MEDIUM (pruning targets low-confidence noise, but RAG++ may use noise
paths as fallback when high-signal paths are sparse)
- Impact: HIGH (RAG++ is the primary GK consumer)
- Mitigation: Three-layer defense:
1. Soft delete (pruned_at, not DELETE) -- reversible in seconds
2. Before pruning: run a test suite of 20 known-good RAG++ queries, record results
3. After pruning: re-run the same 20 queries, compare result overlap. If <80
rollback (NULL all pruned_at values)
- Validation: RAG++ test suite passes with >= 80
#### Risk 3: Entity Embedding Pipeline Produces Garbage Embeddings
- Failure scenario: Entities with no GK predicates (e.g., bare prompt hashes like
"prompt:1069f93fdd8f24c3") are embedded as meaningless vectors, creating noise clusters
that contaminate hierarchy discovery.
- Probability: MEDIUM (8,303 entities are prompt hashes with only contains_prompt edges)
- Impact: MEDIUM (garbage clusters waste effort but do not corrupt good clusters)
- Mitigation: Filter entity list BEFORE embedding. Only embed entities that:
1. Have at least 2 predicates (not counting contains_prompt, has_intent, ran_session)
2. Are not prefixed with "session:", "prompt:", or "agent:" unless they also have Tier 1/2 edges
Expected: ~2,000-3,000 embeddable entities out of 10,781 total.
- Validation: Embedding count < 4,000. No cluster contains >50
MEDIUM RISKS
#### Risk 4: Cluster Labeling Produces Vague Names
- Probability: MEDIUM
- Impact: LOW (labels are human-readable convenience, not machine-critical)
- Mitigation: Three-pass labeling: (1) auto-label from shared predicates, (2) Gemini for
mixed clusters, (3) manual override list for top-20 clusters.
#### Risk 5: Predicate Registry Cold Start Misclassifies Important Predicates
- Probability: LOW (information content is a strong signal)
- Impact: MEDIUM (misclassified Tier 1 predicate treated as Tier 3, low confidence cap)
- Mitigation: Seed the registry with a hardcoded list of 7 known Core predicates.
All others are classified by information content.
#### Risk 6: KARL Boost Creates Positive Feedback Loop
- Probability: LOW (rate-limited to 0.05/day/triple)
- Impact: MEDIUM (frequently-used triples dominate, rare triples starve)
- Mitigation: Confidence ceiling of 0.99 (never reaches 1.0). Decay counterbalances boost.
Net equilibrium: actively-used triples stabilize at ~0.85-0.95, unused decay to threshold.
LOW RISKS
#### Risk 7: SQL Migration Downtime
- Probability: LOW (ALTER TABLE ADD COLUMN is non-blocking in PostgreSQL)
- Impact: LOW (GK is non-critical for <1 minute)
- Mitigation: Run migration during low-traffic period. Test on SQLite first.
#### Risk 8: Predicate similar_to Explodes Triple Count
- Probability: LOW (similarity threshold 0.7 limits pairs)
- Impact: LOW (estimated ~2,000 new triples on a 35K-triple graph)
- Mitigation: Hard cap: max 5 similar_to triples per entity. If >5, keep top-5 by score.
---
3b. Expanded Specifications
Spec 1: SQL Migration Script
-- Migration: v1.1.0 -- Confidence Evolution
-- Run on cloud-vm PostgreSQL
-- Step 1: Add updated_at column
ALTER TABLE knowledge_graph
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
-- Backfill: set updated_at = created_at for existing rows
UPDATE knowledge_graph SET updated_at = created_at WHERE updated_at IS NULL;
-- Step 2: Add soft delete columns
ALTER TABLE knowledge_graph
ADD COLUMN IF NOT EXISTS pruned_at TIMESTAMPTZ DEFAULT NULL;
ALTER TABLE knowledge_graph
ADD COLUMN IF NOT EXISTS pruned_reason TEXT DEFAULT NULL;
-- Step 3: Partial index for active (non-pruned) triples
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_knowledge_active
ON knowledge_graph (subject, predicate, object)
WHERE pruned_at IS NULL;
-- Step 4: Index for decay queries (find stale triples)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_knowledge_updated
ON knowledge_graph (updated_at)
WHERE pruned_at IS NULL;
-- Step 5: Predicate registry table
CREATE TABLE IF NOT EXISTS predicate_registry (
name TEXT PRIMARY KEY,
tier INTEGER NOT NULL DEFAULT 3, -- 1=Core, 2=Semi, 3=Freeform
usage_count BIGINT NOT NULL DEFAULT 0,
avg_confidence FLOAT NOT NULL DEFAULT 0.5,
std_confidence FLOAT NOT NULL DEFAULT 0.0,
information_content FLOAT NOT NULL DEFAULT 0.0,
computed_threshold FLOAT NOT NULL DEFAULT 0.3,
promoted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Step 6: Update upsert to touch updated_at
-- This is handled in Rust code change, not SQL.
-- The ON CONFLICT clause becomes:
-- ON CONFLICT (subject, predicate, object) DO UPDATE SET
-- confidence = GREATEST(knowledge_graph.confidence, EXCLUDED.confidence),
-- source = EXCLUDED.source,
-- updated_at = NOW()Spec 2: Confidence Decay Module (Rust)
// File: src/decay.rs (~100 lines)
use chrono::{DateTime, Utc, Duration};
pub struct DecayConfig {
pub half_life_days: f64, // Default: 90.0
pub min_confidence: f64, // Default: 0.05 (below this = prune candidate)
pub exempt_predicates: Vec<String>, // Tier 1 predicates, no decay
pub exempt_sources: Vec<String>, // e.g., "manual", "human-curated"
}
impl Default for DecayConfig {
fn default() -> Self {
Self {
half_life_days: 90.0,
min_confidence: 0.05,
exempt_predicates: vec![
"is_a".into(), "part_of".into(), "evolved_from".into(),
"works_on".into(), "built_with".into(), "deployed_on".into(),
"depends_on".into(),
],
exempt_sources: vec!["manual".into(), "human-curated".into()],
}
}
}
pub fn compute_decayed_confidence(
current_confidence: f64,
updated_at: DateTime<Utc>,
now: DateTime<Utc>,
config: &DecayConfig,
) -> f64 {
let days_since_update = (now - updated_at).num_seconds() as f64 / 86400.0;
if days_since_update <= 0.0 {
return current_confidence;
}
let decay_factor = 0.5_f64.powf(days_since_update / config.half_life_days);
let decayed = current_confidence * decay_factor;
decayed.max(config.min_confidence)
}
// SQL for batch decay (called from endpoint):
// UPDATE knowledge_graph
// SET confidence = confidence * POWER(0.5, EXTRACT(EPOCH FROM (NOW() - updated_at)) / 86400.0 / $1),
// source = 'decay:' || source
// WHERE pruned_at IS NULL
// AND predicate NOT IN (SELECT name FROM predicate_registry WHERE tier = 1)
// AND source NOT LIKE 'decay:%'
// AND updated_at < NOW() - INTERVAL '$2 days'Spec 3: Topology Metrics (Rust)
// File: src/topology.rs (~200 lines)
pub struct EntityMetrics {
pub entity: String,
pub in_degree: usize,
pub out_degree: usize,
pub total_degree: usize,
pub predicate_diversity: f64, // Shannon entropy
pub avg_confidence: f64,
pub min_confidence: f64,
pub staleness_days: f64,
pub isolation_score: f64, // 1.0 - (degree / max_degree)
}
pub struct PredicateMetrics {
pub predicate: String,
pub edge_count: usize,
pub avg_confidence: f64,
pub std_confidence: f64,
pub unique_subjects: usize,
pub unique_objects: usize,
pub density: f64, // edges / (subjects * objects)
pub information_content: f64, // -log2(edge_count / total_edges)
}
// SQL for entity metrics:
// SELECT subject as entity,
// COUNT(*) FILTER (WHERE subject = e) as out_degree,
// COUNT(*) FILTER (WHERE object = e) as in_degree,
// COUNT(DISTINCT predicate) as predicate_count,
// AVG(confidence) as avg_confidence,
// MIN(confidence) as min_confidence,
// EXTRACT(EPOCH FROM (NOW() - MAX(updated_at))) / 86400 as staleness_days
// FROM knowledge_graph
// WHERE pruned_at IS NULL AND (subject = $1 OR object = $1)
// GROUP BY entity
// SQL for predicate metrics:
// SELECT predicate,
// COUNT(*) as edge_count,
// AVG(confidence) as avg_confidence,
// STDDEV(confidence) as std_confidence,
// COUNT(DISTINCT subject) as unique_subjects,
// COUNT(DISTINCT object) as unique_objects,
// -LOG(2, COUNT(*)::float / (SELECT COUNT(*) FROM knowledge_graph WHERE pruned_at IS NULL)) as info_content
// FROM knowledge_graph
// WHERE pruned_at IS NULL
// GROUP BY predicate
// ORDER BY edge_count DESCSpec 4: Entity Embedding Pipeline (Python)
# File: flows/feed-hub/gk_entity_embeddings.py (~180 lines)
# Prefect flow, runs weekly on cloud-vm
import httpx
import pickle
from pathlib import Path
GK_URL = "http://localhost:8001"
RAG_EMBED_URL = "http://localhost:8000/api/rag/embed"
OUTPUT_PATH = Path("/home/mohameddiomande/gk_entity_embeddings.pkl")
EXCLUDE_PREFIXES = ["session:", "prompt:", "agent:"]
NOISE_PREDICATES = {"contains_prompt", "has_intent", "ran_session", "has_path"}
def fetch_embeddable_entities():
"""Fetch entities with >= 2 non-noise predicates."""
stats = httpx.get(f"{GK_URL}/api/knowledge/stats").json()
total = stats["total_triples"]
entities = {} # entity -> list of (predicate, object, confidence)
offset = 0
while offset < total:
resp = httpx.get(f"{GK_URL}/api/knowledge", params={"limit": 500}).json()
for t in resp["triples"]:
if t["predicate"] not in NOISE_PREDICATES:
entities.setdefault(t["subject"], []).append(
(t["predicate"], t["object"], t["confidence"])
)
offset += 500
if len(resp["triples"]) < 500:
break
# Filter: >= 2 non-noise predicates, no excluded prefixes
return {
e: preds for e, preds in entities.items()
if len(preds) >= 2 and not any(e.startswith(p) for p in EXCLUDE_PREFIXES)
}
def enrich_entity_text(entity, predicates):
"""Build context-rich text for embedding."""
parts = [entity]
for pred, obj, conf in sorted(predicates, key=lambda x: -x[2])[:10]:
parts.append(f"{pred}: {obj} ({conf:.1f})")
return ". ".join(parts)[:4000]
def embed_batch(texts, batch_size=50):
"""Embed texts via RAG++ endpoint."""
embeddings = {}
for i in range(0, len(texts), batch_size):
batch = list(texts.items())[i:i+batch_size]
for entity, text in batch:
resp = httpx.post(RAG_EMBED_URL, json={"text": text}, timeout=10)
if resp.status_code == 200:
emb = resp.json().get("embedding", [])
if len(emb) == 3072:
embeddings[entity] = emb
return embeddingsSpec 5: Hierarchy Discovery (Python)
# File: flows/feed-hub/gk_hierarchy_discovery.py (~200 lines)
# Runs after gk_entity_embeddings.py
import numpy as np
from scipy.cluster.hierarchy import fcluster, linkage
from scipy.spatial.distance import pdist
import httpx
def discover_hierarchy(embeddings, n_coarse=30, n_fine_per_coarse=5):
"""Two-level agglomerative clustering."""
entities = list(embeddings.keys())
vectors = np.array([embeddings[e] for e in entities])
# Level 1: Coarse clustering
linkage_matrix = linkage(pdist(vectors, metric='cosine'), method='ward')
coarse_labels = fcluster(linkage_matrix, t=n_coarse, criterion='maxclust')
# Level 2: Fine clustering within each coarse cluster
hierarchy = {} # {coarse_label: {fine_label: [entities]}}
for cluster_id in range(1, n_coarse + 1):
mask = coarse_labels == cluster_id
cluster_entities = [entities[i] for i, m in enumerate(mask) if m]
cluster_vectors = vectors[mask]
if len(cluster_entities) <= n_fine_per_coarse:
hierarchy[cluster_id] = {0: cluster_entities}
continue
fine_linkage = linkage(pdist(cluster_vectors, metric='cosine'), method='ward')
n_fine = min(n_fine_per_coarse, len(cluster_entities) // 2)
fine_labels = fcluster(fine_linkage, t=max(2, n_fine), criterion='maxclust')
fine_clusters = {}
for entity, label in zip(cluster_entities, fine_labels):
fine_clusters.setdefault(label, []).append(entity)
hierarchy[cluster_id] = fine_clusters
return hierarchy
def label_clusters(hierarchy, embeddings):
"""Label clusters using shared predicates + Gemini fallback."""
labels = {}
for coarse_id, fine_clusters in hierarchy.items():
coarse_entities = [e for fc in fine_clusters.values() for e in fc]
coarse_label = auto_label(coarse_entities) or gemini_label(coarse_entities)
labels[coarse_id] = coarse_label
for fine_id, entities in fine_clusters.items():
fine_label = auto_label(entities) or gemini_label(entities)
labels[(coarse_id, fine_id)] = fine_label
return labels
def auto_label(entities):
"""Try to label from shared GK predicates."""
# Fetch predicates for all entities
shared = None
for entity in entities[:10]:
resp = httpx.get(f"{GK_URL}/api/knowledge",
params={"subject": entity, "limit": 50}).json()
preds = {(t["predicate"], t["object"]) for t in resp["triples"]}
shared = preds if shared is None else shared & preds
if shared:
# Most common shared predicate-object pair
return max(shared, key=lambda x: x[1])[1] # Use the object as label
return None---
3c. Master Execution Checklist
Wave 0: Foundation (Day 1-2)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 0.1 | Run SQL migration on cloud-vm PostgreSQL | Claude | Migration script (Spec 1) | 3 new columns, 2 new indexes, 1 new table | `\d knowledge_graph` shows updated_at, pruned_at, pruned_reason columns | [ ] |
| 0.2 | Update Rust upsert to set updated_at = NOW() | Claude | knowledge_db_pg.rs | Modified upsert SQL | Insert a triple, verify updated_at != created_at on re-insert | [ ] |
| 0.3 | Backfill updated_at = created_at for all existing rows | Claude | SQL one-liner | 47,125 rows updated | `SELECT COUNT(*) WHERE updated_at IS NULL` = 0 | [ ] |
| 0.4 | Add pruned_at IS NULL filter to all queries | Claude | knowledge_db_pg.rs, knowledge_db_sqlite.rs | Modified WHERE clauses | Prune one triple manually, verify it disappears from default queries | [ ] |
Done gate: All columns exist, upsert touches updated_at, pruned triples are invisible.
Wave 1: Confidence Evolution (Day 3-5)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 1.1 | Implement decay module (src/decay.rs) | Claude | Spec 2 | New Rust module | Unit tests: decay after 90 days = 50 | |
| 1.2 | Add POST /api/knowledge/decay endpoint | Claude | decay.rs | New endpoint | curl POST returns {"decayed": N, "exempt": M} | [ ] |
| 1.3 | Modify traversal to use confidence product | Claude | knowledge_handlers.rs | Modified TraversalPath | Traversal returns path_confidence field, product not min | [ ] |
| 1.4 | Add per-predicate threshold computation | Claude | predicate_registry table | Computed thresholds | threshold = max(0.1, avg - 2*std) for each predicate | [ ] |
| 1.5 | Default queries use computed threshold | Claude | query_knowledge_handler | Modified WHERE clause | Query for "has_intent" auto-filters below 0.6 threshold | [ ] |
| 1.6 | Run first decay on production graph | Mohamed | Endpoint from 1.2 | Decayed triples | Before/after confidence distribution comparison | [ ] |
Done gate: Confidence decays, traversal uses products, queries use thresholds.
Wave 2: Topology Pruning (Day 6-8)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 2.1 | Implement topology metrics module (src/topology.rs) | Claude | Spec 3 | New Rust module | Unit tests for Shannon entropy, isolation score, info content | [ ] |
| 2.2 | Compute metrics for all entities and predicates | Claude | topology.rs | Metrics in memory / API | GET /api/knowledge/topology/metrics returns all predicate metrics | [ ] |
| 2.3 | Classify predicates into signal tiers | Claude | Predicate metrics | Updated predicate_registry | Core: 7 predicates, Semi: ~20, Freeform: ~2000 | [ ] |
| 2.4 | Run RAG++ baseline test suite (20 queries) | Mohamed | RAG++ endpoint | Baseline results saved | Results saved as JSON for comparison | [ ] |
| 2.5 | Apply Tier 1 automatic pruning | Claude | Pruning rules | Pruned triples (soft delete) | Count of pruned = ~12,000-15,000 | [ ] |
| 2.6 | Re-run RAG++ test suite, compare overlap | Mohamed | Baseline + post-prune results | Overlap percentage | Overlap >= 80 | |
| 2.7 | Add GET /api/knowledge/topology/health endpoint | Claude | topology.rs, metrics | New endpoint | Returns predicate tiers, prune counts, health score | [ ] |
Done gate: Graph pruned by 25-32
Wave 3: Predicate Registry (Day 9-10)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 3.1 | Seed predicate_registry with initial classification | Claude | Wave 2 metrics | Populated table | 7 Tier 1, ~20 Tier 2, rest Tier 3 | [ ] |
| 3.2 | Add confidence capping for Tier 3 predicates | Claude | knowledge_handlers.rs | Modified upsert | Freeform predicate insertion capped at 0.5 | [ ] |
| 3.3 | Add GET /api/knowledge/predicates endpoint | Claude | predicate_registry table | New endpoint | Returns all predicates with tier, usage, threshold | [ ] |
| 3.4 | Add promotion detection (flag candidates) | Claude | predicate_registry | Promotion flags | Tier 3 predicates with usage>30 + avg_conf>0.4 flagged | [ ] |
| 3.5 | Rebuild and deploy updated GK binary | Claude+Mohamed | All Wave 0-3 changes | New binary on cloud-vm | `cargo build --release`, systemd restart, health check passes | [ ] |
Done gate: Registry populated, confidence capped, predicates endpoint live.
Wave 4: Hierarchy Discovery (Day 11-15)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 4.1 | Build entity embedding pipeline (Python) | Claude | Spec 4 | gk_entity_embeddings.pkl | ~2,000-3,000 entities embedded, 3072 dims each | [ ] |
| 4.2 | Run hierarchy discovery (agglomerative clustering) | Claude | Spec 5 + embeddings | Hierarchy JSON | 20-40 coarse clusters, 3-8 fine per coarse | [ ] |
| 4.3 | Label clusters (auto + Gemini + manual override) | Claude+Mohamed | Hierarchy JSON | Labeled hierarchy | Top-20 clusters have meaningful names | [ ] |
| 4.4 | Ingest hierarchy as is_a / part_of triples | Claude | Labeled hierarchy | ~2,000-4,000 new triples in GK | GET /api/knowledge?predicate=is_a returns results | [ ] |
| 4.5 | Generate similar_to triples from embedding similarity | Claude | Embeddings | ~1,500-2,500 new triples | similar_to triples with confidence = cosine similarity | [ ] |
| 4.6 | Register is_a, part_of, similar_to in predicate registry | Claude | predicate_registry | Updated registry | is_a and part_of are Tier 1, similar_to is Tier 2 | [ ] |
| 4.7 | Create Prefect weekly re-discovery flow | Claude | Specs 4+5 | New flow file | Flow registered in Prefect, runs Sundays at 4am | [ ] |
Done gate: Hierarchy exists in GK, similar_to connections discovered, weekly refresh scheduled.
Wave 5: KARL Integration + Query Planner (Day 16-19)
| ID | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 5.1 | Add report_to_gk() in KARL trajectory_tap.py | Claude | KARL codebase | Modified flush_session | On reward>0.7, entities boosted in GK | [ ] |
| 5.2 | Rate-limit KARL boost (0.05/day/triple) | Claude | report_to_gk() | Rate limiting logic | Same triple boosted twice in a day: second boost rejected | [ ] |
| 5.3 | Add explain mode to traversal endpoint | Claude | knowledge_handlers.rs | explain field in response | Traversal with explain=true returns plan object | [ ] |
| 5.4 | Add Prometheus metrics for graph health | Claude | topology.rs | Metrics endpoint | gk_total_triples, gk_pruned_triples, gk_avg_confidence, gk_predicate_count, gk_decay_runs_total | [ ] |
| 5.5 | Nexus Portal: add /graph-health page | Claude | Nexus codebase | New dashboard page | Shows predicate tiers, pruning stats, confidence distribution | [ ] |
| 5.6 | End-to-end validation: full traversal test | Mohamed | All waves complete | Test report | 10 traversals return confidence-weighted, hierarchy-aware results | [ ] |
Done gate: KARL feedback live, explain mode works, Prometheus metrics exported, Nexus shows health.
---
Prometheus Metrics
| Metric | Type | Description |
|---|---|---|
| `gk_total_triples` | Gauge | Total non-pruned triples |
| `gk_pruned_triples` | Gauge | Soft-deleted triples |
| `gk_avg_confidence` | Gauge | Mean confidence of active triples |
| `gk_predicate_count_by_tier` | Gauge (labeled) | Predicates per tier |
| `gk_decay_runs_total` | Counter | Number of decay runs executed |
| `gk_decay_triples_affected` | Counter | Triples affected by decay |
| `gk_karl_boosts_total` | Counter | KARL confidence boosts applied |
| `gk_hierarchy_entities` | Gauge | Entities with is_a/part_of triples |
| `gk_similar_pairs` | Gauge | Entity pairs connected by similar_to |
| `gk_traversal_explain_ms` | Histogram | Traversal time with explain mode |
---
Kill Criteria
1. After Wave 2: If RAG++ quality drops below 80
pruned_at) and reassess pruning rules. Do not proceed to Wave 3.
2. After Wave 4: If hierarchy clustering produces <10 meaningful clusters (>80
are labeled "miscellaneous" or "unknown"), the embedding quality is insufficient. Pause
hierarchy and investigate embedding enrichment.
3. After Wave 5: If KARL boost creates >10
>0.1 in 30 days without new triple ingestion), reduce boost rate or disable.
---
Total Estimates
| Wave | Tasks | Estimated Hours | Owner |
|---|---|---|---|
| Wave 0 | 4 | 4 | Claude |
| Wave 1 | 6 | 14 | Claude |
| Wave 2 | 7 | 16 | Claude + Mohamed |
| Wave 3 | 5 | 8 | Claude |
| Wave 4 | 7 | 24 | Claude + Mohamed |
| Wave 5 | 6 | 14 | Claude + Mohamed |
| Total | 35 | ~80 hours |
New Rust code: ~600 lines (decay, topology, registry, endpoints)
New Python code: ~500 lines (embedding pipeline, hierarchy discovery, KARL integration)
SQL migrations: ~30 lines
New Supabase table: 1 (predicate_registry)
New Prefect flows: 1 (weekly hierarchy re-discovery)
Modified files: ~8 Rust files, ~2 Python files
---
Rollback Plan
Every wave has an explicit rollback:
- Wave 0: DROP columns (updated_at, pruned_at, pruned_reason). Revert Rust upsert.
- Wave 1: Disable decay endpoint. Revert traversal to use MIN instead of product.
- Wave 2: `UPDATE knowledge_graph SET pruned_at = NULL, pruned_reason = NULL`.
Revert WHERE clause changes. Takes <5 seconds.
- Wave 3: DROP TABLE predicate_registry. Remove confidence cap. Takes <1 minute.
- Wave 4: Delete hierarchy/similarity triples:
`DELETE FROM knowledge_graph WHERE source IN ('hsae-discovery-v1', 'embedding-similarity-v1')`.
Disable Prefect flow.
- Wave 5: Remove report_to_gk() from KARL. Remove explain mode from traversal.
Delete Prometheus metrics. Revert Nexus page.
Each rollback is independent. Reverting Wave 3 does not require reverting Wave 2.
---
Dependency Map (What Blocks What)
Wave 0.1 (migration) ─────> Wave 0.2 (upsert) ─────> Wave 0.3 (backfill)
|
Wave 0.4 (filter)
|
┌─────────────┴──────────────┐
| |
Wave 1.1-1.6 Wave 2.1-2.3
(Confidence) (Topology)
| |
| Wave 2.4-2.7
| (Pruning)
| |
└────────────┬───────────────┘
|
Wave 3.1-3.5
(Registry)
|
Wave 3.5
(Deploy)
|
┌────────────┴───────────────┐
| |
Wave 4.1-4.7 Wave 5.1-5.2
(Hierarchy) (KARL Loop)
| |
└────────────┬───────────────┘
|
Wave 5.3-5.6
(Planner + Obs)Promotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
evo-cube-output/ai-generated-graph-topology/stage3-expand-master-plan.md
Detected Structure
Method · Evaluation · Figures · Code Anchors · Architecture · is Stage Research