Graph ID Model¶
Emergent graph objects have two distinct ID fields that serve different purposes. Understanding the difference is critical for building correct integrations.
The Dual-ID Model¶
As of SDK v0.8.0, every GraphObject and GraphRelationship has four ID fields (two are
the canonical names, two are deprecated aliases):
| Field | Alias (deprecated) | Stability | Changes on update? |
|---|---|---|---|
VersionID |
ID |
Version-specific | Yes — changes on every UpdateObject |
EntityID |
CanonicalID |
Stable | No — stable for the lifetime of the object |
Use EntityID when you need a stable reference to an object across updates.
Use VersionID when you need to reference the exact version (e.g., in relationship endpoints).
Example¶
obj, err := client.Graph.GetObject(ctx, someID)
if err != nil {
return err
}
// Stable identifier — use for bookmarks, links, foreign keys
stableRef := obj.EntityID
// Version-specific — changes after UpdateObject
versionRef := obj.VersionID
// Update the object
updated, err := client.Graph.UpdateObject(ctx, obj.VersionID, &graph.UpdateObjectRequest{
Properties: map[string]any{"status": "active"},
})
// updated.VersionID != obj.VersionID ← new version created
// updated.EntityID == obj.EntityID ← same entity
Don't store VersionID as a long-lived reference
Calling UpdateObject creates a new version. If you saved VersionID before the update,
it will no longer resolve with GetObject. Always use EntityID for stable references.
Deprecated Names¶
In v0.8.0, the old names were deprecated:
GraphObject.ID→ useGraphObject.VersionIDGraphObject.CanonicalID→ useGraphObject.EntityID- Same for
GraphRelationship.IDandGraphRelationship.CanonicalID
The old fields still exist and are cross-populated for backward compatibility — they always contain the same value as their replacements. But they will be removed in a future major version.
Custom JSON Unmarshaling¶
The SDK uses custom UnmarshalJSON on GraphObject and GraphRelationship to cross-populate
all four fields regardless of which JSON field names the server sends. Server responses
may use id/canonical_id (old) or version_id/entity_id (new) — the SDK handles both.
Accepting Either ID¶
Use GetByAnyID when the caller might have either a version-specific or entity-stable ID:
// Works with both obj.VersionID and obj.EntityID
obj, err := client.Graph.GetByAnyID(ctx, anyKindOfID)
graphutil Helpers¶
The graph/graphutil package provides helpers designed for the dual-ID model:
import "github.com/emergent-company/emergent.memory/apps/server/pkg/sdk/graph/graphutil"
// Create an ID set for membership testing
ids := graphutil.NewIDSet(obj)
ids.Contains(someID) // true for both VersionID and EntityID
// Build a lookup index from a slice of objects
idx := graphutil.NewObjectIndex(objects)
obj := idx.Get(anyID) // O(1) lookup by either ID
// Deduplicate a result set by EntityID (keeps newest version of each entity)
unique := graphutil.UniqueByEntity(objects)
See the graphutil reference for full API details.
Further Reading¶
The internal design document at docs/graph/id-model.md contains lifecycle diagrams,
UpdateObject footgun examples, relationship lookup guidance, and deduplication patterns.