Spec — Knowledge-Graph Explorer MVP (Sigma.js + Mastra)

Author: hermes · Date: 2026-06-28 · Status: spec / build-ready (pending board placement) Research basis: research/kg-3d-viz-second-brain-2026-06-28/ (REPORT + ADDENDUM-2d-renderers). All library licenses/versions verified npm/GitHub 2026-06-28. Lane: Hermes specs content/data/capability; Apollo builds the front-end viz + chat UI; backend graph-DB + Mastra tools = separate owner (see §10). Visual/UX design = Apollo's call — this spec fixes data shape + capability contract, not pixels.


1. Goal

A client-embeddable web component: a navigable, customizable 2D knowledge-graph explorer (the Obsidian-graph mental model, but with deeper customization) over a Graphify graph, paired with a "knowledge expert" chat powered by the existing Mastra agents on Railway. The agent reads the graph (summarize / analyze / suggest topics) and can write back (new nodes/edges/topics). Chat and graph are bidirectionally wired: agent answers highlight + fly-to nodes; clicking a node seeds a question.

Explicit non-goals (this MVP): 3D (dropped — overkill); giant-graph GPU mode (cosmos.gl) deferred to phase 2; auto-ingestion pipeline (Graphify already produces the graph); temporal memory (Graphiti) deferred.

2. Stack (all MIT/Apache, verified)

Layer Choice License Why
Framework Next.js (App Router) + React 19 MIT client stack; matches Apollo/ViskaFront
Renderer Sigma.js v3 (sigma) MIT WebGL-2D force renderer = Obsidian's architecture, opened up (custom programs + reducers)
React binding @react-sigma/core v5 MIT idiomatic React mount + hooks (useSigma, useLoadGraph, useRegisterEvents)
Graph model + algos graphology (+ graphology-layout-forceatlas2, graphology-communities-louvain, graphology-metrics) MIT data model, FA2 layout, Louvain communities (= Graphify clusters), centrality (for gap-detection)
Agent runtime Mastra (existing, Railway) already deployed; TS tool-calling + streaming
Chat UI @ai-sdk/react useChat (+ optional assistant-ui) MIT/Apache streams Mastra tool parts; renders tool-invocation states
Graph store Neo4j or Postgres/pgvector (decision §10) agent read/write target + node embeddings

Deferred (phase 2, behind same data layer): @cosmos.gl/graph (MIT) for the 100k–1M+ big-graph mode. ⚠ Do not use @cosmograph/* packages — CC-BY-NC non-commercial.

3. Architecture

Graphify JSON {nodes:[{id,label,community,weight,...}], links:[{source,target,weight}]}
        │  (one-time / on-update load)
        ▼
   Graph store (Neo4j | Postgres+pgvector)  ◄──── agent write-back (new nodes/edges)
        │  read (graph + node embeddings)         ▲
        ▼                                          │
   Next.js app                                     │
   ├─ <GraphExplorer>  (Sigma v3 + @react-sigma)   │
   │     graphology graph ← store/JSON             │
   │     FA2 layout (worker) · Louvain color       │
   │     reducers: LOD · hover-highlight · search  │
   │     imperative API ⇄ chat (highlight/fly-to)  │
   └─ <ExpertChat>  (useChat → Mastra agent) ───────┘
         tools: query-graph · get-neighbors · suggest-gaps · write-node
         stream: tool-output parts + custom data-graph-highlight parts

4. Data contract (Graphify → renderer)

Graphify output already matches the {nodes, links} shape. Canonical node/edge fields the front-end relies on (Hermes-owned contract; if Graphify field names differ, map at load — do not change Graphify):

type GNode = { id: string; label: string; community: number;   // Louvain cluster id
               weight?: number; x?: number; y?: number;          // x/y present iff precomputed
               degree?: number; kind?: string };                 // optional metadata
type GEdge = { source: string; target: string; weight?: number; kind?: string };
type GraphifyExport = { nodes: GNode[]; links: GEdge[]; meta?: { generatedAt: string; version: string } };

5. Renderer spec (Sigma v3 — the customization surface)

This is where "more customizable than Obsidian" is delivered. Required capabilities:

  1. Community coloring + spatial separation — color by community; seed FA2 with per-community centroid offset so clusters read by position, not just hue.
  2. Level-of-detail (semantic zoom)labelRenderedSizeThreshold + a nodeReducer that hides labels/small nodes when zoomed out, reveals them on zoom-in. (Obsidian does a weak version of this; we expose the thresholds.)
  3. Focus + context (hover/click)nodeReducer/edgeReducer dim non-neighbors; highlight the hovered node's neighborhood. Click = "select" (persisted highlight + opens node detail).
  4. Search → fly-to — search box → camera.animate({x,y,ratio}, {duration}) eased fly + zoom to the matched node; highlight it.
  5. Custom node/edge programs (the deep hook) — Sigma v3 lets you register custom WebGL node/edge programs: glow/ring nodes, sized-by-centrality, edge thickness by weight, dashed/colored edges by kind. This is the customization ceiling Obsidian doesn't expose. Apollo owns the visual language.
  6. Interaction — drag-pan, scroll-zoom, node drag (optional), minimap (optional phase 2).
  7. Imperative handleGraphExplorer exposes a ref/context API the chat calls: highlightNodes(ids: string[]), flyTo(id: string), clearHighlight(), getSelected(): string|null.

Animation/aesthetic (Apollo discretion): node fade-in on load, FA2 settle animation, eased camera, hover ring, neighbor-edge emphasis. Keep it Obsidian-clean, not noisy.

6. Agent ⟷ graph capability contract (Mastra tools)

Four tools, Zod-typed in/out so the client knows the result shape. TypeScript-native, no Python (the deliberate low-risk path: the graph already exists, so we own read/write tools over a graph DB rather than adopting a Python GraphRAG engine — see research §3).

// READ — graph-grounded answer + the nodes to highlight
queryGraph: {
  input:  { query: string; seedId?: string },
  output: { answer: string; nodeIds: string[]; subgraph: { nodes: GNode[]; links: GEdge[] } }
}
// READ — ego expansion for click-to-explore
getNeighbors: {
  input:  { nodeId: string; hops?: 1|2; limit?: number },   // limit default 100
  output: { nodes: GNode[]; links: GEdge[] }
}
// READ/ANALYZE — "suggest new topics to research" = structural gap detection (InfraNodus pattern)
suggestGaps: {
  input:  { scope?: 'global' | { communityId: number } },
  output: { suggestions: { question: string; bridges: [number, number][]; rationale: string }[] }
  // impl: low-connectivity community pairs / low-betweenness frontier nodes (graphology-metrics)
  //       → LLM phrases bridging research questions
}
// WRITE — agent persists a new node/edge/topic back to the graph (gated, §9)
writeNode: {
  input:  { node?: GNode; edges?: GEdge[]; provenance: string },  // provenance = required audit string
  output: { written: { nodeIds: string[]; edgeCount: number }; warnings: string[] }
}

Read tools run Cypher (Neo4j) or SQL+pgvector (Postgres). queryGraph retrieves a subgraph (vector search on node embeddings + k-hop expansion), answers grounded, returns cited nodeIds — the bridge to the viz.

7. Bidirectional wiring (the key UX mechanism)

8. Next.js integration (load-bearing gotchas)

  1. WebGL is client-only → the GraphExplorer MUST be dynamic(() => import('./GraphExplorer'), { ssr:false }); SSR throws window is not defined. #1 failure mode.
  2. Wrapper is 'use client'; keep the graph a leaf so the rest of the page still SSRs.
  3. Hold the imperative ref (§5.7) at the page level so ExpertChat can drive GraphExplorer.
  4. Cache the (precomputed-layout) graph JSON keyed by meta.version — Graphify output is an immutable build artifact; treat it as edge/CDN-cacheable.

9. Security (rule #19 — gate the write path)

writeNode mutates business-of-record state. It must never be reachable as a public unauthenticated mutating endpoint. Constraints: - The Mastra write tool runs server-side (Railway), authenticated to the graph DB via a vaulted credential (Hades) — never a browser-side DB connection. - If any HTTP surface exposes write (webhook/API), it MUST authenticate the caller (header-auth / JWT / edge HMAC) — path obscurity is not auth. - writeNode requires a provenance string and writes to a quarantine/pending state by default; promotion to the canonical graph is a separate gated step (mirrors the Viska memory-system §3 guardrail posture — agent-written content is untrusted until validated). Zero-fabrication: written claims carry source provenance. - Browser → graph is read-only (RLS SELECT-only if Postgres); all mutation flows through the authenticated agent tool.

10. Lane + open decisions (route before/with build)

Item Owner Note
Front-end Sigma viz + chat UI shell + bidirectional wiring Apollo this handoff
Visual language (node/edge programs, palette, motion) Apollo Apollo's design domain
Graph store choice: Neo4j vs Postgres/pgvector Proteus (DB governance) Postgres if you want one store for graph+embeddings; Neo4j if you want native graph + mcp-neo4j. Decision blocks backend.
Mastra tools (query/getNeighbors/suggestGaps/writeNode) backend TBD backend owner (not Apollo) TS; could be Apollo full-stack or a backend agent — operator to assign
Write-path credential (graph DB) deposit Hades vaulted; agent-side only
Board placement operator/Apollo adjacent: proteus #207 Graph Viewer v1 (internal, different scope) — link as Related, don't merge

11. Build sequence (MVP milestones)

  1. M0 — shell: Next.js page, dynamic(ssr:false) GraphExplorer, load a Graphify JSON sample into graphology, Sigma mount, FA2 layout, Louvain color. Static graph renders.
  2. M1 — navigability: LOD reducer, hover-highlight focus+context, search→fly-to, node detail panel, click-to-select. Obsidian-parity + more.
  3. M2 — custom visual language: custom node/edge programs (Apollo's design), centrality sizing, edge-by-weight. "More customizable than Obsidian" delivered.
  4. M3 — agent read: wire ExpertChat (useChat → Mastra), queryGraph + getNeighbors, agent→graph highlight/fly-to, graph→agent click-to-ask. Talk-to-your-graph works.
  5. M4 — suggest + write: suggestGaps chips; writeNode (quarantined, gated per §9). Read+write.
  6. Phase 2 (deferred): cosmos.gl big-graph mode behind the same data layer; Graphiti temporal sidecar; minimap; VR (skip).

12. Acceptance criteria (MVP = M0–M4)

13. Rule #18 (no-vaporware) status

Clean — adopt/integrate, not rebuild. Sigma.js, graphology, Mastra, the graph DB, and the Graphify graph all already exist. Net-new code = the React explorer component, the four Mastra tools, the bidirectional glue, and the write-gate. No engine is rebuilt; no existing solution duplicated.