CLI
Serve, worker and migrate
Run your application in production with mayura serve, mayura worker and mayura migrate, and write the module they load.
In production a Mayura application usually runs as separate processes: one or more servers that answer HTTP requests, one or more workers that advance durable workflows, and a migration step that updates the storage schema before new code starts. The CLI runs each of these from one application module that you write:
mayura migrate --app dist/src/app.js
mayura serve --app dist/src/app.js
mayura worker --app dist/src/app.js --probe-host 0.0.0.0 --probe-port 9090The CLI owns only the process lifecycle: starting, handling Ctrl+C or SIGTERM, graceful shutdown and worker probes.
Your module wires its own storage, agents, workflows and authentication with its own installed mayura package.
The application module
The module's default export is an object with up to four functions. Wrap it in defineMayuraApplication from
mayura/cli, which checks the shape:
import { defineMayuraApplication } from 'mayura/cli';
import { listenProductionServer } from 'mayura/server-node';
import { createWorkflowWorker } from 'mayura/workflows';
// openServices, agents, authenticate and workerUnits stand for your own code: storage, agent
// registrations, token verification and the workflow runtimes the worker drives.
let services: Promise<Services> | undefined;
const ready = () => (services ??= openServices());
export default defineMayuraApplication({
async server() {
await ready();
return listenProductionServer({
publicOrigin: 'https://agents.example.com',
hostname: '0.0.0.0',
port: 8080,
tls: { terminatedBy: 'proxy' },
agents,
authenticate,
});
},
async worker() {
return createWorkflowWorker({ units: workerUnits(await ready()) });
},
async migrate() {
await ready();
return { schemaVersion: 1 };
},
async shutdown() {
if (services) await (await services).close();
},
});| Function | Used by | Must return |
|---|---|---|
server() |
mayura serve |
A running server with isAccepting() and close(), such as the result of listenProductionServer from mayura/server-node |
worker() |
mayura worker |
A worker with start(), isReady() and drain(), such as the result of createWorkflowWorker from mayura/workflows |
migrate() |
mayura migrate |
Any JSON-serializable report, printed when the migration finishes |
shutdown() |
all three | Nothing. Runs last, after the server closes, the worker drains or the migration ends, for example to close storage. |
Define at least one function, and only these four. A command whose function is missing fails with INVALID_CONFIG,
for example mayura worker on a module without worker.
--app must name a single regular .js or .mjs file: build your TypeScript first. Directories, symbolic links and
other file types are refused, and the CLI imports nothing else. For a complete, working module, see src/app.ts in
any starter, such as
approval-workflow.
mayura serve
mayura serve --app <module> calls server() and keeps it running. On the first Ctrl+C or SIGTERM it closes the
server gracefully, then calls shutdown(). A second signal during shutdown forces the process to exit.
listenProductionServer stops accepting new requests, fails its readiness check so load balancers stop routing to
it, and drains in-flight requests before it closes. See Deployment.
mayura worker
mayura worker --app <module> calls worker(), then start(). On the first Ctrl+C or SIGTERM it drains the
worker, waiting for in-flight work to finish, then calls shutdown().
| Option | Meaning |
|---|---|
--drain-timeout-ms <ms> |
How long draining may take, from 1 to 300000. Default 30000. |
--probe-port <port> |
Serve HTTP health probes on this port. Off by default. |
--probe-host <host> |
The address the probes listen on. Default 127.0.0.1. |
With --probe-port, the worker answers two probes for your orchestrator:
GET /livezreturns 200 while the process is running, and 503 once it starts stopping.GET /readyzreturns 200 while the worker reports itself ready, and 503 when it does not, for example while it is draining. A worker with a leadership lease is ready only while it can confirm the lease in storage.
The probe listener comes from mayura/server-node in your application's own installation, so mayura must be
installed alongside the module. In a container, use --probe-host 0.0.0.0 so the orchestrator can reach it.
When the worker stops, it reports whether the drain finished in time, and how much work was interrupted if it did not.
Run once
With --once, the worker advances everything that is due and exits, instead of running until it is stopped. Use it
from a scheduled job (a Cloud Run job, a Kubernetes CronJob) or a scheduler that starts a process every minute:
mayura worker --app dist/src/app.js --once --budget-ms 50000It takes the leadership lease once, runs passes until every host has completed a sweep of its runs or the budget runs
out, then releases the lease and calls shutdown(). A pass that has started finishes, so keep the budget below your
platform's time limit by at least your longest tool's timeoutMs. Each host keeps its place in storage, so if a sweep
does not finish, the next run continues from there rather than starting over.
| Option | Meaning |
|---|---|
--once |
Advance what is due, then exit. Not with --probe-port, --probe-host or --drain-timeout-ms. |
--budget-ms <ms> |
Stop starting passes after this long, from 1000 to 3600000. Default 60000. |
It prints a report and its status:
| Status | Meaning | Exit code |
|---|---|---|
succeeded |
Every host completed a sweep. | 0 |
incomplete |
The budget ran out first; the next run continues. Run it more often or give it more time. | 0 |
standby |
Another replica holds the lease, so this run did nothing. | 0 |
held |
An operator holds the fleet, so nothing was driven. | 0 |
failed |
A pass failed, for example because storage was unavailable. | 1 |
The worker must come from createWorkflowWorker, whose runOnce this calls; in your own code, such as a scheduled
function, call worker.runOnce({ budgetMs }) directly.
mayura migrate
mayura migrate --app <module> calls migrate() once, then shutdown(), and exits. Nothing else starts. Run it
before deploying new code, for example as a release step or an init container:
mayura migrate --app dist/src/app.js --jsonMigrations are explicit and one-way. Mayura's storage refuses data in a format or schema version it does not support; see Storage.
Output
In a terminal each command prints one line per stage:
● Worker started, probes on 0.0.0.0:9090. Press Ctrl+C to stop.
● Stopping… (press Ctrl+C again to force)
✔ StoppedWhen output is piped, as in most deployments, each stage is one JSON line, then the command's final JSON document:
{"event":"worker-started","probe":{"hostname":"0.0.0.0","port":9090}}
{"event":"stopping"}
{"event":"stopped","drained":true,"interrupted":0}serve prints serving, stopping and stopped events. migrate prints { "status": "migrated", "report": <report> },
where the report is whatever your migrate() returned. See CLI overview for exit codes and errors.
Good to know
serveandworkerdo not load.env. Set configuration in the real process environment. Only mayura dev reads.env.- Run servers and workers as separate processes. With a leadership lease (
createWorkflowLeadershipfrommayura/workflows), several worker replicas can run without driving the same workflows twice.