Understanding the Output
When you run ox run, OxyMake provides structured feedback about what it is
doing and why. This page explains the output formats, using the 3-rule
workflow from Your First Workflow (stats for two
students, plus a summary).
Terminal Output (Default)
By default, OxyMake prints human-readable progress and ends with a canonical summary line (timings will vary):
Resolving 3 jobs (3 to run, 0 cached)
▸ summary — upstream rebuilt
✓ Completed 3/3 in 0.6s (4.8 jobs/s)
3 succeeded
Completed: 3 succeeded, 0 failed, 0 skipped, 0 cancelled (0.6s)
The last line is the canonical summary, always in the same shape:
Completed: N succeeded, N failed, N skipped, N cancelled (<elapsed>)
- succeeded -- jobs that ran and produced their outputs
- failed -- jobs whose command exited non-zero
- skipped -- jobs whose outputs were already up to date (cache hits)
- cancelled -- jobs that did not run because an upstream job failed
A run is successful (exit code 0) when both failed and cancelled are 0.
Cached Jobs
When outputs are already up to date, OxyMake skips the work and reports the
jobs as skipped:
Cache: 3 of 3 job(s) up-to-date, skipping.
Completed: 0 succeeded, 0 failed, 3 skipped, 0 cancelled (0.0s)
On a partial re-run (one input changed), the cached jobs are listed and the summary reflects the split:
Cache: 1 of 3 job(s) up-to-date, skipping.
Resolving 3 jobs (2 to run, 1 cached)
[1/3] ✓ stats-bob [cached]
▸ summary — upstream rebuilt
✓ Completed 3/3 in 0.4s (7.5 jobs/s)
2 succeeded, 1 skipped
Completed: 2 succeeded, 0 failed, 1 skipped, 0 cancelled (0.4s)
Plan Output
Use ox plan to see what may run without executing:
ox plan
Plan: 3 rules, 3 jobs, 2 source files
Targets: results/summary.json
1. [stats-bob] rule=stats -> [results/bob_stats.json]
2. [stats-alice] rule=stats -> [results/alice_stats.json]
3. [summary] rule=summary -> [results/summary.json]
The header reports the totals (N rules, N jobs, N source files), followed by
the requested targets and the concrete jobs, each shown as
[job-id] rule=<rule> -> [outputs].
The plan is an upper bound of what ox run executes with the same tree and
cache settings. A planned upstream rebuilt job may be skipped at runtime if
its producer rebuilds bytes matching hashes recorded under the same cache key
and the consumer's normal cache check passes. A producer whose cache key changed
still forces its consumers, even with identical bytes.
JSON Output (Agent Mode)
Add --json to ox run for structured NDJSON output -- one self-contained
JSON event per line:
ox run --json
{"event":"run_started","schema_version":1,"ts":"2026-10-08T12:00:00Z","run_id":"run-550e8400-e29b-41d4-a716-446655440000","workflow_path":"Oxymakefile.toml","total_jobs":3,"to_run":3,"cached":0}
{"event":"job_planned","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-bob","rule":"stats","wildcards":{"student":"bob"},"resources":{},"dependencies":[]}
{"event":"job_planned","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-alice","rule":"stats","wildcards":{"student":"alice"},"resources":{},"dependencies":[]}
{"event":"job_planned","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"summary","rule":"summary","wildcards":{},"resources":{},"dependencies":["stats-alice","stats-bob"]}
{"event":"job_started","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-bob","executor":"local","reason":"cache_miss"}
{"event":"job_completed","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-bob","duration_ms":209,"outputs":["results/bob_stats.json"],"peak_memory_bytes":12091392,"cpu_time_ms":41,"node":null}
{"event":"job_started","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-alice","executor":"local","reason":"cache_miss"}
{"event":"job_completed","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"stats-alice","duration_ms":200,"outputs":["results/alice_stats.json"],"peak_memory_bytes":12058624,"cpu_time_ms":39,"node":null}
{"event":"job_started","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"summary","executor":"local","reason":"upstream_rebuilt"}
{"event":"job_completed","schema_version":1,"ts":"2026-10-08T12:00:00Z","job_id":"summary","duration_ms":194,"outputs":["results/summary.json"],"peak_memory_bytes":11943936,"cpu_time_ms":37,"node":null}
{"event":"run_completed","schema_version":1,"ts":"2026-10-08T12:00:00Z","total":3,"succeeded":3,"failed":0,"skipped":0,"cancelled":0,"duration_ms":607,"status":"success"}
Use ox run --metadata external.id=123 --metadata team=research to link a
run to an external object. run_started.metadata is an object of string
labels, empty when none were supplied; labels appear once per run, including
fully cached runs, and never affect cache identity. The same object appears
in run records from ox history --json, retained independently of event logs.
See CLI Commands for validation limits.
Each event carries an event discriminant (run_started, job_planned,
job_started, job_completed, run_completed), a schema_version (1) and
a UTC RFC 3339 ts timestamp. job_completed reports the executor's
observation of the job: peak RSS in bytes (peak_memory_bytes), user +
system CPU time (cpu_time_ms) and, for Ray tasks, the host it ran on
(node); each is null when the executor did not measure it. job_started
and job_completed also name where the job ran: endpoint (the local host
name, or the Ray dashboard address) and submission_id (the Ray submission
ID; null for local jobs). Every planned job gets its job_planned line
right after run_started, before any job starts or is skipped. The format is
designed for AI agents and scripts: each line parses independently. Use
--report-json <path> to write the same stream to a file. See
Agent-Driven Workflows for details.
Every executing run, fully cached runs included, also writes this stream to
.oxymake/events/<run-id>.ndjson. A Ray DAG run writes run_started at
submission: its job_completed and job_failed lines are appended when a
task's record is synchronised, by ox run --follow or by a later ox status,
so their ts is the synchronisation time. The file is created before the run is
recorded or any job starts: if it cannot be created (for example, the
directory is not writable), ox run exits 1 without recording the run.
Each executing invocation mints one opaque UUIDv4-based run ID. The same full value names its default event file and appears in state, run artifacts, and executor metadata. Legacy IDs remain valid exact keys. Run IDs do not sort by creation time; use recorded timestamps.
Status and History JSON
ox status --json includes top-level integer "schema_version": 1 in
every emitted object: the current or selected run (--run ID), grouped
summaries (--group-by rule or stage), absent state, and any separate Ray
driver or cluster summary. Ray status can emit several consecutive JSON
objects; consumers should parse a stream of JSON values.
ox history --json includes the same field in each NDJSON run record and
each job record selected by --run-id ID. No state, no runs, or a selected
run with no job history produces an empty stream.
Status and history each have their own schema version, independent of each other and of the run-event journal. They start at 1; keeping them independent lets a snapshot or history format change without forcing event-log consumers to migrate. A non-additive change (removing or renaming a field, changing its type or meaning, or changing the record structure) increments the affected version. Additive fields do not increment it; readers should ignore unknown fields. These payloads remain unstable before 1.0. Error diagnostics on stderr are separate from these success-record schemas.
DAG Visualization
Use ox dag to render the dependency graph. The default format is Graphviz
DOT:
ox dag
digraph oxymake {
rankdir=LR;
"results/summary.json" -> "all";
"stats" -> "results/{student}_stats.json";
"data/{student}.csv" -> "stats";
"summary" -> "results/summary.json";
"results/{student}_stats.json" -> "summary";
}
Other formats:
ox dag --format mermaid # Mermaid graph syntax
ox dag --format dot # Graphviz DOT (same as default)
ox dag --group-by rule # Collapse nodes by field
ox dag --json # Structured JSON
To trace a single target's dependency chain instead, use ox explain:
ox explain results/summary.json
Dependency chain for: results/summary.json
► 1. [summary] rule=summary
inputs: [results/alice_stats.json, results/bob_stats.json]
outputs: [results/summary.json]
2. [stats-alice] rule=stats
inputs: [data/alice.csv]
outputs: [results/alice_stats.json]
3. [stats-bob] rule=stats
inputs: [data/bob.csv]
outputs: [results/bob_stats.json]
Error Output
When a job fails, OxyMake reports the failure, cancels the dependent jobs, and ends with a non-zero exit code:
Resolving 1 jobs (1 to run, 0 cached)
[1/1] ✗ broken FAILED (exit 1)
error: job broken failed: exit code 1
stderr: --- stderr ---
stderr: boom
✗ Completed 1/1 in <0.1s
1 failed
Failed: broken
Completed: 0 succeeded, 1 failed, 0 skipped, 0 cancelled (0.0s)
Failed jobs (showing 1 of 1):
broken: boom
Run 'ox logs --failed' for full details.
ox logs --failed prints the full captured output of each failed job. In
--json mode, the failure is reported as a job_completed event with a
non-success status, so automated tooling can recover programmatically.
Verbosity Levels
Control output detail with -v:
ox run # Normal output
ox run -v # Verbose: job start/end, durations, and exit codes
ox run -vv # Debug: also show each job's stdout/stderr
Next Steps
- CLI Commands -- full command reference
- Execution Modes -- how rules are executed