How workers expand iii
Workers add capability to a iii system. Each one contributes functions and triggers 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). - Triggers it advertises, which other workers can bind their functions to (see Using iii / Triggers).
Create a new worker
A worker is any process that installs a iii SDK, connects to an engine, and registers functions or triggers. 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 or
src/main.py.
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 trigger -n dev compose::add worker=./workers/my-worker. See
Using iii / Workers for the registry and local-path surface.
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 register_worker. 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 Untrusted workers and access
control and the iii-worker-manager
worker page.- Node / TypeScript
- Python
- Rust
namespace scopes everything this worker registers, so an identically-named worker or function id
can coexist in another namespace.
Leave the option out and the SDK reads the III_NAMESPACE environment variable itself, so these two
are equivalent when III_NAMESPACE is set in the worker’s environment:
- Node / TypeScript
- Python
- Rust
default namespace when neither the option nor III_NAMESPACE gives it
one. The browser SDK has no environment to read, so it takes the option only.
Omit the option to serve many tenants from one worker package: set 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.
Worker lifecycle
States
Workers transition through a small set of states after connecting:connecting → connected → available / busy → disconnected. connecting is the WebSocket handshake.
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.
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:
Example: list registry contents
Example: list registry contents
- Node / TypeScript
- Python
- Rust
Handling Worker disconnects
When a Worker’s WebSocket closes, the Engine cleans up after it automatically. Its Functions and Triggers leave the live registry, and any in-flight invocations of those Functions are cancelled.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.
Example: catch `invocation_stopped`
Example: catch `invocation_stopped`
- Node / TypeScript
- Python
- Rust
Subscribe to changes
You can register a Trigger against one of the engine’s discovery events to react to topology changes as they happen. This is particularly useful for continuing work when a Worker comes back online.Example: subscribe to discovery events
Example: subscribe to discovery events
- Node / TypeScript
- Python
- Rust
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 override the manifest with its own scripts.run and
use pre_run or post_run for lifecycle hooks.
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. Once the Worker is running, iii treats a
Compose-managed process and a manually run process that uses the iii SDK identically.
If a worker isn’t starting correctly, check its manifest, the Compose daemon output, and
iii trigger -n dev compose::status.Shutting down a worker
Call the SDK’sshutdown to close the WebSocket cleanly. The engine removes the worker’s Functions
and Triggers from the registry, fires engine::workers-available with worker_disconnected, and
cancels in-flight invocations targeting them with invocation_stopped.
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
Shutdown is useful for One-shot / ephemeral workers. Kubernetes Jobs, serverless containers,
or scheduled scripts can connect like any other Worker, do their work, and
shutdown()
(shutdown_async().await in Rust).