How Semantica's GraphBuilder Handles Entity Merging and Conflict Detection

Semantica's GraphBuilder resolves entity conflicts through priority-driven attribute merging for nodes and deterministic curve-group assignment for parallel edges, implemented in graphStore.ts and orchestrated by graphSceneState.ts.

The GraphBuilder in semantica-agi/semantica uses the Graphology library as its underlying graph engine. All mutations pass through a thin wrapper layer that centralizes entity merging and conflict detection logic. This article examines the specific mechanisms Semantica employs to handle duplicate entities, resolve attribute conflicts, and visually reconcile parallel relationships.


Node-Level Entity Merging

Semantica merges node entities through the batchMergeNodes function in explorer/src/store/graphStore.ts. This function processes batches of node definitions and delegates to Graphology's native merge operations.

Attribute Overwrite with Priority Weighting

When batchMergeNodes receives a node definition with an existing id, it calls graph.mergeNode(id, attributes):

import { batchMergeNodes } from "./store/graphStore";

batchMergeNodes([
  { 
    id: "gene-TP53", 
    attributes: { 
      label: "TP53", 
      x: 10, 
      y: 20, 
      semanticGroup: "entity", 
      visualPriority: 5 
    } 
  },
  { 
    id: "gene-TP53", 
    attributes: { 
      color: "#ff6666", 
      visualPriority: 8 
    }
  }
]);

Key behaviors:

  • Later attributes overwrite earlier ones — Graphology's mergeNode performs a shallow merge where new properties replace existing values
  • visualPriority and labelPriority fields persist across merges and inform the renderer's selection of dominant visual properties
  • semanticGroup classification enables downstream aggregation and theme-based styling decisions

The graphSceneState.ts module subsequently aggregates nodes by semanticGroup, computes a dominant group representative, and applies unified entity shapes (entityShape, entityShapeKind, entityAspectRatio) based on theme configuration in graphTheme.ts.


Edge-Level Conflict Detection and Resolution

Parallel edges—multiple relationships between identical source-target pairs—represent the primary conflict scenario for relationships. Semantica handles this through a dedicated normalization pipeline.

Batch Edge Insertion and Self-Loop Filtering

The batchMergeEdges function in graphStore.ts processes relationship batches:

import { batchMergeEdges } from "./store/graphStore";

batchMergeEdges([
  {
    id: "e1",
    source: "gene-TP53",
    target: "disease-Cancer",
    attributes: { 
      edgeType: "associated_with", 
      weight: 1.0, 
      visualPriority: 2 
    }
  },
  {
    id: "e2",
    source: "gene-TP53",
    target: "disease-Cancer",
    attributes: { 
      edgeType: "causes", 
      weight: 0.9, 
      visualPriority: 3 
    }
  }
]);

Implementation details:

  • Deterministic edge IDs — built from supplied id with fallback logic for uniqueness
  • Self-loop prevention — edges where source === target are silently skipped (allowSelfLoops: false in graph instantiation)
  • mergeDirectedEdgeWithKey — Graphology's keyed merge ensures idempotent edge creation

Parallel Edge Normalization

After batch insertion, normalizeParallelMetadataForPair executes for each source-target pair with multiple edges. This function implements conflict detection through sorting and annotation:

Attribute Purpose Determination Logic
parallelIndex Position among parallel edges Sort by visualPriority → weight → lexical edgeId, then assign ordinal
parallelCount Total parallel edge count Count of edges sharing the same source-target pair
familySize Edges sharing familyId Grouped sub-count for curve bundling
curveGroup Non-overlapping curve assignment Computed via curveGroupForPair using deterministic hashing
// Pseudocode illustrating the normalization logic
function normalizeParallelMetadataForPair(graph, source, target) {
  const edges = graph.directedEdges(source, target);
  
  const sorted = edges.sort((a, b) => {
    const attrA = graph.getEdgeAttributes(a);
    const attrB = graph.getEdgeAttributes(b);
    
    // Primary: higher visualPriority wins
    if (attrB.visualPriority !== attrA.visualPriority) {
      return attrB.visualPriority - attrA.visualPriority;
    }
    // Secondary: higher weight wins
    if (attrB.weight !== attrA.weight) {
      return attrB.weight - attrA.weight;
    }
    // Tertiary: lexical edge ID
    return a.localeCompare(b);
  });
  
  sorted.forEach((edge, index) => {
    graph.mergeEdgeAttributes(edge, {
      parallelIndex: index,
      parallelCount: sorted.length,
      curveGroup: curveGroupForPair(source, target, edge)
    });
  });
}

This deterministic ordering guarantees visually stable renders where edge priority and relationship semantics remain consistent across graph updates.


Visual Conflict Resolution via Scene State

The graphSceneState.ts module in explorer/src/workspaces/GraphWorkspace/ operates as the visualization state manager, translating graph topology into renderable scene elements. Its conflict resolution responsibilities include:

  • Node aggregation by semantic group — clustering nodes with identical semanticGroup values to reduce visual clutter
  • Priority-based label selection — when merged nodes carry multiple labels, labelPriority determines the displayed text
  • Theme-coordinated shape assignment — mapping semanticGroup to entityShape, entityShapeKind, and colour schemes defined in graphTheme.ts

Complete Workflow Example

The following pattern from cookbook/introduction/17_Conflict_Detection_and_Resolution.ipynb demonstrates end-to-end entity merging and conflict resolution:

// Step 1: Initialize graph store with conflict-aware configuration
import { useGraphStore } from "./store/graphStore";

const graph = useGraphStore.getState().graph;

// Step 2: Merge potentially conflicting entity definitions
batchMergeNodes(nodeDefinitions);  // Handles duplicate IDs via attribute merge

// Step 3: Merge relationship definitions including parallel edges
batchMergeEdges(edgeDefinitions);  // Triggers parallel edge detection

// Step 4: Access normalized graph state
const normalizedEdges = graph
  .directedEdges("entity-A", "entity-B")
  .map(e => ({
    id: e,
    ...graph.getEdgeAttributes(e)
  }));

// normalizedEdges now contain parallelIndex, curveGroup for rendering

Summary

  • batchMergeNodes in graphStore.ts merges node attributes through Graphology's mergeNode, with visualPriority governing visual dominance
  • batchMergeEdges handles relationship insertion, filtering self-loops and enabling parallel edge detection
  • normalizeParallelMetadataForPair resolves edge conflicts via deterministic sorting by priority, weight, and identifier, annotating edges with render metadata
  • graphSceneState.ts aggregates semantic groups and applies theme-based visual unification, completing the conflict resolution pipeline
  • The conflict detection notebook (17_Conflict_Detection_and_Resolution.ipynb) provides reproducible examples of these mechanisms in action

Frequently Asked Questions

How does Semantica determine which node attributes survive a merge conflict?

Semantica uses attribute timestamping via merge order—later attribute objects overwrite earlier ones—supplemented by visualPriority and labelPriority fields that explicitly signal importance to the renderer. The graphSceneState.ts module consults these priorities when selecting representative properties for display.

What happens when two edges connect the same source and target nodes?

The parallel edge normalizer detects this condition, sorts edges by visualPriority → weight → lexical ID, and assigns each edge parallelIndex, parallelCount, and a deterministic curveGroup. This ensures non-overlapping visual representation without discarding relationship semantics.

Where is the entity shape and styling logic defined?

Visual styling rules reside in explorer/src/workspaces/GraphWorkspace/graphTheme.ts, which maps semanticGroup values to concrete entity shapes (entityShape, entityShapeKind, entityAspectRatio) and colour palettes. The graphSceneState.ts module applies these mappings during scene construction.

Can self-referencing relationships exist in Semantica graphs?

No—the underlying Graphology instance is instantiated with allowSelfLoops: false, and batchMergeEdges explicitly skips edges where source === target before any merge operation occurs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →