Workflow Graph
Nodes, edges (origin vs forward), labels, properties, attachments, and how ingest builds and connects the graph.
The workflow-node graph is the core data structure in OptixLog. It captures the flow of work through your engineering process as a directed graph of typed nodes.
Nodes
A workflow node is a typed record of a unit of work: a simulation run, a measurement, a layout file, an analysis report. Each node belongs to exactly one project.
A node has the following components:
node.id # str — stable node id
node.display_name # str — human-readable name
node.label_ref # EntityRef | None — the node's type label
node.properties # dict[str, str] | None — key/value metadata
node.attachments # list[FileRef] | None — associated files
node.parent_refs # list[EntityRef] | None — upstream nodes (origin edges)
node.child_refs # list[EntityRef] | None — downstream nodes (forward edges)
node.created_by_ref # EntityRef | None — who created this node
node.created_at # datetime
node.updated_at # datetimeIncludes required for related data
properties, attachments, parent_refs, and child_refs are None unless you request them with include=["properties", "attachments", "parents", "children"]. See Pagination, Filtering, and Includes.
Labels
A label identifies the type of a node within a project (e.g. SimulationRun, MeasurementResult, LayoutFile). Labels are defined in the project's schema and are accessible through project.workflow_nodes.labels.
# List all labels in a project:
for label in project.workflow_nodes.labels.iter():
print(label.id, label.name)
# Filter nodes by label:
nodes = project.workflow_nodes.list(label="SimulationRun")Properties
Properties are a dict[str, str] of key-value metadata attached to a node. They capture scalar attributes like solver type, wavelength, run ID, or resolution. Properties are defined by the node type's schema fields and populated by the to_payload() method of the generated node dataclass.
Filter nodes by property value using has_property:
# Nodes where solver == "fdtd":
nodes = project.workflow_nodes.list(has_property={"solver": "fdtd"})
# Combine with label filter (AND logic):
nodes = project.workflow_nodes.list(
label="SimulationRun",
has_property={"wavelength_nm": "1550.0"},
)Attachments
Attachments are files associated with a node. Each attachment is a FileRef:
file_ref.id
file_ref.name
file_ref.uri
file_ref.mime
file_ref.size
file_ref.role # str | None — semantic role (e.g. "result", "config")
file_ref.slot # str | None — named slot in the node schemaAttachments are populated when you request include=["attachments"].
Edges: origin and forward
The graph's directed edges connect nodes in the direction data flows:
- Origin edges (
parent_refs) — the upstream nodes that produced or fed into this node. If this node is a simulation result, its origin nodes might be the config file and the layout that were inputs. - Forward edges (
child_refs) — the downstream nodes derived from this node. If this node is a simulation result, its forward nodes might be analysis reports that consumed it.
When you ingest a node, you specify its position in the graph one of two ways:
Mode 1 — natural language prompt. OptixLog resolves the edges from context:
result = project.ingest(
data=sim_node,
prompt="Ran FDTD simulation on the grating coupler layout from this morning.",
)Mode 2 — explicit edges. You supply node IDs directly:
result = project.ingest(
data=sim_node,
origin_nodes=["layout-node-id"],
forward_nodes=["analysis-node-id"],
)The IngestResult always reflects the resolved edges:
result.origin_nodes # list[EntityRef] — resolved upstream nodes
result.forward_nodes # list[EntityRef] — resolved downstream nodesReading the graph
# Get a node with all related data:
node = project.workflow_nodes.get(
"node-id",
include=["label", "properties", "attachments", "parents", "children"],
)
# Walk origin edges:
for parent in node.parent_refs or []:
parent_node = project.workflow_nodes.get(parent.id, include=["properties"])
# Iterate all simulation nodes:
for node in project.workflow_nodes.iter(label="SimulationRun"):
print(node.id, node.display_name)Ingest side effects
ingest creates or updates a node in the graph:
- If a node matching the payload already exists, it is updated in place and
IngestResult.createdisFalse. - If no matching node exists, a new node is created and
IngestResult.createdisTrue. - Either way, the node's edges are resolved and stored.