Workers
Breaking changes may occur without a major version bump until the API is stable. See known limitations for current gaps.
Workers handle background processing outside the request-response cycle. Workflow step execution, cron-based scheduling, and long-running tool calls all run in workers, keeping the API responsive.
By default, workers run in the same process as the API. For production workloads, you can split them into separate processes or containers and scale each one independently.
When to use workersDirect link to When to use workers
Workers matter when any of these apply:
- Workflow steps take more than a few seconds and shouldn't block API responses
- You need event durability so in-flight work survives process restarts
- Different parts of the system need to scale independently (e.g., more orchestration capacity without more API instances)
- Background tool calls should run on dedicated compute
If your application handles light traffic and workflows complete fast, the default in-process setup works fine. Skip the worker infrastructure until you need it.
Worker typesDirect link to Worker types
Mastra has three built-in worker types. Each handles a specific kind of background processing.
Orchestration workerDirect link to Orchestration worker
Subscribes to workflow events on the PubSub bus and executes workflow steps. Every workflow.start, step transition, and lifecycle event flows through this worker.
In a split deployment, the orchestration worker pulls events from a distributed PubSub backend and delegates step execution back to the API over HTTP. In-process, it runs steps directly.
The orchestration worker requires a PubSub backend that supports pull mode (e.g., RedisStreamsPubSub or GoogleCloudPubSub).
Scheduler workerDirect link to Scheduler worker
Polls storage for due cron schedules and publishes workflow.start events. It's a producer only, meaning it creates work for the orchestration worker to pick up.
The scheduler reads declarative schedule fields from your workflow definitions automatically. See Scheduled workflows for how to declare schedules.
Don't run more than one scheduler instance. Multiple schedulers polling the same storage would fire duplicate events for the same schedule.
Background task workerDirect link to Background task worker
Executes agent tool calls marked with background: { enabled: true }. When an agent invokes a background tool, the API dispatches the task to this worker instead of blocking the response stream.
The background task worker manages concurrency limits, task lifecycle, and result delivery through the PubSub bus.
How workers runDirect link to How workers run
In-process mode (default)Direct link to In-process mode (default)
With no configuration, Mastra creates and starts workers inside the API process. Events flow through an in-memory PubSub, and everything shares a single Node.js runtime.
import { Mastra } from '@mastra/core/mastra'
export const mastra = new Mastra({
// Workers run in-process by default.
// No pubsub or worker config needed.
})
This setup needs no external infrastructure beyond your storage adapter. It doesn't survive process crashes, and you can't scale individual components.
Split processesDirect link to Split processes
To run workers in their own processes, configure a distributed PubSub backend and use the MASTRA_WORKERS environment variable to control which workers start in each process.
- Redis Streams + PostgreSQL
- Google Cloud Pub/Sub + LibSQL
import { Mastra } from '@mastra/core/mastra'
import { RedisStreamsPubSub } from '@mastra/redis-streams'
import { PostgresStore } from '@mastra/pg'
export const mastra = new Mastra({
storage: new PostgresStore({
connectionString: process.env.DATABASE_URL!,
}),
pubsub: new RedisStreamsPubSub({
url: process.env.REDIS_URL!,
}),
})
import { Mastra } from '@mastra/core/mastra'
import { GoogleCloudPubSub } from '@mastra/google-cloud-pubsub'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
storage: new LibSQLStore({
url: process.env.DATABASE_URL!,
}),
pubsub: new GoogleCloudPubSub({
projectId: process.env.GCP_PROJECT_ID!,
}),
})
Any supported storage backend works. Swap the storage adapter for your preferred database.
Run the same build artifact in multiple containers, each with a different MASTRA_WORKERS value to control which worker starts in each process.
Split deployments require a distributed PubSub backend (RedisStreamsPubSub or GoogleCloudPubSub), a shared storage backend, and network connectivity between the orchestration worker and the API.
Select workersDirect link to Select workers
Set MASTRA_WORKERS to control which workers run in each process:
| Value | Behavior |
|---|---|
false | Disable all workers. Use this for the API process in a fully split deployment. |
orchestration | Start the orchestration worker. |
scheduler | Start the scheduler worker. |
backgroundTasks | Start the background task worker. |
orchestration,backgroundTasks | Start multiple workers from a comma-separated allowlist. |
You can also pass a worker name to the CLI. The command sets MASTRA_WORKERS in the spawned process:
mastra worker start orchestration
Network architectureDirect link to Network architecture
Workers are internal infrastructure. They're not exposed to end users and don't need their own subdomain or public URL, including an inbound HTTP route.
In a split deployment:
- The API server is the only public-facing process: It serves all client HTTP requests. These requests include REST endpoints and agent interactions, plus workflow triggers and custom routes.
- Workers connect outbound only: They pull events from the distributed PubSub backend and read/write to the shared storage database. They don't accept inbound traffic from clients.
- The orchestration worker calls the API internally: It sends step execution requests to the API over the container network using
MASTRA_STEP_EXECUTION_URL. This is internal service-to-service communication, not a public endpoint.
All three worker types (orchestration, scheduler, background task) sit behind the API on a private network. They share access to the PubSub backend and storage database but never receive traffic directly from clients. HTTP routes for worker-related features run on the API server rather than the worker process. One example is token minting for a voice integration.
Deploy split workersDirect link to Deploy split workers
Build the API and worker artifacts:
mastra build
mastra worker build --output-dir .mastra/worker
mastra build creates the API artifact in .mastra/output/. mastra worker build creates a worker artifact in .mastra/worker/. The following Dockerfile accepts either directory:
FROM node:22-alpine
ARG MASTRA_OUTPUT=.mastra/output
WORKDIR /app
COPY ${MASTRA_OUTPUT}/package.json ${MASTRA_OUTPUT}/.npmrc* ./
RUN npm install --omit=dev
COPY ${MASTRA_OUTPUT}/ .
EXPOSE 4111
CMD ["node", "index.mjs"]
See Deploy a Mastra server for more information about the build output.
Docker ComposeDirect link to Docker Compose
The following configuration runs PostgreSQL, Redis, the API, and one process for each worker type. Every process uses shared infrastructure, and the worker processes use the worker artifact.
x-worker: &worker
build:
context: .
args:
MASTRA_OUTPUT: .mastra/worker
x-mastra-environment: &shared-environment
DATABASE_URL: postgres://mastra:${POSTGRES_PASSWORD}@postgres:5432/mastra
REDIS_URL: redis://redis:6379
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: mastra
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: mastra
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mastra']
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
api:
build:
context: .
args:
MASTRA_OUTPUT: .mastra/output
ports:
- '4111:4111'
environment:
<<: *shared-environment
WORKER_TOKEN: ${WORKER_TOKEN}
MASTRA_WORKERS: 'false'
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost:4111/api/agents']
interval: 5s
timeout: 3s
retries: 5
orchestration-worker:
<<: *worker
environment:
<<: *shared-environment
MASTRA_WORKERS: orchestration
MASTRA_STEP_EXECUTION_URL: http://api:4111/api
MASTRA_WORKER_AUTH_TOKEN: ${WORKER_TOKEN}
depends_on:
api:
condition: service_healthy
scheduler-worker:
<<: *worker
environment:
<<: *shared-environment
MASTRA_WORKERS: scheduler
depends_on:
api:
condition: service_healthy
background-task-worker:
<<: *worker
environment:
<<: *shared-environment
MASTRA_WORKERS: backgroundTasks
depends_on:
api:
condition: service_healthy
volumes:
pgdata:
Set the secrets next to docker-compose.yml, along with any model provider credentials your application needs:
POSTGRES_PASSWORD=your-secure-password
WORKER_TOKEN=your-shared-secret-token
Configure the API auth provider to accept WORKER_TOKEN before exposing the deployment. The orchestration worker sends the same value through MASTRA_WORKER_AUTH_TOKEN. The scheduler and background task workers don't call the step execution endpoint in this pull-based topology, so they don't need that variable.
Start the stack and verify that the containers and API are available:
docker compose up -d
docker compose ps
curl http://localhost:4111/api/agents
KubernetesDirect link to Kubernetes
Create separate Deployments for the API, orchestration worker, scheduler worker, and background task worker. Use the same image and Secret for each Deployment. Set only the role-specific environment variables directly on each container.
The orchestration worker Deployment has the following shape:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orchestration-worker
spec:
replicas: 1
selector:
matchLabels:
app: orchestration-worker
template:
metadata:
labels:
app: orchestration-worker
spec:
containers:
- name: worker
image: your-registry/mastra-workers:latest
env:
- name: MASTRA_WORKERS
value: orchestration
- name: MASTRA_STEP_EXECUTION_URL
value: http://api:4111/api
envFrom:
- secretRef:
name: mastra-secrets
resources:
requests:
cpu: 250m
memory: 256Mi
Use MASTRA_WORKERS: scheduler and MASTRA_WORKERS: backgroundTasks for the other worker Deployments. Set MASTRA_WORKERS: 'false' on the API Deployment and expose the API with a Service. Give every process access to the same database and PubSub backend. Configure the API auth provider with a worker token, then expose that token to the orchestration worker as MASTRA_WORKER_AUTH_TOKEN. See Deploy Mastra to Kubernetes for the base Kubernetes resources.
Apply the manifests, then verify the pods and API:
kubectl apply -f k8s/
kubectl get pods
kubectl port-forward svc/api 4111:4111
In a separate terminal, request an API route:
curl http://localhost:4111/api/agents
Step execution URLDirect link to Step execution URL
In a fully split deployment, the orchestration worker delegates workflow step execution to the API over HTTP. Set MASTRA_STEP_EXECUTION_URL to the API's internal URL, including the /api prefix:
MASTRA_STEP_EXECUTION_URL=http://api:4111/api
Without this variable, the orchestration worker attempts to execute steps in its own process, which doesn't have access to the full Mastra runtime in a split deployment.
The endpoint uses the server's normal auth pipeline. If the API has an auth provider, set MASTRA_WORKER_AUTH_TOKEN to a bearer token that provider accepts. Mastra forwards the value as an Authorization: Bearer credential. The configured auth provider validates the token. See Worker authentication for server configuration and other credential formats.
Scale workersDirect link to Scale workers
The orchestration and background task workers can scale horizontally. PubSub consumer groups distribute events across their instances:
docker compose up -d --scale orchestration-worker=3
docker compose up -d --scale background-task-worker=2
For Kubernetes, change the Deployment replica count manually or use a HorizontalPodAutoscaler:
kubectl scale deployment/orchestration-worker --replicas=3
kubectl scale deployment/background-task-worker --replicas=2
Run exactly one scheduler worker. Multiple schedulers polling the same storage can publish duplicate events for a schedule.
Crash recoveryDirect link to Crash recovery
A distributed PubSub backend persists unacknowledged events, which lets orchestration and background task workers resume after a restart. When the API is unavailable, a failed step-execution request causes the event to be delivered again. Because an event can be processed more than once, handlers should be idempotent when possible.
The scheduler calculates the next fire time from the current time after it restarts. It doesn't replay schedules that elapsed while it was unavailable.
If the API crashes while a step is executing, that work can be lost and the workflow run can remain in a running state. See known limitations and durable agent crash recovery.
Known limitationsDirect link to Known limitations
- No dead-letter queue: Failed events are nacked and retried, but there's no DLQ for events that fail after all retries.
- No built-in health endpoint: Workers don't expose an HTTP health check. Use container-level liveness probes or process monitoring.
- Scheduler is single-instance: Running multiple scheduler processes causes duplicate schedule fires.
- Runs stuck in "running" after API crash: If the API process crashes while executing a workflow step, the run remains in
runningstatus with no automatic retry. For durable agents, setrecovery.durableAgentsto'auto'in the Mastra config to automatically re-drive orphaned runs on server restart. See Crash recovery for details.
RelatedDirect link to Related
- Worker authentication: Secure worker-to-API communication
- Workers reference: Details about worker environment variables and types, with a list of supported storage backends
- CLI reference:
mastra worker buildandmastra worker start - PubSub: Event delivery backends
- Scheduled workflows: Declare cron schedules on workflows