# How Semantica's GraphBuilder Handles Entity Merging and Conflict Detection

> Discover how Semantica's GraphBuilder merges entities and detects conflicts using priority-driven attribute merging and deterministic curve-group assignment. Explore the code in graphStore.ts.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: how-to-guide
- Published: 2026-08-20

---

**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`](https://github.com/semantica-agi/semantica/blob/main/graphStore.ts) and orchestrated by [`graphSceneState.ts`](https://github.com/semantica-agi/semantica/blob/main/graphSceneState.ts).**

The **GraphBuilder** in [semantica-agi/semantica](https://github.com/semantica-agi/semantica) uses the [Graphology](https://graphology.github.io/) 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`](https://github.com/semantica-agi/semantica/blob/main/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)`:

```typescript
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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/graphStore.ts) processes relationship batches:

```typescript
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 |

```typescript
// 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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/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:

```typescript
// 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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/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`](https://github.com/semantica-agi/semantica/blob/main/explorer/src/workspaces/GraphWorkspace/graphTheme.ts)**, which maps `semanticGroup` values to concrete **entity shapes** (`entityShape`, `entityShapeKind`, `entityAspectRatio`) and colour palettes. The [`graphSceneState.ts`](https://github.com/semantica-agi/semantica/blob/main/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.