OptixLog Docs
Getting Started

First Ingest

Generate typed bindings, construct a node, call ingest, and read the IngestResult including node_id, created, and graph edges.

This page walks through a complete end-to-end ingest: generating bindings, constructing a typed node, submitting it to a project, and reading back the result.

Prerequisites

You have completed either the offline quickstart or the live quickstart. You have optixlog_gen.py in your working directory.

Generate bindings

If you have not yet generated bindings, run:

optixlog generate

This writes optixlog_gen.py containing OptixClient, Projects, and one frozen dataclass per node type in your schema.

Construct and ingest a node

ingest_example.py
from optixlog_gen import OptixClient, Projects, SimulationNode

client = OptixClient()  # reads credentials from ~/.optixlog/credentials.toml

# Construct a typed node.
# Fields are validated at construction time — incorrect types are caught by pyright/mypy.
node = SimulationNode(
    config={"mesh": "fine", "resolution": 20},
    solver="fdtd",
    wavelength_nm=1550.0,
    other_param="demo",
)

# Ingest mode 1: let OptixLog position the node using a natural-language prompt.
result = client.project(Projects.GRATING_COUPLER_LAB).ingest(
    data=node,
    prompt="Simulated a grating coupler with a fine FDTD mesh at 1550 nm.",
)

Read the IngestResult

result.project_id    # str  — the project this node was written into
result.node_id       # str  — the stable id of the created or updated node
result.created       # bool — True if a new node was created, False if an existing node was updated
result.origin_nodes  # list[EntityRef] — nodes that this node flows from (inputs)
result.forward_nodes # list[EntityRef] — nodes that this node flows to (outputs)

origin_nodes and forward_nodes contain EntityRef values with .type, .id, and optionally .name.

Ingest mode 2: explicit graph edges

If you know which existing nodes this node should connect to, pass them directly instead of a prompt:

result = client.project(Projects.GRATING_COUPLER_LAB).ingest(
    data=SimulationNode(config={"mesh": "fine"}, solver="fdtd"),
    origin_nodes=["node-id-a", "node-id-b"],
    forward_nodes=["node-id-c"],
)

origin_nodes are the upstream (input) nodes; forward_nodes are the downstream (output) nodes.

Exactly two ingest shapes

The two shapes — data + prompt and data + origin_nodes + forward_nodes — are mutually exclusive. Passing both prompt and origin_nodes/forward_nodes at the same time matches no overload and is a static type error.

Type safety

Passing a node type to a project that does not include it in its schema is a static type error:

from optixlog_gen import OptixClient, Projects, LayoutNode

client = OptixClient()

# LayoutNode is only in MODULATOR_PROGRAM's schema, not GRATING_COUPLER_LAB.
# This line is a pyright reportArgumentType / mypy [call-overload] error:
client.project(Projects.GRATING_COUPLER_LAB).ingest(
    data=LayoutNode(gds_path="x.gds"),
    prompt="wrong project",
)

See Typed Codegen for how this works.

Working directly with SDKClient and Pipeline

You can also ingest without generated bindings by using SDKClient and Pipeline directly. This is useful when you need runtime flexibility or are building tooling that cannot generate bindings ahead of time.

raw_ingest.py
from optixlog import SDKClient
from optixlog.pipeline import Pipeline

client = SDKClient(
    base_url="https://optixlog.leidos.com",
    api_key="sk-opt-xxxx",
)
pipeline = Pipeline(client)

project = pipeline.project("proj_grating_7f3a")

# You must supply a class that implements IngestibleNode (has to_payload()).
# Node classes from optixlog_gen already do this.

In practice, you will almost always use the generated OptixClient — it is safer and gives you autocomplete and type checking.

Next steps

On this page