Your first DAG¶
In this chapter you'll write your first DAG — a workflow of two shell tasks connected by a dependency — then run it with the cronova scheduler and watch it succeed, from both the CLI and the web console.
Create the DAG file¶
A DAG (directed acyclic graph) is a set of tasks with dependency edges, defined as a single YAML file in the ./dags/ directory. Each shell task runs as an OS subprocess.
Create dags/hello.yaml:
dag_id: hello
tasks:
- id: greet
type: shell
command: echo "hello from cronova"
- id: report
type: shell
command: echo "run $CRONOVA_RUN_ID finished greeting"
deps: [greet]
That's a complete, runnable workflow. Let's walk through it line by line.
dag_id¶
The unique identifier for the DAG. It's required, and it's the name you'll use everywhere else — cronova trigger hello, the console's DAG list, the run history.
tasks¶
The list of tasks — also required. Each entry gets an id that must be unique within the DAG.
type¶
The task type. shell runs command as an OS subprocess via sh -c, so anything you can type in a terminal works here. There are four more types — python, sql, jar, and http — covered later in the tutorial.
Tip
shell is the default type, so you could omit these two lines entirely.
We spell it out here so the file reads unambiguously.
command¶
What the task executes. Notice $CRONOVA_RUN_ID: cronova injects run information into every task's environment as CRONOVA_* variables — the run id, the DAG id, the task id, the logical date, and more. Your scripts can use them without any wiring.
deps¶
The dependency edge — this is what makes it a graph. report waits for greet and (by default) runs only after greet succeeds. Edges are cycle-checked when the file loads, so an accidental loop is rejected instead of hanging your workflow.
Start the scheduler¶
cronova serve runs the scheduling loop, the REST API, and the web console in one process:
It loads every *.yaml and *.yml file from ./dags. A malformed file is logged and skipped — it never takes the scheduler down.
Check it — from a second terminal:
hello is registered. You can also open the console at http://localhost:8090 — the DAG list shows hello with the same details.
Trigger a run¶
Note
cronova trigger only creates the run — it writes a run row to the
database and returns immediately. The running cronova serve picks it up
on its next scheduling tick (default every 2s), so execution starts
almost instantly, just not inside the trigger command itself.
Watch it run¶
RUN_ID LOGICAL_DATE STATE TRIGGER TASKS
hello__manual_1783468804512345600 2026-07-07T08:15:04Z success manual greet=success report=success
The run went greet first, then report — exactly the order deps dictates. If you run cronova runs hello quickly enough after triggering, you'll catch the states advancing: queued → running → success.
The same thing in the console¶
Open http://localhost:8090 and you'll find the console equivalents of everything you just did:
- The DAG list shows
hello, with a one-click manual trigger — no terminal needed. - Click into
hellofor the run history and per-task states of each run. - Click a task instance for live log tailing — you'll see
hello from cronovain thegreetlog, and the actual run id printed byreport.
Scheduled vs. manual¶
You may have noticed hello.yaml has no schedule field. That's deliberate: omitting schedule makes the DAG manual-only — it runs when you trigger it (from the CLI, the console, or the API) and never by itself. That's why cronova dags shows (manual) in the SCHEDULE column.
To make cronova run it on its own, add one line — a cron expression or an interval:
dag_id: hello
schedule: "@every 1m" # or a cron expression like "0 2 * * *"
tasks:
# ...unchanged...
Save the file, and cronova dags now shows SCHEDULE=@every 1m — the scheduler creates a new run every minute. Schedules, start_date, and catchup/backfill get their own chapter next.
Warning
A schedule of @every 1m keeps producing runs for as long as serve is
up. For experiments, either remove the schedule line again or pause the
DAG from the console (or cronova pause hello) when you're done.
What you learned¶
- A DAG is one YAML file in
./dags: a requireddag_idplus ataskslist, withdepsdrawing the dependency edges. cronova serveruns the scheduler and the console at http://localhost:8090;cronova dagslists what it loaded.cronova trigger <dag_id>creates a run andserveexecutes it on the next tick;cronova runs <dag_id>and the console's run history show every task's state.- No
schedulefield means manual-only; adding one hands the DAG to the scheduler.
The full field-by-field schema is in the DAG & Task Reference. Next: put hello on a real schedule — cron expressions, intervals, start_date, and catchup — in Schedules.