Skip to main content
The worker bus is how a live change reaches every process in a deployment. When one worker persists a config change — a reloaded tool, a replaced manifest, a soft restart — the bus carries that change to every other worker sharing the manifest and collects a per-worker outcome, so the whole fleet converges instead of one process drifting ahead of its siblings.

It is internal infrastructure, not a plugin

The bus is app-owned internal infrastructure, in the same class as the reload gate. It is not a plugin and sits deliberately outside the plugin system: nothing about it is registrable, swappable, or user-selectable, no manifest field chooses an implementation, and there is exactly one bus. You do not author a bus or name one in a manifest — you configure the single built-in with environment variables, and that is the whole surface. This is a deliberate line. A backend is a plugin — you pick celery, rq, or arq. The bus is not: fan-out is the app’s own concern, so a backend-runtime process receives fleet changes through the bus exactly like a serving HTTP worker, and carries no fan-out surface of its own.

Enable it

The bus runs on a plain Redis, configured entirely through TAI_BUS_* environment variables. Set TAI_BUS_REDIS_URL to turn it on:
With TAI_BUS_REDIS_URL unset the bus is off, and the process runs on a no-op local variant: a mutation applies in that one process and reaches no sibling. That is the correct, supported shape for exactly one deployment shape — a single worker, in file config mode, with no backend — and the wrong shape for every other (see Boot rules below). Any plain, module-less Redis runs the bus — redis:7, redis:8, or valkey. No Redis modules are required.

Boot rules

Three deployment shapes cannot converge without the bus, so tai serve and tai backend refuse to start without TAI_BUS_REDIS_URL when any holds:
  • More than one server worker. Sibling workers would serve stale config after a reload with no channel to converge on. tai serve --workers N (N > 1) is refused busless.
  • A registered backend. A manifest that names a backend_module runs a separate backend-runtime process that must converge with the server processes on every reload. This is re-checked on every reload, so a manifest or env change that adds a backend — or removes the bus while a backend remains — is refused too.
  • TAI_CONFIG_MODE=k8s. The k8s config mode exists for multi-pod shared config, and a pod cannot see its own replica count, so k8s mode always requires the bus.
The refusal names TAI_BUS_REDIS_URL. A busless k8s boot is rejected on the missing bus, not on a later kubeconfig error, because the k8s and workers checks read only boot-fixed env and args and run before the config manager is built.

Namespacing

TAI_BUS_NAMESPACE (default tai) prefixes the control channel, every reply channel, and every presence key. Redis pub/sub is server-global — it is not scoped by the numeric database index — so two deployments sharing one Redis would cross-deliver each other’s fleet ops unless they diverge by namespace.
When several independent stacks share one Redis, give each a unique TAI_BUS_NAMESPACE. Two stacks left on the default namespace deliver each other’s reloads and manifest replaces to the wrong fleet.

What the fleet report tells you

A mutation waits for the fleet and returns a per-origin report — one row per worker with its outcome. Beyond a plain applied/failed, three honest states name a worker the change did not cleanly reach:
  • missing — the worker is alive but never acknowledged the op inside the short ack window.
  • departed — the worker’s presence key expired; it went away (a rolling restart or a SIGKILL mid-reload).
  • timed_out — the worker acknowledged the op but did not finish applying it by the deadline. The op usually still applies once its reload gate frees; verify with the fleet census or a fleet reload.
These states are never hidden. A worker the broadcast could not converge is reported, logged, and surfaced in the UI — a failed propagation is never a fake success.

Documented limitations

The boot rules catch the shapes they can detect from inside one process. Some multi-process shapes look identical to a legitimate single busless process, so the rules cannot flag them for you. In every one of these, set TAI_BUS_REDIS_URL yourself:
  • The workers-count rule lives in the tai CLI only. An external process manager (gunicorn, a supervisor, a container orchestrator) that drives the ASGI factory with its own --workers bypasses the rule entirely — the factory cannot count sibling processes. Set the bus in any multi-process deployment you run outside tai serve.
  • Single-worker fleets look busless. A supervisor running many tai serve -w 1 processes on one host, or several machines sharing one manifest over a network filesystem, pass every boot rule — each process legitimately sees workers=1, file mode, and no backend — yet run busless-stale. Set the bus in any multi-process file-mode deployment.
  • Mixed transports are undetectable. A stdio or sse process sharing a manifest with an http fleet is another shape the rules cannot see.
  • A shared Redis needs a unique namespace. Distinct stacks on one Redis that do not set unique TAI_BUS_NAMESPACE values cross-deliver fleet ops.
The rule of thumb: if two or more processes share a manifest, they need the bus, whether or not a rule fires.

Operational knobs

The timing knobs have safe defaults; change them only for a deployment whose reloads legitimately run long.

See also

  • Live operations — the mutation surface the bus fans out.
  • Manage a fleet — inspecting the fleet and driving fleet-wide operations.
  • Backends — the task-execution plugin, which the bus is deliberately not.
  • Config and secrets — the full environment-variable reference.
  • Deploy — the multi-worker and k8s deployment shapes the boot rules gate.