Knowledge Graph — structured cognition.

Memories aren't a flat blob — they're a graph. 4 relation types, an entity registry that resolves alias-to-canonical-name, temporal validity on every edge so the past stays queryable, and three new MCP tools (memory_kg_query, memory_kg_timeline, memory_kg_invalidate) that turn the graph into a first-class API surface. Shipped in v0.6.3 Streams B + C — the foundation v0.7's Apache AGE acceleration replaces the dual-path stubs of.

4 relations entity registry (Stream C) temporal validity (Stream B) 3 KG MCP tools v0.7: Apache AGE acceleration
The graph

Memories + edges = the KG.

Every memory is a node. Every memory_link row is a directed edge with a typed relation and (since v0.6.3) a temporal validity window. The graph is queryable via recursive CTE today, and via Apache AGE Cypher in v0.7.

M1: Q3 OKR draft tier=mid · created Mar 15 M2: Q3 OKR review tier=mid · created Apr 1 M3: Q3 retro tier=long · consolidated M4: We use Postgres tier=long · created Jan 5 M5: Migrated to MySQL tier=long · created Apr 20 M6: Postgres steps tier=mid · derived E1: alphaone aliases: AO, AlphaOne LLC E2: project-orion aliases: Orion, Proj-O related_to supersedes derived_from contradicts about_entity about_entity Memory node Entity node (KG)
The four relations

Closed set — wire-format stable.

Every memory_link row carries one of these four relation tags. The set is closed (validators reject anything else); adding a new relation is a wire-format change, not a content tweak.

related_to

Generic association. The default. "These memories are about the same thing." No semantic claim beyond "look at both."

e.g. M1 ↔ M2: both about the same project
supersedes

The newer memory replaces the older. The graph captures the supersession explicitly so recall can prefer the latest.

e.g. M2 supersedes M1: review replaces draft
contradicts

Explicit disagreement. Often auto-created by memory_detect_contradiction (LLM-driven). Surfaces conflicts on recall.

e.g. "we use Postgres" contradicts "migrated to MySQL"
derived_from

Provenance. Used by memory_consolidate to link N source memories to the consolidated output. Audit chain stays intact.

e.g. consolidated retro derived_from each weekly note
Temporal validity — v0.6.3 Stream B

Edges with a window.

Schema v15 (v0.6.3) adds four nullable columns to memory_links: valid_from, valid_until, observed_by, and signature (placeholder for v0.7 attested identity). Edges now carry a window — "this relation was true between Mar 15 and Apr 20." Past states stay queryable via memory_kg_timeline; the present query (memory_kg_query) walks only currently-valid edges.

// SQL — schema v15 memory_links columns CREATE TABLE memory_links ( source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE, target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE, relation TEXT NOT NULL, created_at TEXT NOT NULL, valid_from TEXT, // when the edge started being true (default = source.created_at) valid_until TEXT, // NULL = still valid; non-null = invalidated at this time observed_by TEXT, // agent_id of the observer (provenance) signature BLOB, // v0.7 placeholder for Ed25519 attestation PRIMARY KEY (source_id, target_id, relation) ); // Indexes that make recursive CTE fast CREATE INDEX idx_links_temporal_src (source_id, valid_from, valid_until); CREATE INDEX idx_links_temporal_tgt (target_id, valid_from, valid_until); CREATE INDEX idx_links_relation (relation, valid_from);
Entity registry — v0.6.3 Stream C

Aliases resolved to canonical names.

Entities are first-class. An entity is a long-tier memory tagged entity with metadata.kind = "entity"; its aliases live in the entity_aliases side table. memory_entity_register creates one (idempotent on (canonical_name, namespace)); memory_entity_get_by_alias resolves any alias to the canonical entity.

memory_entity_registersince v0.6.3 Stream C
Idempotent on (canonical_name, namespace) — re-registering reuses the entity_id and merges new aliases via INSERT OR IGNORE. A non-entity memory occupying the same (title, namespace) hard-errors rather than letting the upsert silently overwrite unrelated content.
// MCP — memory_entity_register { "canonical_name": "AlphaOne LLC", "namespace": "alphaone/research/papers", "aliases": ["AlphaOne", "AO", "alpha-one"], "kind": "organization" // optional · arbitrary tag } → {"entity_id": "550e8400-…", "created": true, "aliases_added": ["AlphaOne","AO","alpha-one"]}
memory_entity_get_by_aliassince v0.6.3 Stream C
Resolve any alias to the canonical entity. Returns the most-recently-created entity if multiple match.
// MCP — memory_entity_get_by_alias {"alias": "AO", "namespace": "alphaone/research/papers"} → {"entity_id": "550e8400-…", "canonical_name": "AlphaOne LLC", "all_aliases": ["AlphaOne","AO","alpha-one"]}
KG query tools — v0.6.3 Stream C

Walk the graph as data.

memory_kg_querysince v0.6.3
Walks currently-valid edges from a starting memory or entity. Recursive CTE in v0.6.3, replaced by Apache AGE Cypher in v0.7. Supports filters by relation type, depth, and namespace.
// MCP — memory_kg_query { "start_id": "550e8400-…", "relations": ["supersedes", "derived_from"], // optional filter "depth": 3, // optional · default 3 · max 8 "as_of": "2026-04-15T00:00:00Z" // optional · point-in-time (default: now) } → {"nodes": [...], "edges": [...], "depth_reached": 3, "edges_walked": 47}
memory_kg_timelinesince v0.6.3
Returns the chronological sequence of edges that connect to a memory or entity. Includes both currently-valid and historically-valid (now-invalidated) edges. Powers "how did we arrive at this?" UIs.
// MCP — memory_kg_timeline { "node_id": "550e8400-…", // memory OR entity id "since": "2026-01-01T00:00:00Z", "until": "2026-04-30T00:00:00Z", "relations": ["supersedes", "contradicts"] } → {"events": [ {"at": "2026-03-15T…", "type": "edge_added", "relation": "related_to", "target_id": "…"}, {"at": "2026-04-01T…", "type": "edge_added", "relation": "supersedes", "target_id": "…"}, {"at": "2026-04-20T…", "type": "edge_invalidated", "relation": "related_to", "target_id": "…"} ]}
memory_kg_invalidatesince v0.6.3
Soft-deletes an edge by stamping valid_until = now(). The edge stays in the database (memory_kg_timeline still surfaces it) but disappears from memory_kg_query's present-tense walk. Past-state queries with as_of < valid_until continue to traverse it.
// MCP — memory_kg_invalidate { "source_id": "550e8400-…", "target_id": "abc12345-…", "relation": "related_to" } → {"invalidated": true, "valid_until": "2026-04-27T05:30:00Z"} // Soft-delete preserves audit. To hard-delete the edge row, // use the existing memory_link delete path (federation-fanout aware).
v0.7 — Apache AGE acceleration

From recursive CTE to Cypher.

v0.6.3's KG queries use Postgres recursive CTEs (or SQLite WITH RECURSIVE). Performance is fine up to a few hundred thousand edges; past that, Apache AGE's Cypher engine is materially faster on graph workloads. v0.7 Bucket 2 (per the v0.7 charter) wraps v0.6.3's KG schema with AGE and replaces the dual-path stubs that v0.6.3 ships as scaffolding.

// v0.6.3 — recursive CTE walk (current) WITH RECURSIVE walk(source, target, depth) AS ( SELECT source_id, target_id, 1 FROM memory_links WHERE source_id = ?1 AND (valid_until IS NULL OR valid_until > now()) UNION ALL SELECT ml.source_id, ml.target_id, w.depth + 1 FROM memory_links ml JOIN walk w ON ml.source_id = w.target WHERE w.depth < 8 ) SELECT * FROM walk; // v0.7 — Apache AGE Cypher (planned) MATCH (m:Memory {id: $id})-[r:RELATED_TO|SUPERSEDES*1..8]->(target) WHERE r.valid_until IS NULL OR r.valid_until > datetime() RETURN target, r;
Why this is "structured cognition"

What the KG actually buys you.

Provenance survives consolidation. When memory_consolidate collapses 12 weekly notes into one retro, the original 12 don't disappear from the audit trail — each gets a derived_from edge to the retro. "Show me the source memories for this consolidated decision" is a one-query KG walk.
Contradictions are explicit. "We use Postgres" + "We migrated to MySQL" with a contradicts edge surface together on recall. The agent sees the conflict; the operator decides which is current via supersedes.
Time travel is free. as_of on KG queries returns the graph as it was at any past moment — useful for "what did we believe last quarter?" forensics. Without temporal validity columns, this requires a snapshot-based backup strategy.
Entity-centric recall. "All memories about AlphaOne" no longer requires fuzzy text matching — the entity registry resolves aliases, and edges to the entity node enumerate every related memory.