> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Worker

> WebSocket bridge that connects SDK workers to the engine for function registration, invocation, and trigger management.

The Worker Manager opens a WebSocket server that SDK workers connect to. Through this connection, workers register functions, receive invocations, manage triggers, and stream data via channels.

```
iii-worker-manager
```

The `iii-worker-manager` is **mandatory** and always loaded. When explicitly configured in the `workers:` section, the first entry's `port` sets the main engine WebSocket port. Additional entries start separate listeners — optionally with an `rbac` block for role-based access control. Multiple `iii-worker-manager` entries are supported.

## Architecture

```mermaid theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
graph LR
    SDK[SDK Worker] -->|WebSocket| WM[Worker Manager]
    WM -->|Register| FR[Function Registry]
    WM -->|Register| TR[Trigger Registry]
    WM -->|Invoke| Engine[Engine]
    Engine -->|Result| WM
    WM -->|Result| SDK
```

Workers connect via WebSocket, send a registration message with metadata (runtime, version, OS), and then exchange messages with the engine for the lifetime of the connection. On disconnect, the worker and any session-scoped triggers are cleaned up automatically.

## Sample Configuration

```yaml title="iii-config.yaml" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
workers:
  # Main engine port — workers connect here by default
  - name: iii-worker-manager
    config:
      port: 49134

  # Additional listener with middleware and RBAC
  - name: iii-worker-manager
    config:
      host: 0.0.0.0
      port: 49135
      middleware_function_id: my-project::middleware-function
      rbac:
        auth_function_id: my-project::auth-function
        on_trigger_registration_function_id: my-project::on-trigger-reg
        on_trigger_type_registration_function_id: my-project::on-trigger-type-reg
        on_function_registration_function_id: my-project::on-function-reg
        expose_functions:
          - match("engine::*")
          - match("*::public")
          - metadata:
              public: true
          - metadata:
              name: match("*public*")
```

## Configuration

Top-level fields on the `config` object:

<ResponseField name="port" type="number" required>
  The port to listen on. For the first `iii-worker-manager` entry this sets the main engine WebSocket port. Additional entries start separate listeners. Defaults to `49134`.
</ResponseField>

<ResponseField name="host" type="string">
  The host to bind to. Defaults to `0.0.0.0`.
</ResponseField>

<ResponseField name="middleware_function_id" type="string">
  Function ID to invoke before every function call from a worker on this port. Receives a `MiddlewareFunctionInput` with the target function ID, payload, action, and auth context. The middleware is responsible for calling the target function and returning the result.
</ResponseField>

<ResponseField name="rbac" type="RbacConfig">
  RBAC configuration block. When present, the listener applies role-based access control to all connections. See [RBAC](#rbac) below.
</ResponseField>

## Middleware

When `middleware_function_id` is configured, every worker invocation on that port is routed through the middleware instead of calling the target function directly. Middleware can be used on any port, with or without RBAC.

### MiddlewareFunctionInput

The middleware function receives:

<Expandable title="MiddlewareFunctionInput">
  <ResponseField name="function_id" type="string" required>
    The function the worker wants to invoke.
  </ResponseField>

  <ResponseField name="payload" type="Record<string, unknown>" required>
    The payload sent by the worker.
  </ResponseField>

  <ResponseField name="action" type="TriggerAction">
    Routing action (enqueue, void), if any.
  </ResponseField>

  <ResponseField name="context" type="Record<string, unknown>" required>
    The context from the auth result for this connection (empty object when RBAC is not configured).
  </ResponseField>
</Expandable>

The middleware is responsible for calling the target function (e.g. via `trigger()`) and returning the result. This gives you full control to modify, validate, or audit requests.

## RBAC

When the `rbac` block is present in an `iii-worker-manager` entry, that listener enforces role-based access control: authentication on connect, function invocation authorization, and gated function/trigger registration.

<Info title="How-to guidance">
  For step-by-step instructions on enabling RBAC, writing auth and middleware functions, and connecting workers, see [Worker RBAC](../how-to/worker-rbac).
</Info>

### RBAC Architecture

```mermaid theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
graph LR
    Worker[Worker] -->|WebSocket| RBAC[RBAC Port]
    RBAC -->|Auth| AuthFn[Auth Function]
    AuthFn -->|AuthResult| RBAC
    RBAC -->|Invoke| Middleware[Middleware Function]
    Middleware -->|engine.call| Engine[Engine]
    Engine -->|Result| RBAC
    RBAC -->|Result| Worker
```

On connection to an RBAC port, the optional auth function is called with the request headers, query parameters, and client IP address. Once authenticated, workers can invoke exposed functions and register triggers subject to RBAC rules.

### RBAC Configuration

<ResponseField name="auth_function_id" type="string">
  Function ID to invoke for authentication when a worker connects. Receives an `AuthInput` with headers, query parameters, and IP address. Must return an `AuthResult` object. If the function fails or returns no result, the connection is rejected.
</ResponseField>

<ResponseField name="expose_functions" type="FunctionFilter[]">
  List of filters that determine which functions are accessible to workers on the RBAC port. A function is accessible if it matches **any** filter. Two filter types are supported:

  * **match("pattern")** -- Wildcard pattern on the function ID. `*` matches any characters.
  * **metadata** -- Key-value conditions on function metadata.
</ResponseField>

<ResponseField name="on_trigger_registration_function_id" type="string">
  Function ID to invoke when a worker attempts to register a trigger. Receives the trigger details and auth context. Must return an `OnTriggerRegistrationResult` object with the (possibly mapped) registration fields. Throw an error to deny the registration.
</ResponseField>

<ResponseField name="on_trigger_type_registration_function_id" type="string">
  Function ID to invoke when a worker attempts to register a trigger type. Receives the trigger type details and auth context. Must return an `OnTriggerTypeRegistrationResult` object with the (possibly mapped) registration fields. Throw an error to deny the registration.
</ResponseField>

<ResponseField name="on_function_registration_function_id" type="string">
  Function ID to invoke when a worker attempts to register a function. Receives the function details and auth context. Must return an `OnFunctionRegistrationResult` object with the (possibly mapped) registration fields. Throw an error to deny the registration. See [Function Registration](#function-registration).
</ResponseField>

### Function Filters

#### Wildcard Match

Match functions by their ID using `*` as a wildcard:

```yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
expose_functions:
  - match("engine::*")       # all functions starting with engine::
  - match("*::public")       # all functions ending with ::public
  - match("api::*::read")    # e.g. api::users::read, api::orders::read
```

#### Metadata Match

Match functions by their registered metadata. Values can be exact or use `match()` for wildcard patterns:

```yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
expose_functions:
  - metadata:
      public: true                   # metadata.public === true
  - metadata:
      name: match("*public*")        # metadata.name contains "public"
      tier: "free"                   # metadata.tier === "free"
```

All keys in a metadata filter must match for the filter to pass (AND logic). Multiple filters in `expose_functions` use OR logic -- a function is exposed if **any** filter matches.

### Authentication

When `auth_function_id` is configured, the function is called on every new WebSocket connection before the worker can invoke any functions.

#### Auth Input

The auth function receives:

<Expandable title="AuthInput">
  <ResponseField name="headers" type="Record<string, string>" required>
    HTTP headers from the WebSocket upgrade request.
  </ResponseField>

  <ResponseField name="query_params" type="Record<string, string[]>" required>
    Query parameters from the connection URL. Each key maps to an array of values to support repeated keys.
  </ResponseField>

  <ResponseField name="ip_address" type="string" required>
    IP address of the connecting client.
  </ResponseField>
</Expandable>

#### Auth Result

The auth function must return:

<Expandable title="AuthResult">
  <ResponseField name="allowed_functions" type="string[]">
    Additional function IDs to allow beyond `expose_functions`. Defaults to empty.
  </ResponseField>

  <ResponseField name="forbidden_functions" type="string[]">
    Function IDs to deny even if they match `expose_functions`. Takes precedence over `allowed_functions`. Defaults to empty.
  </ResponseField>

  <ResponseField name="allowed_trigger_types" type="string[]">
    Trigger type IDs this worker is allowed to register triggers for. When omitted, all types are allowed.
  </ResponseField>

  <ResponseField name="allow_trigger_type_registration" type="boolean">
    Whether this worker can register new trigger types. Defaults to `false`.
  </ResponseField>

  <ResponseField name="allow_function_registration" type="boolean">
    Whether this worker can register new functions. Defaults to `true`.
  </ResponseField>

  <ResponseField name="function_registration_prefix" type="string">
    When set, all function IDs registered by this worker are internally prefixed with `{prefix}::`. Trigger registrations auto-prefix their `function_id` reference. The prefix is stripped when dispatching invocations back to the worker, providing transparent namespace isolation per session.
  </ResponseField>

  <ResponseField name="context" type="Record<string, unknown>">
    Arbitrary context object passed to the middleware function on every invocation. Defaults to empty object.
  </ResponseField>
</Expandable>

#### Access Resolution Order

1. If the function is in `forbidden_functions` -- **denied**
2. If the function is in `allowed_functions` -- **allowed**
3. If the function is `engine::channels::create` -- **allowed** (always whitelisted for channel support)
4. If any `expose_functions` filter matches -- **allowed**
5. Otherwise -- **denied**

### Registration Gating

Workers on an RBAC port can register functions, trigger types, and triggers when permitted by the auth result.

#### Function Registration

A worker can register a function if **both** conditions are met:

1. `allow_function_registration` is `true` in the auth result (defaults to `true`)
2. If `on_function_registration_function_id` is configured, the hook returns an `OnFunctionRegistrationResult` object

When `function_registration_prefix` is set in the auth result, registered function IDs are automatically prefixed with `{prefix}::{function_id}`. Trigger registrations also auto-prefix the `function_id` they reference. The prefix is stripped when dispatching invocations back to the worker, so the worker SDK never sees it.

The hook receives:

<Expandable title="OnFunctionRegistrationInput">
  <ResponseField name="function_id" type="string" required>
    The function ID being registered.
  </ResponseField>

  <ResponseField name="description" type="string">
    Optional description provided by the worker.
  </ResponseField>

  <ResponseField name="metadata" type="Record<string, unknown>">
    Optional metadata provided by the worker.
  </ResponseField>

  <ResponseField name="context" type="Record<string, unknown>" required>
    The context from the auth result for this session.
  </ResponseField>
</Expandable>

The hook must return:

<Expandable title="OnFunctionRegistrationResult">
  <ResponseField name="function_id" type="string">
    Mapped function ID. Omit to keep the original.
  </ResponseField>

  <ResponseField name="description" type="string">
    Mapped description. Omit to keep the original.
  </ResponseField>

  <ResponseField name="metadata" type="Record<string, unknown>">
    Mapped metadata. Omit to keep the original.
  </ResponseField>
</Expandable>

If the hook throws an error or the auth check fails, the registration is silently dropped.

#### Trigger Type Registration

A worker can register a trigger type if **both** conditions are met:

1. `allow_trigger_type_registration` is `true` in the auth result
2. If `on_trigger_type_registration_function_id` is configured, the hook returns an `OnTriggerTypeRegistrationResult` object (or throws to deny)

#### Trigger Registration

A worker can register a trigger if **both** conditions are met:

1. The trigger's `trigger_type` is in `allowed_trigger_types` from the auth result
2. If `on_trigger_registration_function_id` is configured, the hook returns an `OnTriggerRegistrationResult` object (or throws to deny)

All registration hooks can map fields by returning different values in the result object. Omitted fields keep their original values from the registration request.

Triggers registered by a session are automatically cleaned up when the worker disconnects.

## Channel Support

Channels work on every `iii-worker-manager` port. The worker mounts the channel WebSocket endpoint at `/ws/channels/{channel_id}` on the same port.

SDK workers can use `createChannel()` without any changes. On RBAC ports, the `engine::channels::create` function is automatically whitelisted to enable channel creation. Channel data transfer uses the standard channel WebSocket endpoint, which validates access via the `access_key` capability token.

## Protocol

The Worker Manager uses the standard iii engine WebSocket protocol. SDK workers connect with `registerWorker()` and everything works out of the box.

### Worker to Server

| Type                  | Fields                                                 | Description                                                                                                                                                                                                                    |
| --------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `registerworker`      | `runtime`, `version`, `name`, `os`, `telemetry`, `pid` | Register the worker and send metadata.                                                                                                                                                                                         |
| `registerfunction`    | `function_id`, `description?`, `metadata?`             | Register a function. On RBAC ports, requires `allow_function_registration` and, if `on_function_registration_function_id` is configured, the hook must return a result object; otherwise the registration is silently dropped. |
| `unregisterfunction`  | `function_id`                                          | Unregister a function owned by this worker.                                                                                                                                                                                    |
| `invokefunction`      | `invocation_id`, `function_id`, `data`                 | Invoke a function. RBAC checks apply on RBAC ports.                                                                                                                                                                            |
| `registertriggertype` | `id`, `description`                                    | Register a new trigger type. On RBAC ports, requires `allow_trigger_type_registration`.                                                                                                                                        |
| `registertrigger`     | `id`, `trigger_type`, `function_id`, `config`          | Register a trigger. On RBAC ports, requires the type to be in `allowed_trigger_types`.                                                                                                                                         |
| `unregistertrigger`   | `id`                                                   | Unregister a trigger owned by this session.                                                                                                                                                                                    |
| `ping`                | --                                                     | Keepalive ping.                                                                                                                                                                                                                |

### Server to Worker

| Type               | Fields                                              | Description                                                                          |
| ------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `workerregistered` | `worker_id`                                         | Sent after successful connection and auth. The `worker_id` serves as the session ID. |
| `invokefunction`   | `invocation_id`, `function_id`, `data`              | Engine invoking a function on the worker.                                            |
| `invocationresult` | `invocation_id`, `function_id`, `result?`, `error?` | Result of an `invokefunction` request.                                               |
| `pong`             | --                                                  | Response to `ping`.                                                                  |

If authentication fails on an RBAC port, the server sends a JSON error message (`{"type":"error","error":{...}}`) and closes the connection.

## Security Considerations

* The RBAC port should be the **only** port exposed to external networks. The main engine port should remain internal.
* Always configure `auth_function_id` in production to prevent unauthenticated access on RBAC ports.
* Use `expose_functions` to limit the blast radius. Prefer explicit patterns over broad wildcards like `match("*")`.
* The middleware function is the right place for request validation, rate limiting, and audit logging.
* `forbidden_functions` from the auth result takes precedence over all other allow rules, providing a hard deny mechanism per worker.
* Trigger registrations are scoped to the session and cleaned up on disconnect.
