Backend plugin · listing tai42/backend-rqInstall
Permissions
Provides
rq
Backend — RQ execution backend — background tool runs and schedules over Redis.
RQ is Redis-only like arq, with a worker, a scheduler, and the RQ dashboard.
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
TheRQ_ env group. RQ_REDIS_URL and RQ_REDIS_MAX_CONNECTIONS fall back to
the shared TAI_DEFAULT_REDIS_URL / TAI_DEFAULT_REDIS_MAX_CONNECTIONS when
unset; a set RQ_ value always wins. The retry and socket settings are read
from RQ_ alone. Every Redis connection of the backend — the tools, the enqueue
path, the worker and the beat — is built from them.
Run the processes
worker runtime runs RQ’s work loop on a dedicated thread, so the process’s
event loop stays free for worker-bus ops. Its lifecycle is the shared one every
backend gets: it waits for the app to finish booting before it consumes, then
SIGTERM (or SIGINT) drains it warm — no new jobs, the current one finishes, the
process exits after teardown — and a second signal kills the running work-horse.
An idle worker stops within one dequeue poll (5s).
beat and dashboard 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.
A waiting worker that loses Redis keeps reconnecting through RQ’s own loop,
logged at ERROR, outside RQ_RETRY_ATTEMPTS.
beat takes rq-scheduler’s own options that are not about the connection
(-b/--burst, -i/--interval, -v/--verbose, --quiet, --path, --pid,
-j/--job-class) and -u/--url. Its connection comes from the RQ_ settings,
like the worker’s: the URL falls back to TAI_DEFAULT_REDIS_URL, and -u/--url
replaces only the URL. The scheduler loop has no reconnect of its own, so the
beat rides a Redis outage only within RQ_RETRY_ATTEMPTS; a longer one ends it.
The dashboard opens its own Redis connection from its --redis-url option,
outside the backend’s connection settings, so the RQ_ retry and timeouts do
not apply to it.
Liveness
Theworker and beat processes report each pass 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 liveness 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 on each RQ heartbeat: every dequeue poll (at most 5s apart) and every job boundary. While a forked work-horse runs a job, the worker’s horse monitor reports; on thesoloandgeventpools, where the job runs in the worker process, a monitor thread reports once a second until the job ends. It is an OS thread undergeventtoo, so a job that holds the CPU does not starve it. Those pools cannot end every job atRQ_TASK_TIMEOUT(one blocked in a C call, or undergeventone that never yields), so the monitor stops reporting once a job runs past it, and the probe ends the worker. A report that fails (the file is owned by another live process, or cannot be written) stops the worker before its next job, and the launch exits non-zero.beatreports each pass of the scheduler loop, promising the next within its-i/--interval.dashboardserves HTTP, so it is probed over HTTP instead:GET /favicon.icoanswers 200 while the dashboard serves; under--url-prefixthe path is/<prefix>/favicon.ico. With--usernameset, that path sits behind the same basic auth as the rest of the dashboard, so the probe sends theAuthorizationheader.
docker-compose.yml
Send a task to a chosen worker
Thesync_task and async_task branches take an optional queue argument, a
label of letters, digits, _ and -. A job sent with queue: gpu goes to the
queue <RQ_QUEUE_NAME>.gpu, its callback with it; any other value is refused
before anything is written, and no queue keeps the job on RQ_QUEUE_NAME. A
preset can fix queue in its arguments like any other argument.
--queue <label> serves that label’s queue alone, and
moves its countdown / eta jobs onto it; a worker without the flag serves
RQ_QUEUE_NAME. A stack that runs labelled tasks runs one more worker per
label. A job on a queue no worker serves waits: a sync_task raises
TimeoutError after RQ_TASK_TIMEOUT, and an async_task id stays queued.
Recurring schedules carry no queue: beat puts them on RQ_QUEUE_NAME, so they
need a worker without a label.
Quirks
- A tool with its own
queueparameter cannot take async_taskorasync_taskbranch. - RQ has no disabled-schedule state, so
backend_list_schedulesalways reports a schedule as enabled, and disabling one deletes it. backend_registered_tasksandbackend_list_failed_tasksare not implemented.- The rq scheduler cannot honour a relative interval: a schedule with
relative: trueis refused on create, update and import. - The worker runs every job — enqueued, or fired by the scheduler — under its own
RQ_TASK_TIMEOUT, whatever timeout the job was stored with, so a change converges through a worker recycle.
See also
- Backends — the contract all three shipped backends implement.
- Schedule a tool — putting work on a schedule.

