Skip to main content

Waves

FlowLayer does not have a manual wave field.

It derives startup waves from the dependsOn graph.

From dependencies to waves

FlowLayer builds a directed graph from services.<name>.dependsOn.

At each planning step, every service with no remaining unresolved dependencies becomes eligible for the next wave. Eligible service names are sorted alphabetically, then emitted as one wave.

Example:

{
"services": {
"cache": {
"cmd": ["redis-server"]
},
"db": {
"cmd": ["postgres", "-D", "./var/db"]
},
"api": {
"cmd": ["go", "run", "./cmd/api"],
"dependsOn": ["cache", "db"]
},
"worker": {
"cmd": ["go", "run", "./cmd/worker"],
"dependsOn": ["db"]
}
}
}

Computed plan:

[0] cache, db
[1] api, worker

Declaration order does not define the plan. The dependency graph does.

Startup order

FlowLayer executes waves sequentially.

Within a single wave:

  • independent services start concurrently
  • each start is tracked separately
  • FlowLayer waits for the whole wave to finish its start attempt before moving on

That gives you deterministic sequencing between waves without forcing unrelated services to start one by one.

What blocks the next wave

Service kind matters here. In the current schema, kind defaults to daemon, and kind: "oneshot" marks a service that is expected to exit after successful start.

The next wave waits on the current wave's start behavior:

  • daemon service with ready.type: "http" or ready.type: "tcp": waits for readiness or a failed start
  • daemon service with no readiness probe: continues after successful spawn
  • oneshot service with no readiness probe: waits for exit code 0
  • oneshot service with a readiness probe: waits for exit code 0, then continues probing readiness asynchronously

This is why dependsOn plus readiness gives stronger startup gating than dependsOn alone.

Topological ordering details

Wave planning is deterministic:

  • initial eligible services are sorted alphabetically
  • each subsequent eligible set is sorted alphabetically
  • duplicate dependency entries are removed during validation

This keeps repeated runs stable even when the service map order changes.

Cycles and invalid graphs

FlowLayer refuses to start when the dependency graph is invalid.

Common failures:

  • dependsOn references an unknown service
  • a service depends on itself
  • the graph contains a cycle

A cycle fails planning before any service starts, with an error shaped like:

dependency cycle detected among services: [api db worker]

Design advice

Use dependsOn for hard startup requirements only.

If two services can start independently, leave them in the same wave and let FlowLayer launch them together. Over-declaring dependencies makes the plan slower and less parallel than it needs to be.

For probe details, continue with Readiness. For a minimal runnable config, see First Stack.