OptixLog Docs
Concepts

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          # datetime

Includes 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 schema

Attachments 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 nodes

Reading 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.created is False.
  • If no matching node exists, a new node is created and IngestResult.created is True.
  • Either way, the node's edges are resolved and stored.

On this page