lynx-runner with the Basilisk engine, streaming telemetry, and retrieving per-job trajectory artifacts.
The scenario used here is scenario_basic_orbit_lynx.py.
What Lynx handles for you
You bring the simulation. Lynx handles everything around it.
Lynx invokes your scenario as a subprocess. Your scenario reads its parameters, runs, writes results, and exits — that’s the full contract.
How the runner launches your scenario
For each job, the Basilisk engine plugin inlynx-runner does the following:
- Creates a per-job output directory
- Starts a gRPC telemetry server on a dynamic local port
- Spawns your Python entrypoint as a subprocess with these environment variables injected:
Where the runner places the run configuration file on disk is an internal detail. Your scenario reads the path from
TETRYX_RUN_CONFIG and does not need to know or hardcode the backing location.
- Waits for the process to exit
- If an early termination condition fires, sends SIGKILL to the subprocess immediately
- Collects output files from
TETRYX_OUTPUT_DIRand attaches them to the job result
The run configuration
TETRYX_RUN_CONFIG points to a JSON file with this shape:
parameters keys and values come directly from the dispersions you defined in your batch config. Lynx samples them using the method you specified (Latin Hypercube Sampling in this tutorial).
The scenario
scenarios/aerospace/python/scenario_basic_orbit_lynx.py reads the run config, builds a headless Basilisk simulation, steps through the orbit, and emits scalar telemetry at each time step.
Reading the run config:
TETRYX_TELEMETRY_URL is not set the client silently no-ops. Your scenario will still run and produce artifacts.
Writing artifacts:
The scenario writes three files to TETRYX_OUTPUT_DIR before exiting:
These become the
output_files attached to the job result, retrievable via GET /jobs/{job_id}/results.
Dispersed parameters
The tutorial config (scenarios/aerospace/configs/basic_orbit_tutorial.yaml) disperses these orbital elements using uniform distributions:
Each job in the batch receives a unique point sampled from this 7-dimensional space via Latin Hypercube Sampling. With LHS, 50 runs give better coverage than 50 purely random draws.
Runner configuration
The runner needs to know which Python executable to use, where the scenario entrypoint lives, and where to write per-job output. These are set in the runner’s YAML config file. A minimal config for this tutorial (configs/lynx-runner-basilisk.yaml):
Start a Basilisk runner
Submit the batch
Monitor progress
Retrieve results
Once the batch status isCompleted, fetch all job results:
output_files paths are on the runner’s local filesystem. Artifact upload to S3 (and retrieval via the API) is a planned feature.
Early termination
Early termination lets you kill a simulation mid-run when a condition is met, without waiting for the job timeout. The runner’s watchdog monitors telemetry and sends SIGKILL to the subprocess if a condition fires. To enable it in the batch config:orbit.radius_km drops below 6371 km (Earth’s mean radius), the runner kills the subprocess and marks the job failed.
Adapting this scenario to your simulation
The integration contract your scenario must satisfy:- Read
TETRYX_RUN_CONFIGat startup — this is the path to your sampled parameter set - Write artifacts to
TETRYX_OUTPUT_DIRbefore exiting - Exit 0 on success, non-zero on failure
- Optionally stream scalar telemetry to
TETRYX_TELEMETRY_URLduring execution
What comes next
- Connect a GitHub Actions workflow that submits a batch on push and fails CI if jobs fail
- Expand to 500+ runs with
"sampling_method": "latin_hypercube"and a fixed seed for reproducible studies - Add a second runner type (e.g.
px4_sitl) using the same control plane and API - Enable artifact upload to S3 for cross-machine artifact retrieval (planned)