Manually Updating the Graph

Add nodes and fact triplets, or update existing nodes and edges

Overview

Zep provides several ways to manually change a graph:

  • Use graph.add_nodes to add one or more nodes without creating relationships.
  • Use graph.add_fact_triple to add a relationship and its source and target nodes together.
  • Use graph.node.update to update an existing node by UUID.
  • Use graph.edge.update to update an existing edge by UUID.

Adding Nodes

Use add_nodes when you already have structured entity data and do not need Zep to extract nodes from an episode or create relationships between them. Zep stores the node contents you provide and derives the search representation from each node’s name.

The same method handles both single-node and batch creation. Each request must contain between 1 and 100 nodes and target one user_id or graph_id.

Adding a Single Node

Pass one item in the nodes list to add a single node:

1from zep_cloud import AddNodeItem
2from zep_cloud.client import Zep
3
4client = Zep(api_key=API_KEY)
5
6result = client.graph.add_nodes(
7 user_id=user_id, # Optional: You can use graph_id instead of user_id
8 nodes=[
9 AddNodeItem(
10 name="Alice Johnson",
11 summary="An engineer on the platform team.",
12 label="Person",
13 attributes={"role": "Engineer", "active": True},
14 metadata={"source": "crm"},
15 )
16 ],
17)
18
19# UUIDs are assigned before the asynchronous task begins.
20node_uuid = result.nodes[0].uuid_
21task_id = result.task_id

Adding Nodes in a Batch

Include multiple items in the same nodes list to create up to 100 nodes in one request. The nodes may use different entity types, but they must all belong to the same user or graph.

1from zep_cloud import AddNodeItem
2from zep_cloud.client import Zep
3
4client = Zep(api_key=API_KEY)
5
6result = client.graph.add_nodes(
7 graph_id=graph_id, # Optional: You can use user_id instead of graph_id
8 nodes=[
9 AddNodeItem(
10 uuid_="c2b42bf8-5d3f-4c92-92a6-43d8854a1e5b",
11 name="Acme Corporation",
12 summary="An enterprise software company.",
13 label="Organization",
14 attributes={"tier": "enterprise"},
15 ),
16 AddNodeItem(
17 uuid_="79166282-3c4d-4d67-a6b6-4da1bd3a2f11",
18 name="Alice Johnson",
19 label="Person",
20 attributes={"role": "Engineer"},
21 ),
22 ],
23)
24
25node_uuids = [node.uuid_ for node in result.nodes]
26task_id = result.task_id

Node Creation Behavior

add_nodes returns a task_id and the accepted nodes immediately. Each returned node includes its server-assigned or caller-supplied UUID, but the node is not available to read or search until the asynchronous task succeeds. See Check Data Ingestion Status for how to poll the task.

Supplying a UUID makes node creation retry-safe and enables upserts:

  • If the UUID does not exist, Zep creates the node with that UUID.
  • If the UUID already exists in the graph, Zep updates that node. A non-empty summary or label replaces the existing value, attributes are merged, and the existing name is preserved.
  • If you omit the UUID, Zep creates a new node with a server-assigned UUID. Nodes are not deduplicated by name or content.

When add_nodes upserts an existing node, an attribute set to null is stored as null; it is not deleted. Use graph.node.update to rename a node, delete an attribute, or clear its entity type.

Each node supports the following fields:

FieldRequiredDescription
nameYesNode name, up to 50 characters.
summaryNoNode summary, up to 500 characters.
labelNoA single entity type, up to 100 characters. The base Entity label is implicit. If the label matches an ontology type with properties, attributes are validated against it.
attributesNoUp to 10 custom attributes. When label matches an ontology type with properties, the attributes are validated against it.
metadataNoUp to 10 metadata fields attached to the provenance episode when a new node is created.
uuidNoA caller-supplied UUID v4 used as the node’s identity for creation, upserts, and retries.
created_atNoRFC 3339 creation time for a new node. Defaults to the request time.

Updating a Node

Use graph.node.update to update an existing node by UUID. Only the fields you provide are changed, and the updated node is returned synchronously.

1from zep_cloud.client import Zep
2
3client = Zep(api_key=API_KEY)
4
5updated_node = client.graph.node.update(
6 uuid_=node_uuid,
7 name="Alice Smith",
8 summary="A principal engineer on the platform team.",
9 labels=["Person"],
10 attributes={
11 "role": "Principal Engineer",
12 "legacy_field": None, # Deletes this attribute
13 },
14)

Node attributes are merged with the existing attributes. Set an attribute to null to delete it. If you provide labels, the list replaces the existing labels. Python and TypeScript callers can use an empty list to clear the node’s entity type.

The current Go SDK omits an empty Labels slice from the request. To clear a node’s entity type from Go, send {"labels": []} directly to PATCH /graph/node/{uuid}.

Updating an Edge

Use graph.edge.update to update an existing entity edge by UUID. You can change its relationship name, fact, temporal fields, and attributes. The source and target node UUIDs cannot be changed.

1from zep_cloud.client import Zep
2
3client = Zep(api_key=API_KEY)
4
5updated_edge = client.graph.edge.update(
6 uuid_=edge_uuid,
7 name="WORKS_AT",
8 fact="Alice Smith is a principal engineer at Acme Corporation",
9 valid_at="2025-01-15T00:00:00Z",
10 attributes={
11 "role": "Principal Engineer",
12 "legacy_field": None, # Deletes this attribute
13 },
14)

Edge attributes are merged with the existing attributes. Set an attribute to null to delete it. Temporal fields use RFC 3339 / ISO 8601 timestamps.

Adding a Fact Triplet

You can add manually specified fact/node triplets to the graph. Provide the fact, a relationship name, and the source and target node names or UUIDs. Zep will then create a new corresponding edge and nodes, or use existing nodes and an edge if they represent the same entities and relationship. If the new fact invalidates an existing fact, Zep marks the existing fact as invalid and adds the new fact triplet.

The add_fact_triple method returns a task_id that can be used to track the processing status of the operation. See the Check Data Ingestion Status recipe for how to poll task status.

1from zep_cloud.client import Zep
2
3client = Zep(
4 api_key=API_KEY,
5)
6
7result = client.graph.add_fact_triple(
8 user_id=user_id, # Optional: You can use graph_id instead of user_id
9 fact="Paul met Eric",
10 fact_name="MET",
11 target_node_name="Eric Clapton",
12 source_node_name="Paul",
13)
14
15# The result includes a task_id for tracking processing status
16task_id = result.task_id
17print(f"Fact triple task ID: {task_id}")

Retrieving Created UUIDs

Because fact triple creation is asynchronous, the UUIDs for the edge and nodes are not returned immediately. After the task completes, you can retrieve them by calling client.task.get() with the task_id returned from add_fact_triple. The task’s params field will contain edge_uuid, source_node_uuid, and target_node_uuid.

1import time
2
3from zep_cloud.client import Zep
4
5client = Zep(api_key=API_KEY)
6
7# Add a fact triple
8result = client.graph.add_fact_triple(
9 user_id=user_id,
10 fact="Paul met Eric",
11 fact_name="MET",
12 target_node_name="Eric Clapton",
13 source_node_name="Paul",
14)
15
16task_id = result.task_id
17if task_id is None:
18 raise RuntimeError("The add fact triple response did not include a task ID")
19
20# Poll until the task completes
21while True:
22 task = client.task.get(task_id=task_id)
23
24 if task.status == "succeeded":
25 break
26 elif task.status == "failed":
27 raise Exception(f"Task failed: {task.error}")
28
29 time.sleep(10)
30
31# Retrieve the created UUIDs from task.params
32params = task.params or {}
33edge_uuid = params.get("edge_uuid")
34source_node_uuid = params.get("source_node_uuid")
35target_node_uuid = params.get("target_node_uuid")
36
37print(f"Edge UUID: {edge_uuid}")
38print(f"Source Node UUID: {source_node_uuid}")
39print(f"Target Node UUID: {target_node_uuid}")

Custom types and attributes

You can attach custom scalar attributes to nodes and edges, and optionally reference custom entity and edge types from your ontology to enforce a schema.

  • source_node_labels / target_node_labels — specify a single entity type name. When it matches an ontology type with properties, the node attributes are validated against it.
  • fact_name — serves as both the relationship display name and the edge type name looked up in your ontology.
  • source_node_attributes / target_node_attributes / edge_attributes — custom scalar properties on those nodes and edges, usable for filtering in graph searches.

Attribute validation — specifying types is optional. If a label or fact_name is not found in your ontology, attributes pass through without validation:

SituationResult
Label not in ontologyAll node_attributes pass through — no error
fact_name not in ontologyAll edge_attributes pass through — no error
Label in ontology, matching type has no propertiesAll attributes pass through
Label in ontology and type HAS properties definedAttributes validated strictly

When validation activates, every attribute key and value type must match the schema — otherwise the call returns HTTP 400.

1from zep_cloud.client import Zep
2
3client = Zep(api_key=API_KEY)
4
5# Assumes your ontology defines:
6# - Entity type "Person" with properties: { "age": int, "role": text }
7# - Edge type "WORKS_AT" with properties: { "start_year": int, "department": text }
8
9result = client.graph.add_fact_triple(
10 user_id=user_id,
11 fact="Alice works at Acme Corp",
12 fact_name="WORKS_AT", # matches edge type in ontology
13 source_node_name="Alice",
14 source_node_labels=["Person"], # matches entity type "Person" in ontology
15 source_node_attributes={
16 "age": 30, # validated: must be int
17 "role": "Engineer", # validated: must be text
18 },
19 target_node_name="Acme Corp",
20 target_node_labels=["Organization"], # not in ontology — passes through freely
21 edge_attributes={
22 "start_year": 2021, # validated against WORKS_AT schema
23 "department": "Engineering",
24 },
25)

Attribute values must be scalar types: string, number, boolean, or null. Nested objects and arrays are not supported.

Fact triplet metadata

You can attach metadata to a fact triple, which makes the resulting edge and nodes filterable via episode metadata filters in graph search. See Episode metadata for full details on metadata constraints and update semantics.

Metadata values must be scalars (string, number, boolean, or null) or non-empty arrays of scalars. A maximum of 10 keys are allowed.

1from zep_cloud.client import Zep
2
3client = Zep(api_key=API_KEY)
4
5result = client.graph.add_fact_triple(
6 user_id=user_id,
7 fact="Alice works at Acme Corp",
8 fact_name="WORKS_AT",
9 source_node_name="Alice",
10 target_node_name="Acme Corp",
11 metadata={"source": "crm_import", "confidence": 0.95},
12)

Specifying Node UUIDs

You can optionally specify source_node_uuid and/or target_node_uuid to control which nodes are used. The behavior depends on whether you provide a UUID:

ScenarioBehavior
UUID provided and node existsUses the existing node with that UUID
UUID provided but node doesn’t existReturns a 404 error. You must omit the UUID to create a new node.
UUID omittedSearches for an existing node by name. If no match is found, creates a new node with an auto-generated UUID.

See the associated SDK reference for complete details on all available parameters.

Field Limits

FieldLimit
factMaximum 250 characters
fact_nameMaximum 50 characters; must be SCREAMING_SNAKE_CASE
source_node_name, target_node_nameMaximum 50 characters each
source_node_summary, target_node_summaryMaximum 500 characters each
created_at, valid_at, invalid_at, expired_atRFC 3339 / ISO 8601 format (e.g., 2024-01-15T10:30:00Z)