Backend plugin · listing tai42/backend-celeryInstall
Permissions
Provides
celery
Backend — Celery execution backend — worker/beat/flower runtime, prefork-pool turnover on worker-bus ops, and RedBeat scheduling.
Choose Celery when you already run RabbitMQ, or when you want beat and flower.
The broker, result backend, and RedBeat schedule store are configured
independently.
Enable it
manifest.yml
tai serve, tai backend, a config reload and a config write all
refuse it unless TAI_BUS_REDIS_URL and HOOKS_REDIS_URL are set (each falls back
to TAI_DEFAULT_REDIS_URL). The backend process matches the events it raises
against its own hooks registry, so an in-memory one would run none of the hooks
registered through the server.
Configuration
TheCELERY_ env group. CELERY_BROKER_URL, CELERY_RESULT_BACKEND, and
CELERY_REDBEAT_REDIS_URL each fall back to the shared TAI_DEFAULT_REDIS_URL
when unset — with the default set and CELERY_BROKER_URL unset, the broker
resolves to Redis (a first-class Celery broker) rather than the AMQP default. An
operator on RabbitMQ sets CELERY_BROKER_URL explicitly.
CELERY_REDBEAT_REDIS_MAX_CONNECTIONS falls back to
TAI_DEFAULT_REDIS_MAX_CONNECTIONS the same way. The retry and socket settings
are read from their own namespace alone.
Redis connections
A Redis result backend takes its retry and timeouts fromCELERY_RESULT_BACKEND_RETRY_ATTEMPTS, …_RETRY_ON_TIMEOUT,
…_SOCKET_TIMEOUT and …_SOCKET_CONNECT_TIMEOUT; a Redis broker takes the
same four under CELERY_BROKER_, for the read and the write side alike; the
RedBeat store takes them under CELERY_REDBEAT_. A knob left unset keeps
celery’s or kombu’s own default. Every retry is logged as a warning, and the
last failure is raised.
- The plugin picks the Redis result backend and broker transport classes
itself, so a URL from
CELERY_RESULT_BACKEND,CELERY_BROKER_URL,CELERY_BROKER_READ_URLorCELERY_BROKER_WRITE_URLgoes through them alike.CELERY_LOADERand--loaderdo not change the app’s loader. - The Redis URL forms taken are
redis://,rediss://andredis+socket://(andredis://,rediss://andunix://forCELERY_REDBEAT_REDIS_URL). Every other Redis form —sentinel://, a;failover list,unix://orsocket://for the result backend or the broker,redis-cluster://orredis-sentinel://for RedBeat — stops the app at startup with an error naming the setting. A non-Redis broker or result backend (AMQP,rpc://, …) is left to celery, with celery’s own retries. - With
RETRY_ATTEMPTSunset, a Redis connection makes one attempt: a result write fails on the first error, a worker whose Redis read side is not reachable at start exits, and a publish over a Redis write side fails at once. Celery’s own startup connect retry is switched off when the read side is Redis, and its publish retry when the write side is Redis, each side on its own; the namespace’sRETRY_ATTEMPTStakes their place. - A running worker that loses its Redis broker keeps reconnecting through
celery’s own loop (
broker_connection_max_retries), logged at ERROR. CELERY_BROKER_RETRY_ATTEMPTSnever retries a read kombu polls: a worker’s task and fanout reads, and a control command’s reply read (inspect(),control) in any process. A drop there raises at once. A worker’s drop restarts celery’s consumer, which reconnects; a control call fails with the connection error, and a retry of that call reconnects.- A result wait whose Redis result backend drops is reconnected and
re-subscribed by
CELERY_RESULT_BACKEND_RETRY_ATTEMPTS; after each reconnect it reads the stored state of every task it still awaits, so a result written meanwhile still arrives. When that retry runs out, the wait raises the connection error, and the next wait, on any task, connects again, re-subscribes every task still awaited and reads their stored state. - kombu’s own connect step retries once after 2 s, and its channel open
retries within
broker_connection_timeout; neither is logged. - flower’s events, inspector and results go through the plugin’s app and the settings above. Its broker and queue-length views open their own Redis connection per request, outside these settings, with no retry.
Run the processes
Liveness
Theworker and beat processes report the passes of their loop into a
liveness file at TAI_BACKEND_LIVENESS_PATH. tai42-backend-probe (shipped
with tai42-kit) run in a process’s environment exits 0 while the promise its
file holds is not overdue by more than TAI_BACKEND_LIVENESS_GRACE seconds, and
1 otherwise, so it serves as that container’s exec liveness probe. Each process
on one filesystem needs its own TAI_BACKEND_LIVENESS_PATH, and removes its
file when it exits.
workerreports once a second from a consumer bootstep on the timer of Celery’s event loop, so a report proves the loop is passing. While the broker connection is down the loop does not pass, so the file goes stale; the reports resume once the consumer reconnects. Per pool:prefork: the jobs run in the pool children and the loop goes on while they run; a child stuck in a job is the pool’s to end, byCELERY_TASK_TIMEOUT.solo: the job runs on the loop’s own thread, so the running job reports once a second from its own asyncio loop. A job that blocks that loop stops the reports. The pool cannot end a job, so a job pastCELERY_TASK_TIMEOUTholds a wedged slot: the worker then reports nothing, so the probe fails and the orchestrator restarts it.threads: the loop goes on while the jobs run in the pool’s threads. Once every thread holds a job pastCELERY_TASK_TIMEOUTthe worker reports nothing; one such job beside free threads keeps it reporting.gevent,eventlet: the timer runs on its own green thread, so there a report proves the process and that thread.
worker -Bembeds a beat, which reports each tick into its own file,<TAI_BACKEND_LIVENESS_PATH>-beat, with its own pid. The worker reports nothing while its beat’s promise is overdue, so the one probe on the worker’s file covers both. When the worker exits, even before it ever reached its broker, it ends its beat and removes both files.beatreports each tick of RedBeat’s scheduler, promising the next within the interval the beat sleeps (at most--max-interval). A beat that cannot hold RedBeat’s lock waits for it before its first tick and reports nothing while it waits, so run one beat (standalone or embedded with-B). The app names the reporting scheduler as itsbeat_scheduler;--scheduleris refused bytai backend workerandtai backend beat.flowerserves HTTP, so it is probed over HTTP instead:GET /healthcheckanswers 200 while Flower serves, without authentication even when Flower’s own auth is on. Under--url_prefix, the path is/<prefix>/healthcheck.
docker-compose.yml
Worker lifecycle
Everything around the Celery worker is the shared backend lifecycle every backend gets, not this plugin’s own: the launch waits for the app’s boot self-resync before the worker consumes (a worker running against a half-built tool registry would fail its jobs permanently), the worker’s blocking run loop runs on a dedicated thread so the process’s event loop stays free for worker-bus ops, the first SIGTERM or SIGINT asks Celery for a warm drain, a repeated one escalates to a cold stop, and the drain is awaited to completion before the process tears down. Celery is the one shipped backend whose workers PERSIST ACROSS JOBS, so it is the one that declares pool turnover: after a fleet op that declares an effect (the tool registry or the compiled-template cache changed) applies, the live manifest is re-exported into the env and the prefork pool is replaced and confirmed before the op reportsapplied: every idle pre-restart child is gone and
the pool is back to size, while a child busy with a job finishes it, exits after it
and takes no new one, so it is not waited for. The turnover’s budget is what is left of
the bus apply window (TAI_BUS_APPLY_TIMEOUT), measured from this worker’s receipt
of the op, less a margin, so a pool that will not come back reports a truthful
failed rather than a guessed timeout.
beat and flower pull no queued work, so they run on the event loop and keep
whatever signal handling their own CLI installs. Nothing else runs in those
processes, so that is deliberate.
Quirks
CELERY_MANIFEST_KEYandCELERY_TOOL_NAME_ARGpin wiring built at boot — a forked child reads the env key it was given and a queued job carries the kwarg name its producer used — so a change to either converges through a process recycle rather than in place: the recycled process exits and the deployment’s supervisor starts it again.- On macOS, prefer the
soloorthreadspool — forking around the system resolver deadlocks. backend_list_failed_tasksis not implemented: Celery keeps no queryable failed-task index.- A dispatched tool runs once: the backend never retries a tool body. A tool that
wants retries declares a
ToolRetryPolicy.CELERY_TASK_TIMEOUTis bound into Celery’stask_time_limitwhen the worker starts, so a change converges through a process recycle. The prefork pool enforces it; thesoloandthreadspools cannot end a job, and there the worker’s liveness stops reporting once every slot holds a job past the budget (see Liveness). - Schedules use the contract’s typed cadence; this backend honours an interval’s
relativeflag and stores schedules in RedBeat’s own format.
See also
- Backends — the contract all three shipped backends implement.
- Schedule a tool — putting work on a schedule.

