Skip to content

Feature: Knowledge Graph memory — connected facts with relationships, decay, and contradiction detection #3

@huichalaf

Description

@huichalaf

Summary

Replace the flat MEMORY.md approach with a Knowledge Graph backed by SQLite — where facts have relationships, confidence scores that decay over time, source provenance, and automatic contradiction detection. This transforms memory from a list of isolated facts into a connected, self-maintaining knowledge base.

Problem

Today MEMORY.md is a flat markdown file. Facts have no relationships, no confidence scores, no provenance, and no expiration. This causes:

  1. No connections
  2. No aging
  3. No source tracking — you can't tell where a fact came from or how to re-verify it.
  4. Silent contradictions — if new information contradicts an existing fact, both coexist without warning.
  5. No structure

Proposed Design

1. Knowledge Edges — connected facts

Add a knowledge_edges table alongside the existing FTS5 index:

CREATE TABLE knowledge_edges (
  from_chunk TEXT NOT NULL,    -- source chunk key (path:startLine:endLine)
  to_chunk   TEXT NOT NULL,    -- target chunk key
  relation   TEXT NOT NULL,    -- relation type
  weight     REAL DEFAULT 1.0, -- strength of connection
  created_at TEXT NOT NULL,
  PRIMARY KEY (from_chunk, to_chunk, relation)
);

Relation types:

  • related_to — general association
  • caused_by
  • used_by — code reference (e.g., endpoint used_by service file)
  • contradicts — conflicting information
  • supersedes — newer version of same knowledge
  • part_of

This enables graph traversal during search

2. Confidence Decay — knowledge ages

Every promoted fact gets a confidence score that decays over time:

interface VerifiedKnowledge {
  snippet: string;
  verifiedAt: string;           // when it was verified
  confidenceScore: number;      // initial confidence (0-1)
  currentConfidence: number;    // decays with half-life
  sources: string[];            // provenance chain
  validationMethod: string;     // how it was verified
  lastRevalidated?: string;     // last re-verification date
  domain?: string;              // optional domain tag
}
CREATE TABLE knowledge_confidence (
  chunk_key          TEXT PRIMARY KEY,
  initial_confidence REAL NOT NULL,
  verified_at        TEXT NOT NULL,
  half_life_days     INTEGER DEFAULT 90,  -- domain-configurable
  sources            TEXT,                -- JSON array
  validation_method  TEXT,
  last_revalidated   TEXT,
  domain             TEXT
);

Decay formula (same pattern as temporal decay in memory search):

const ageDays = (Date.now() - verifiedAt) / (1000 * 60 * 60 * 24);
const lambda = Math.LN2 / halfLifeDays;
const currentConfidence = initialConfidence * Math.exp(-lambda * ageDays);

During dreaming, facts with currentConfidence < 0.3 get flagged for re-verification (feeds into the autoresearch loop from #2) or deprecation.

3. Contradiction Detection

When new knowledge is promoted (from dreaming or autoresearch), compare against existing facts:

async function detectContradictions(
  newSnippet: string,
  existingMemory: SearchResult[]
): Promise<Contradiction[]> {
  // 1. Find semantically similar existing facts
  const similar = await memoryDB.search(extractKeywords(newSnippet));

  // 2. For high-similarity matches, check for contradiction
  //    Using keyword overlap + negation detection
  for (const existing of similar) {
    const similarity = jaccardSimilarity(newSnippet, existing.snippet);
    if (similarity > 0.5) {
      // Same topic — check if they agree or contradict
      const hasNegation = detectNegationDifference(newSnippet, existing.snippet);
      const hasDifferentValues = detectValueDifference(newSnippet, existing.snippet);

      if (hasNegation || hasDifferentValues) {
        contradictions.push({
          existing: existing,
          new: newSnippet,
          type: hasNegation ? "negation" : "value_mismatch",
        });
      }
    }
  }
  return contradictions;
}

Resolution strategy:

Scenario Action
New has higher confidence + more recent Replace old, add supersedes edge
Confidence tie Flag in DREAMS.md for human review
New has lower confidence Discard new, log in DREAMS.md
Both from code cross-ref but different files Keep both, add contradicts edge for investigation

4. Research Provenance — every fact has a trail

Every fact in memory tracks where it came from:

- The change flight endpoint uses POST /do
  *(verified: 2026-04-13, confidence: 0.92, decay: 90d)*
  *(sources: ")*
  *(method: code-crossref)*

This enables:

  • Re-verification — go back to the same sources to check if still valid
  • Trust assessment — code-crossref > web-confirm > single-source
  • Staleness detection — if source file was modified after verification date, flag for re-check

5. Enhanced Search with Graph Traversal

Modify memory_search to optionally follow edges:

async function searchWithGraph(
  query: string,
  options: { followEdges?: boolean; maxDepth?: number }
): Promise<SearchResult[]> {
  // 1. Standard FTS5/BM25 search
  const directResults = await memoryDB.search(query);

  if (!options.followEdges) return directResults;

  // 2. For top results, follow edges to find related knowledge
  const relatedResults: SearchResult[] = [];
  for (const result of directResults.slice(0, 3)) {
    const edges = await getEdges(result.chunkKey);
    for (const edge of edges) {
      if (edge.relation !== "contradicts") {  // don't surface contradictions silently
        const related = await getChunk(edge.to_chunk);
        if (related) {
          related.score *= edge.weight * 0.5;  // discount related results
          related.via = `${edge.relation}${result.citation}`;
          relatedResults.push(related);
        }
      }
    }
  }

  // 3. Merge, deduplicate, re-rank
  return mergeAndRank([...directResults, ...relatedResults]);
}

6. Dreaming Integration

During Deep phase, edges are built automatically:

// After promoting a candidate
for (const promoted of promotedCandidates) {
  // Find related existing chunks
  const related = await memoryDB.search(extractKeywords(promoted.snippet));
  for (const match of related.slice(0, 3)) {
    if (match.score > 0.4 && match.chunkKey !== promoted.key) {
      await addEdge(promoted.key, match.chunkKey, "related_to", match.score);
    }
  }

  // Check for contradictions
  const contradictions = await detectContradictions(promoted.snippet, related);
  for (const c of contradictions) {
    await addEdge(promoted.key, c.existing.chunkKey, "contradicts", 1.0);
    logToDreams(`⚠️ Contradiction detected: new "${promoted.snippet}" vs existing "${c.existing.snippet}"`);
  }
}

DREAMS.md Output Example

## Deep Sleep — 2026-04-13 03:00

### Knowledge Graph Updates
- Added 3 new edges (2 related_to, 1 part_of)
- Re-verified 2 aging facts (confidence refreshed)
- Flagged 1 contradiction for human review:
  ⚠️ "fareKey is a string" vs "fareKey is an object" (both from code, different files)

### Confidence Report
- 24 facts above 0.7 confidence ✅
- 8 facts between 0.3-0.7 (aging) ⚠️
- 2 facts below 0.3 → scheduled for re-verification 🔄

Configuration

{
  "memory": {
    "knowledgeGraph": {
      "enabled": false,
      "followEdges": true,
      "maxEdgeDepth": 2,
      "confidenceDecay": {
        "enabled": true,
        "defaultHalfLifeDays": 90,
        "revalidationThreshold": 0.3,
        "domainOverrides": {
          "api": 60,
          "business-rules": 180,
          "code-patterns": 120
        }
      },
      "contradictionDetection": {
        "enabled": true,
        "autoResolve": false,
        "similarityThreshold": 0.5
      }
    }
  }
}

Implementation Plan

Phase What Files
1 SQLite tables for edges + confidence lib/memory-db.ts
2 Confidence decay calculation lib/knowledge-confidence.ts (new)
3 Edge creation during dreaming lib/dreaming.ts
4 Contradiction detection lib/contradiction-detector.ts (new)
5 Graph-aware search lib/memory-db.ts (extend search)
6 Provenance tracking lib/dreaming.ts + server.ts
7 DREAMS.md reporting lib/dreaming-markdown.ts or equivalent

Backward Compatibility

  • MEMORY.md remains the human-readable view (no format change)
  • Graph data lives in SQLite (.memory.sqlite), alongside existing FTS5 index
  • If knowledgeGraph.enabled = false, everything works exactly as today
  • Graph is derived/rebuildable — deleting .memory.sqlite just loses edges and confidence, not the facts themselves

Relation to Other Proposals


Happy to contribute implementation. This builds naturally on the existing SQLite + FTS5 infrastructure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions