Serve mode

mix --serve runs a Mix script as a long-lived, supervised Bus citizen. The runtime registers one service identity, executes the script's initialisation body, and then runs an unconditional event pump for author-defined on handlers and runtime-reserved verbs.

Synopsis

mix [--no-prelude] [--strict-arity] \
  --serve citizen.mix [--name alpha] [--no-prelude]

After the script path, serve mode accepts only --name SERVICE and --no-prelude. It does not pass positional arguments to a served script. $0 is set to the script path.

Service identity

--name takes precedence. Without it, the service name is the script's file stem:

citizen.mix        -> citizen
alpha.worker.mix   -> alpha.worker

A leading mixos- prefix is removed from either the explicit name or derived file stem. Empty names and names beginning with . are rejected before the broker connection. The broker performs the remaining identifier validation.

The registered provenance contains the mix package, version, repository revision, dirty state, build time, and a current RFC 3339 timestamp.

Startup

Serve mode performs these operations in order:

  1. Initialises structured logging.
  2. Reads, lexes, and parses the script.
  3. Resolves the broker URL.
  4. Establishes a supervised connection and registers the service name.
  5. Constructs an evaluator with the serve Bus handler and reserved runtime.
  6. Loads the prelude unless disabled.
  7. Executes the initialisation body and enters the event pump.

An unreadable script, lexer or parser failure, logging failure, runtime creation failure, exhausted initial connection budget, or initialisation error exits non-zero. Initial broker connection failure is fatal; it does not enter a background retry loop before the first registration succeeds.

Supervision

MixServeHandler wraps one SupervisedClient. All requests, emits, replies, registrations, and topic operations use that connection. Serve mode does not route simple names through the transient local-port shortcut.

After registration, a broker interruption is treated as transient. The client reconnects, re-registers the service, and replays its topic-subscription registry. Its incoming receiver survives those reconnects. A terminal receiver close ends the event pump.

Calling bus_reconnect() is unnecessary in serve mode; the supervised client owns recovery.

Runtime-reserved verbs

The runtime checks reserved verbs before author handlers. A script cannot shadow them.

CommandArgumentsResult
HELPNoneJSON array of accepted commands.
INFONoneJSON object containing name, version, and description.
QUITNoneReplies with status 0 and {}, then starts graceful shutdown.
SERVICE.props.getOptional pathReturns the root property snapshot or one value.
SERVICE.props.listNoneReturns all defined lifecycle leaf paths.
SERVICE.props.describeRequired pathReturns type and property metadata for one path.

Replace SERVICE with the registered name. For example:

alpha.props.get
alpha.props.list
alpha.props.describe

HELP lists the six runtime commands first. It then appends sorted, deduplicated author-handler command names. Author handlers that collide with a reserved name are omitted because they are unreachable.

INFO.version is the mix runtime version. A served script does not provide a separate executable version.

For property commands, a successfully parsed Bus args header takes precedence. When the header is absent or cannot be parsed, the runtime parses the request body as JSON. An empty request selects the root snapshot.

Lifecycle properties

The property tree is read-only and reports L1 conformance.

PathTypeValue
lifecycle.started_atstringRFC 3339 process start time.
lifecycle.uptime_snumberLive seconds since process start; marked transient.
lifecycle.modestringserving.
lifecycle.healthstringok.
lifecycle.props_levelstringL1.

The runtime uses the shared Bus property encoder with sensitive-value redaction enabled.

props.watch, props.set, and props.delete are not runtime-reserved. The runtime does not provide L2 watches, property mutation, or world aggregation. An author may define handlers with those names as domain behaviour.

Author handlers

The script defines domain commands with Mix on handlers. The event pump receives the Bus command, headers, and body through the supervised connection. The evaluator supplies replies through the same connection and preserves application return codes separately from transport failures.

Request-style sends await a reply. Map arguments use Bus header/body routing when a body key is present or the command is a property command. Emits are fire-and-forget and use header/body routing for map arguments.

Application error return codes remain application results. A transport failure raises a mesh-unavailable runtime error.

Broker configuration

The broker URL is resolved from the first applicable source:

  1. The file named by MIXOS_NODE_CONFIG.
  2. node.conf.mix under the resolved MixOS configuration directory.
  3. The system node.conf.mix, unless MIXOS_ETC explicitly isolates the configuration directory.

The reader consumes only these strict-data fields:

wg_ip: "192.0.2.5"
noded: { port: 4200 }

Unknown fields and sections are ignored. An absent wg_ip uses loopback. An absent noded.port uses 4200. When no file exists, or the selected file cannot be read or parsed, the URL falls back to the loopback broker on port 4200.

The first existing file is authoritative. A parse failure does not continue to a later candidate. The resolver never writes or migrates node configuration, and it does not read a legacy TOML format.

Logging

Serve mode uses the shared logging core with a journald-first preset. It enables standard error when no journal socket is available and also for a foreground run attached to a terminal. Structured entries carry the registered service name. RUST_LOG overrides the default filter.

There is no live property-backed log-filter mutation in this package. Change RUST_LOG and restart the citizen to change filtering.

Shutdown

SIGTERM, Ctrl-C, QUIT, and a terminal event-pump close converge on one shutdown path:

  1. Stop accepting new inbound work.
  2. Deregister from the broker with a five-second bound.
  3. Drain in-flight asynchronous handler tasks.
  4. Abort remaining tasks and synthesise shutdown replies when the connection can still deliver them.
  5. Exit.

A clean interrupt, QUIT, or clean pump end returns status 0 when deregistration succeeds or the connection is already gone. Script failure, deregistration failure, or expiration of the deregistration bound returns a non-zero status.

This page as Markdown · This page describes the current main branch of MixOS.