Skip to main content

fyrer Task Dependency Graph and Topological Ordering

fyrer builds a directed acyclic graph (DAG) from your depends_on declarations and runs tasks in topological order — dependencies first, as concurrent as possible. You declare what must come before what; fyrer figures out the rest.

depends_on syntax

Each entry in depends_on is either a fully-qualified package:task reference to a task in another package, or a bare task name that refers to a task in the same package.

tasks:
dev:
cmd: bun run dev
depends_on:
- ui:build # another package's task
- check # same package's task

In this example, dev will not start until both ui:build (in the ui package) and check (in the same package as dev) have completed successfully.

Execution order

Execution is streaming, not level-barriered: a task starts as soon as all of its dependencies have succeeded or were cached, up to the concurrency limit. fyrer plan groups tasks into levels for display only.

  • Level 0 — tasks with no unmet dependencies.
  • Level 1 — tasks whose depends_on entries are all in level 0.
  • Level N — tasks whose dependencies are all covered by earlier levels.

Levels are how fyrer plan visualises the graph. At runtime, tasks don't wait for levels to drain. For example, if ui:build finishes before shared:build, then web:build (which depends only on ui:build) starts immediately — it doesn't wait for api:build.

Visual example

Consider a monorepo where api and web both depend on shared library builds:

Level 0 (concurrent): ui:build shared:build
Level 1 (concurrent): api:build web:build # web:build starts as soon as ui:build succeeds
Level 2: web:dev api:dev # dev servers are persistent leaves

ui:build and shared:build start immediately. As soon as ui:build succeeds, web:build is scheduled even if shared:build is still running. web:dev and api:dev start only after their respective build dependencies succeed.

Failure propagation

If a task exits with a non-zero status, it is marked failed. All tasks that transitively depend on it are marked skipped — they are not executed at all. The final summary distinguishes between failed tasks (those that actually crashed) and skipped tasks (those that couldn't run because an upstream dependency failed).

This means a single failure in a foundational task (such as a shared library build) surfaces immediately without wasting time attempting tasks that were never going to succeed.

!

fyrer validates the entire dependency graph at startup before running any task. If a circular dependency is detected — for example, api:build depends on web:build and web:build depends on api:build — fyrer will report an error and exit without executing anything.