Automation and scripting¶
Every OpenTernary command is designed to be driven by scripts, CI and other programs, including the TUI, which is itself a client of these contracts. The full specification is in the output reference. This guide shows how to use it.
--json: one result on stdout¶
With --json, stdout contains exactly one line: a versioned JSON document. Everything for humans (progress,
tables, logs, errors) goes to stderr. In a terminal you see both, so the output can look doubled, but a
redirect captures only the JSON:
{"schema_version": 1, "command": "quantize", "exit_code": 0,
"execution": "completed", "artifact_validation": "file_integrity", "quality_acceptance": "not_run",
"result": {"run_dir": "…/runs/ev", "run_id": "e67150d8", "artifact": "…/runs/ev/artifacts/snapshot", …},
"error": null}
exit_coderepeats the process exit code.execution,artifact_validationandquality_acceptanceare the separate verdicts.resultis command-specific.errorisnullon success, otherwise the error message.
The JSON content never depends on the display language (OPENTERNARY_LANG).
# shell: read fields with jq
openternary plan ./my-model --json | jq -r '.result.targets | map(select(.quantizable)) | length'
--quiet suppresses progress output but still prints the result and any error.
Exit codes¶
| Code | Meaning | Typical cause |
|---|---|---|
0 |
success: the operation or plan finished | |
1 |
unexpected runtime or I/O failure | a bug, a full disk |
2 |
invalid configuration, argument or input | unknown config key, missing file, mismatched runs in compare |
3 |
unsupported capability | quantize --device cuda, a plugin with the wrong API version |
4 |
a required dependency is missing | quantizing without PyTorch |
5 |
required hardware or device unavailable | setup's GPU test failed |
6 |
explicit acceptance criteria not met / no feasible candidate | compare --require-acceptance, optimize |
7 |
search budget exhausted | optimize |
130 |
interrupted | Ctrl+C, TUI cancel |
Exit code 6 still means the command ran: execution is completed, and the verdict was negative.
Progress events: --events-jsonl¶
Each line of the file is one event:
{"schema_version": 1, "sequence": 1, "command": "quantize", "run_id": null, "stage": "started", "completed": null, "total": null, "unit": null, "elapsed_s": 0.0, "detail": {}}
{"schema_version": 1, "sequence": 2, "command": "quantize", "run_id": "e67150d8", "stage": "run_created", "completed": null, "total": null, "unit": null, "elapsed_s": 0.004, "detail": {}}
{"schema_version": 1, "sequence": 3, "command": "quantize", "run_id": "e67150d8", "stage": "convert.tensors", "completed": 0, "total": 21, "unit": "tensor", "elapsed_s": 1.776, "detail": {"tensor": "lm_head.weight"}}
…
{"schema_version": 1, "sequence": 8, "command": "quantize", "run_id": "e67150d8", "stage": "completed", "completed": null, "total": null, "unit": null, "elapsed_s": 1.842, "detail": {}}
- The file must not exist yet, so that two runs cannot write into one file by accident. Its parent folder is created if needed.
--events-jsonlcannot be combined with--dry-run, because a dry run writes nothing.sequenceincreases by one per event. Unknown totals and units arenull.- Fine-grained stages are throttled to at most 5 lines per second per stage, but the first line and the line with
completed == totalare always written.
Every stage, with its unit and detail fields, is listed in the progress events reference. To follow a long run from another terminal:
Non-interactive behaviour¶
OpenTernary never waits for input when nobody is there to answer:
- Installs need
--yes.setupandupdaterefuse to install anything without--yeswhen stdin is not a terminal or--jsonis used (exit code2). - No TUI in pipes. With no arguments,
openternaryopens the TUI only when stdin and stdout are both terminals andCIis not set. Otherwise it prints help and exits with2.OPENTERNARY_NO_TUI=1forces this behaviour everywhere. - No network surprises. The only automatic network request is the daily PyPI version check in an interactive
doctoror the TUI. SetOPENTERNARY_NO_UPDATE_CHECK=1to turn it off. Models are never downloaded.
Example: GitHub Actions¶
Model-free checks run on any runner without a GPU, PyTorch or model download:
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v9
- run: uv tool install openternary --python 3.12
- run: openternary doctor --json > doctor.json
- run: openternary calibrate --config configs/my-calibration.yaml --dry-run --json
A validation job on a self-hosted GPU runner can then run the real pipeline and gate on quality:
set -e
openternary quantize "$MODEL" --config quant.yaml --output runs/candidate --json > /dev/null
openternary quality runs/candidate/artifacts/snapshot --data quality.json --device cuda --output runs/candidate-q --json > /dev/null
openternary compare runs/baseline-q runs/candidate-q --require-acceptance --output runs/gate --json > gate.json
# exit code 6 here fails the job when the candidate does not pass
From Python¶
The same services are available without a subprocess. See the Python API: