Skip to main content

Readiness

Readiness is optional and configured per service with the ready object.

Shape

{
"services": {
"api": {
"cmd": ["go", "run", "./cmd/api"],
"port": 3000,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3000/healthz"
}
}
}
}

Supported types are none, tcp, and http.

When ready is present, ready.type is required.

No readiness

You can omit ready entirely, or set it explicitly to type: "none":

{
"services": {
"worker": {
"cmd": ["go", "run", "./cmd/worker"],
"ready": {
"type": "none"
}
}
}
}

Confirmed behavior:

  • daemon service: FlowLayer does not poll; a successful spawn is enough to continue
  • oneshot service: FlowLayer waits for exit code 0; there is no extra readiness probe
  • when type is none, FlowLayer clears ready.url and ready.port

Use none when process start itself is your success condition.

TCP readiness

type: "tcp" checks whether a TCP connection can be opened.

{
"services": {
"db": {
"cmd": ["postgres", "-D", "./var/db"],
"port": 5432,
"ready": {
"type": "tcp"
}
}
}
}

Rules:

  • ready.port must be greater than 0
  • if ready.port is omitted and service.port > 0, FlowLayer uses service.port
  • the probe connects to 127.0.0.1:<port>

TCP readiness is a good fit when "listening on the port" is the right local-ready signal.

HTTP readiness

type: "http" performs a GET request to ready.url.

{
"services": {
"api": {
"cmd": ["go", "run", "./cmd/api"],
"ready": {
"type": "http",
"url": "http://127.0.0.1:3000/healthz"
}
}
}
}

Rules:

  • ready.url is required for HTTP readiness
  • a response with status 200 to 399 counts as ready
  • probe transport errors do not fail the probe immediately; FlowLayer keeps polling

Use HTTP readiness when the process can listen before the application is actually usable.

Polling and timeout behavior

Readiness polling is fixed in the current implementation:

  • FlowLayer polls every 200 ms
  • each HTTP request and TCP dial uses a 200 ms attempt timeout
  • there is no user-configurable per-service readiness timeout

Startup behavior depends on service type:

  • daemon service: FlowLayer waits until the probe passes or the process exits
  • oneshot service with readiness: FlowLayer waits for exit code 0, then keeps probing in the background until ready or session shutdown

If a daemon exits before becoming ready, startup fails instead of continuing to later waves.

Ready vs running

FlowLayer tracks process startup and readiness separately.

Practical rule:

  • running means the process launch or start phase succeeded
  • ready means the readiness gate, if any, has passed

Without a readiness probe, a service can move straight from startup to ready. With a readiness probe, there can be a gap between "the process exists" and "the service is ready for dependents."

That distinction matters most when you use Waves to gate API services behind databases, migrations, or background dependencies.

Common pitfalls

  • ready.type: "http" without ready.url fails validation.
  • ready.type: "tcp" needs either ready.port or service.port.
  • TCP readiness always probes 127.0.0.1, so it is for local listeners.
  • There is no built-in config knob for "fail if not ready after N seconds". A daemon that keeps running but never becomes ready will keep the wave waiting.
  • The readiness schema is intentionally small. If you need custom logic, put that logic behind an HTTP endpoint or a command wrapper and keep FlowLayer on none, tcp, or http.

See Waves for startup sequencing and First Stack for a minimal server config.