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
mergeNodeperforms a shallow merge where new properties replace existing values visualPriorityandlabelPriorityfields persist across merges and inform the renderer's selection of dominant visual propertiessemanticGroupclassification 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
idwith fallback logic for uniqueness - Self-loop prevention — edges where
source === targetare silently skipped (allowSelfLoops: falsein 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
semanticGroupvalues to reduce visual clutter - Priority-based label selection — when merged nodes carry multiple labels,
labelPrioritydetermines the displayed text - Theme-coordinated shape assignment — mapping
semanticGrouptoentityShape,entityShapeKind, and colour schemes defined ingraphTheme.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
batchMergeNodesingraphStore.tsmerges node attributes through Graphology'smergeNode, withvisualPrioritygoverning visual dominancebatchMergeEdgeshandles relationship insertion, filtering self-loops and enabling parallel edge detectionnormalizeParallelMetadataForPairresolves edge conflicts via deterministic sorting by priority, weight, and identifier, annotating edges with render metadatagraphSceneState.tsaggregates 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →