Running Konjugate from the command line

August 26, 2026

Every page in this series so far has driven the desktop app directly: a window, a canvas, a click. Underneath all of it is the same native simulation engine, and it's reachable on its own, from a plain terminal, with no window involved at all. That's the installed konjugate command (the development build used below is called konjugateEngine), useful for scripting, CI or batch runs across many models at once.

Inspecting and validating a file

inspect reports on the container itself, without touching the model inside it:

$ konjugate inspect thermalManagement.kjt --report report.json
$ cat report.json
{"reportVersion":1,"format":"kjt","containerVersion":1,"encrypted":false}

validate goes further, running the same native validator every save and run in the app itself goes through:

$ konjugate validate thermalManagement.kjt --report report.json
$ cat report.json
{"reportVersion":1,"engineVersion":"0.2.0","valid":true,"summary":{"nodes":3,"edges":3},"issues":[]}

An empty issues array is a clean bill of health. A model with a real problem reports it the same way any other blocking error does elsewhere in the app, just as structured JSON instead of a dialog.

Running a model

run needs a small configuration file of its own, since there's no dialog here to fill in a target time:

{
  "name": "Default",
  "targetTime": 5,
  "globalTimeStep": 0.01,
  "outputInterval": 0.1
}

The same constraints "Running a simulation"'s run dialog enforces apply here too: timestep and output interval can't exceed the target time, and the output interval has to be an integer multiple of the timestep. This same file is also where a backend gets pinned explicitly, automatic, serial, thread-pooled or partitioned, the same four choices "Running a simulation"'s Run configuration dialog offers.

$ konjugate run thermalManagement.kjt --configuration run.json --output result.bin
$ echo $?
0

Nothing prints on success, and result.bin isn't a format meant to be read by hand: it's the same internal binary segment layout the app itself embeds in a .kjt file when a project is saved with its results, packed sample batches and checkpoints rather than plain numbers. That's because this run command is the engine's own low-level contract, the one Konjugate's Electron process spawns internally every time a run happens in the app. It was never meant to be the end-user entry point for automation, which is what the next section actually is.

Getting a portable result

konjugate --cli is a separate, higher-level wrapper around the same engine, one that writes something you can actually do something with afterward:

$ konjugate --cli run thermalManagement.kjt --target-time 5 --output-kjt result.kjt --output-csv result.csv
Running "thermalManagement.kjt" with configuration "Default" for 5s of simulated time...
Wrote result.kjt
Wrote result.csv

--output-csv isn't a separate converter reading the binary segment back out after the fact: it calls the exact same conversion function the app's own Export results as CSV button uses, so a CLI run and a GUI run of the same model agree exactly:

time (s),Battery module — Temperature (K),Battery module — State of charge (%),Enclosed air — Temperature (K),Electrical losses — Heat flow (W)
0,353.2,84.6,293.15,420
0.1,353.16996396696425,84.6,293.1643355289622,420
0.2,353.13998114534144,84.6,293.1786604685886,420

--output-kjt writes the original project back out with that run's result embedded, in the exact same format a Save with results produces in the app. Opening it isn't a hypothetical: this is that file, reopened, with no run required, no editing needed first:

The CLI's --output-kjt file reopened in the app, already in Results mode
RESULTS · MODEL LOCKED, available 5 s · target 5 s, and the exact same node values the CSV above reports at time 0.

Both flags are optional and independent; passing neither still runs the simulation but writes nothing. --target-time is the only thing required beyond the project itself, and the project needs at least one run configuration already saved in it (the same kind "Running a simulation"'s Run configuration dialog creates) for --cli run to know what timestep and backend to use: pointing it at a project with none reports that directly and exits 2, and --configuration picks a specific saved one by name or index when a project has more than one.

--cli validate is the equivalent for a plain check, printing the same kind of report straight to stdout instead of a file:

$ konjugate --cli validate thermalManagement.kjt
{
  "reportVersion": 1,
  "engineVersion": "0.2.0",
  "valid": true,
  "summary": { "nodes": 3, "edges": 3 },
  "issues": []
}

In the development tree this is electron . --cli run ... from the repository root rather than a standalone konjugate binary; a packaged install accepts konjugate --cli run|validate ... directly. Its exit codes are simpler than the low-level table below and don't share meanings with it: 0 for success, 2 for bad usage (a missing --target-time, an unresolvable --configuration, no run configurations at all or validate finding a blocking error), 1 for anything else that failed at runtime.

Recovering structure from data

infer is the one command that doesn't take a .kjt project at all. Its input is a CSV, the same kind of file "Recovering structure from data" imports through the canvas:

$ konjugate infer series.csv --report report.json
$ cat report.json
{"reportVersion":1,"engineVersion":"0.2.0","edges":[{"sourceColumn":"columnA","targetColumn":"columnB","lag":1,"terms":[{"degree":1,"coefficient":29.9108}],"intercept":-0.0516942,"score":0.998751,"provenance":"continuousLagged"}],"selfTerms":[{"targetColumn":"columnB","rate":-9.94899}]}

That coefficient is worth pausing on if it looks unexpectedly large: infer reports a continuous-time rate, not a per-row discrete step, so a relationship written into this particular CSV at roughly 3 per 0.1-second step comes back as roughly 30 per second. The report is keyed by CSV column name, columnA, columnB, because the command has no notion of Konjugate node or state IDs; resolving a candidate into a real node and edge is the caller's job, exactly as it's the reviewer's job in the app's own import flow. --skeleton-threshold, --coefficient-threshold, --validation-fraction, --lags, --ridge-penalties and --degrees tune the same fitting process described in "Recovering a Causal Graph from a System."

Exit codes, not exceptions

This table is the low-level engine's own contract, the one inspect, validate, run and infer all follow. Every one of them reports success or failure through its exit code, meant to be checked by whatever called it rather than parsed from text:

Code Meaning
0 Command succeeded; validation found no errors
2 Validation completed and found blocking errors
3 Input, container or payload is invalid
4 Encryption requires credentials or an unsupported feature
5 Engine execution failed
64 Invalid command-line usage

Two of these are easy to see directly. Pointing any command at a file that isn't a real Konjugate project reports its exact problem and exits 3:

$ konjugate inspect not-a-project.kjt
INVALID_FORMAT: This is not a Konjugate project file.
$ echo $?
3

Running the command with no arguments at all reports its own usage and exits 64:

$ konjugate
Usage: konjugate capabilities | <inspect|validate|run> <project.kjt> [--report report.json] [--configuration run.json --output result.bin --control-stream protobuf]
       konjugate infer <series.csv> --report report.json [--skeleton-threshold X] [--coefficient-threshold X] [--validation-fraction X] [--lags 1] [--ridge-penalties 0.01,0.1,1.0,10.0] [--degrees 1,3]
$ echo $?
64

A protected project needs its password too, and never as a plain command-line argument someone could read out of a process list or shell history: it's read from the inherited KONJUGATE_PASSWORD environment variable instead.

Where to go from here

Nothing here does anything the app itself can't already do. What it adds is running the same checks and simulations without opening a window at all: across many models in a loop, on a machine with no display, or as one more step in a build. "Saving, loading, and project files" covers the same .kjt format from the GUI side.


Follow Konjugate on LinkedIn for updates.

Comments