How workers expand iii
Workers add capability to an iii system. Each one contributes functions the engine can route to. This page covers deploying and wiring workers into a project. Once connected, a worker exposes:- Functions, callable by
function_idfrom anywhere in the system (see Using iii / Functions). - The same functions as event sources: any worker can observe them, so its own functions run each time they run (see Using iii / Observers).
Create a new worker
A worker is any process that installs an iii SDK, connects to an engine, and registers functions or observers. Create a normal TypeScript, JavaScript, Python, Rust, or Go project using that language’s package tools, then add the corresponding iii SDK and an entrypoint such assrc/index.ts,
src/main.py, or main.go.
To let Compose start a local worker, add an iii.worker.yaml at the project root:
iii.worker.yaml
worker-compose.yaml, or add it through a running daemon with
iii call -n dev compose::add worker=./workers/my-worker. See
Declare workers with Compose for path://
workers and Add a registry worker for registry
packages.
The engine and SDK packages can have different patch versions within the same minor line. Keep the
engine and SDKs on the same minor version, for example
0.11.x, unless a release note says
otherwise.Connecting to the engine
A worker connects to the engine over WebSocket. The convention is to set the engine URL via theIII_URL environment variable, but it can also be passed explicitly to registerWorker. The
connection string is the only coupling between a worker and the iii instance it joins, so the worker
process can be deployed anywhere reachable on the network.
This connects with full trust, appropriate for workers you run. For an untrusted worker (a browser
client or a third party’s), connect through the
iii-worker-manager RBAC listener and gate it
with an auth function instead. See Access Control and the iii-worker-manager
worker page.- Node / TypeScript
- Python
- Rust
- Go
namespace scopes everything this worker registers, so an identically-named worker or function id
can coexist in another namespace. The SDK reads it from the option, then from the III_NAMESPACE
environment variable, then falls back to default.
With III_NAMESPACE set in the worker’s environment, these two are equivalent:
- Node / TypeScript
- Python
- Rust
- Go
III_NAMESPACE per deployment,
and each deployment of the same image registers in its own namespace.
For calling across namespaces and handling a rejected registration, see Use
namespaces.
The browser SDK has no environment to read, so it takes the option only.
Worker lifecycle
States
Workers transition through a small set of states after connecting:connected → available / busy → disconnected. connected means the worker has joined the engine’s
registry. available and busy describe whether the worker is currently handling invocations.
disconnected is the terminal state when the WebSocket closes. The engine tracks these transitions
and surfaces them to other workers and tooling through its discovery functions, so the rest of the
system can react. The WebSocket handshake that precedes connected is not one of these states.
Inspecting the live registry
To see what’s currently connected to the engine, invoke one of theengine::*::list functions to
get the current state of the registry. Each returns a list:
List workers, functions and observers
List workers, functions and observers
- Node / TypeScript
- Python
- Rust
- Go
Handling worker disconnects
When a worker’s WebSocket closes, the engine cleans up after it automatically. Its functions leave the live registry, observers of those functions gopending until a worker registers them again,
and any in-flight invocations of those functions are cancelled. The worker’s own observers go
suspended for observers.observer_grace_ms (30 s by default): if it reconnects in that time it
keeps them, and otherwise they are disposed. When it reconnects later, its SDK observes again, under
the same id, each of them that has neither max_fires nor expires_in_ms (see
Observer states).
In flight requests
In flight requests will get ainvocation_stopped error, catch these errors and treat them like a
cancellation. Retrying will fail until the worker that owns this function reconnects.
Handle a stopped invocation
Handle a stopped invocation
- Node / TypeScript
- Python
- Rust
- Go
Observe registry changes
The engine hosts three functions that run when the registry changes. Observe them to react to topology changes as they happen, for example to continue work when a worker comes back online.event on engine::on-workers-changed is worker_connected, worker_disconnected, or
worker_metadata_updated.
With capture: ["input"], the target receives the event as the payload’s input. See
Engine-hosted event sources.
React to registry changes
React to registry changes
- Node / TypeScript
- Python
- Rust
- Go
Worker manifest
iii.worker.yaml is the manifest at the worker’s root that tells Compose how to start a local or
bundled worker. A local Compose container can replace the start command with its own scripts.run,
and add scripts.pre_run or scripts.post_run lifecycle hooks. The container cannot change the
manifest’s name or runtime.base_image; those stay as the manifest declares them.
description is an optional one-line, human/LLM-readable summary of what the worker does.
scripts.start launches the worker. Here, watchfiles reloads it whenever you edit a source file.
runtime.base_image selects the OCI image used for a bundled worker’s root filesystem. The manifest
is metadata about starting the worker.
See Worker manifest for the field reference.
If a worker isn’t starting correctly, check its manifest, the Compose daemon output, and
iii call -n dev compose::status.iii does not distinguish a Compose-managed process from a manually run process that uses the iii
SDK. Both connect to the engine and register the same way.
Shutting down a worker
Call the SDK’sshutdown to close the WebSocket cleanly. The engine then applies the cleanup
described in Handling worker disconnects at once, instead of waiting
for the dropped socket.
Without shutdown, an abrupt process exit reaches the same state once the engine notices the
dropped socket. Graceful shutdown makes it deterministic and faster.
- Node / TypeScript
- Python
- Rust
- Go
Shutdown is useful for one-shot and ephemeral workers. Kubernetes Jobs, serverless containers, and
scheduled scripts connect like any other worker, do their work, and call
shutdown().