The CLI
ltl check traces/ --rules rules.ts --out artifacts/It folds the same monitor over recorded traces. There is no second engine — batch checking is the transition run over a file instead of over a live run, which is what makes a CI failure and a runtime verdict mean the same thing.
Arguments
Section titled “Arguments”<trace...> |
Trace files, or directories searched for .jsonl. Verdict streams a previous run wrote are skipped, so a second run does not check its own output. Symlinks are followed, and a link pointing at an ancestor stops rather than recursing. |
-r, --rules |
A .ts/.js/.mjs/.cjs module exporting rules, or a default export containing one; or a .json document. |
-o, --out |
Where the artifacts go. Without it, each verdict stream is written beside its own trace. |
--strict |
Promote unresolved to a failure. |
--no-write |
Report only. |
Exit codes
Section titled “Exit codes”0 |
Clean. |
1 |
Violations — and unresolved under --strict. |
2 |
Usage or configuration error: a rules file that will not load, a directory that cannot be written, a trace that is not JSON. |
Lint warnings go to stderr and never affect the exit code. A warning is advice.
What --strict is for
Section titled “What --strict is for”A trace with no closing run.finish is an incomplete recording — a run that was killed, or one still in flight. An
obligation such a run never closed resolves to unresolved rather than violated, because a trace that simply stops is
not evidence that a property broke.
That is the lenient default: unresolved appears in the summary and in the counts, and the build passes. --strict
promotes it, which is what you want when every run in the corpus is supposed to have completed and a truncated one means
something went wrong upstream.
A trace declares otherwise by carrying its own run.finish; the status inside it decides the outcome, so a run that
genuinely failed or was cancelled is read as such.
Two shapes of imperfect recording are read the way the recorder writes them. A run killed mid-append leaves half an
object on its final line: the check drops that tail and reads the prefix, which is the evidence the trace exists to
preserve — a bad line anywhere earlier is real corruption and still refuses the file as a configuration error. And a
trace with no events at all resolved nothing, so its cells read unresolved.
What a failure looks like
Section titled “What a failure looks like”ltl check — 12 traces, 6 rules
agent-looping LOOP-01 violated at seq 4 (tool.call fetchRates) — loop(n=3) agent-unguarded SEC-01 violated at seq 0 (tool.call restartService) — O toolResult(queryMetrics)
66 satisfied · 5 violated · 0 pending · 1 unresolved (unresolved does not fail; use --strict) wrote 25 files, including artifacts/summary.jsonEach line is one broken rule in one trace, at the event that broke it, with the subterm that failed. A rule is reported once per trace even though its verdict stream carries both the transition into failure and the terminal verdict — printing both would read as one property breaking twice.
In a workflow
Section titled “In a workflow”- name: Check recorded traces run: npx ltl check traces/ --rules rules.ts --out artifacts/
- uses: actions/upload-artifact@v4 if: always() with: name: ltl-artifacts path: artifacts/Upload the artifacts on failure. summary.json names, for every rule and every trace, the status
it ended on and the event that caused it, which is the fastest path from “the build is red” to the line to look at.