> ## 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.

# Move workers from config.yaml to Compose

> Manual migration to the breaking worker-compose.yaml engine and project model.

iii 0.23 removes the legacy worker lifecycle. There is no compatibility period or automatic
migrator: `iii worker`, the `worker::*` functions, engine fallback through `iii.toml`, and
engine-managed project workers are gone.

A managed project now has one `worker-compose.yaml`. Its `engine:` section configures the engine;
`containers:` declares project workers. If a direct `config.yaml` still declares a project worker,
startup and reload stop with `UNSUPPORTED_CONFIG_WORKERS` and list every entry to migrate.

## Back up the project

Keep a copy of:

* `config.yaml` and any path passed with `--config`
* an existing `worker-compose.yaml`
* the configuration worker's storage directory, `./config/` by default
* state, queue, and stream data paths
* `iii.lock`, if the project used the removed installer

Rollback requires those files and the previous iii binary. The 0.23 engine does not accept the old
project-worker declarations.

## Build the new engine section

Move these five engine-owned worker configs from list entries in `config.yaml` to the direct
`engine.workers` map:

| Worker               | Why it remains engine-owned                                                        |
| -------------------- | ---------------------------------------------------------------------------------- |
| `configuration`      | Owns schemas, values, and change notifications used by engine and project workers. |
| `iii-worker-manager` | Owns engine WebSocket and RBAC listeners.                                          |
| `iii-http-functions` | Provides outbound HTTP functions inside the engine process.                        |
| `iii-stream`         | Uses engine internals and engine-owned adapters.                                   |
| `iii-sandbox`        | Owns the builtin sandbox lifecycle.                                                |

The map value is the worker config itself; do not keep the old nested `config:` key. Multiple
instances use a `#instance` suffix, such as `iii-worker-manager#rbac`.

```yaml worker-compose.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
# namespace: default
engine:
  url: ws://127.0.0.1:49134
  registration_namespace_grace_ms: 5000
  workers:
    configuration:
      adapter:
        name: fs
        config:
          directory: ./config
    iii-worker-manager:
      host: 127.0.0.1
      port: 49134
    iii-http-functions: {}
    iii-stream:
      host: 127.0.0.1
      port: 3112
    iii-sandbox:
      auto_install: true
```

`engine.url` defaults to `ws://127.0.0.1:49134`. Compose writes the engine-only representation to
`~/.iii/compose/<daemon-namespace>/engine-config.yaml` with owner-only permissions and starts the
engine from it. Do not edit that generated file.

Remove explicit `iii-engine-functions`, `iii-telemetry`, and `iii-observability` entries. The
engine injects them automatically, and declaring one under `engine.workers` fails.

## Move project workers to containers

| Old name                 | Compose package/configuration id  |
| ------------------------ | --------------------------------- |
| `iii-http` or `http`     | `http`                            |
| `iii-cron` or `cron`     | `cron`                            |
| `iii-queue` or `queue`   | `queue`                           |
| `iii-state` or `state`   | `state`                           |
| `iii-pubsub` or `pubsub` | `pubsub`                          |
| `iii-bridge`             | `bridge`                          |
| `iii-exec`               | no package; use Compose `scripts` |

Copy each old project worker's `config:` value to `config_override`, preserve its storage paths,
and pin the package version.

```yaml worker-compose.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
containers:
  state:
    worker: package://api.workers.iii.dev/state
    version: "0.22.2"
    config_name: state
    config_override:
      adapter:
        name: kv
        config:
          store_method: file_based
          file_path: ./data/state_store.db
  http:
    worker: package://api.workers.iii.dev/http
    version: "0.21.3"
    config_name: http
    config_override:
      host: 127.0.0.1
      port: 3111
  app:
    worker: path://.
    start_after: [state, http]
    scripts:
      pre_run: pnpm build
      run: pnpm start
      post_run: pnpm cleanup
```

Translate `iii-exec` commands into `pre_run`, `run`, and `post_run`. Custom local workers use
`path://`; registry workers use `package://` plus a version.

You can also add a registry worker after the managed daemon is running:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii trigger -n dev compose::add worker=http
iii trigger -n dev compose::add worker=state
```

`compose::add` writes `containers:` and restarts affected containers. It never edits `engine:` or
restarts the engine. A registry root of kind `engine` is rejected and points to
`engine.workers.<name>` when the worker is configurable.

## Preserve stored configuration

Standalone packages use unprefixed configuration ids. A value stored as `iii-state` is not copied
to `state` automatically.

1. Before upgrading, read the old value with `configuration::get`.
2. Put it under `config_override`, or write it to the new id with `configuration::set`.
3. Verify the new worker before removing the old stored entry.

Do not rename only the YAML file: filesystem-backed entries also contain id and schema metadata.

## Start the result

For a managed engine, the file owns both lifecycles:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii compose --namespace dev --up --file worker-compose.yaml
```

The explicit namespace (`dev`) addresses `compose::*` and overrides the file's `namespace:` for
its containers. Changing `engine:` while the daemon runs returns `ENGINE_RESTART_REQUIRED`; stop
and restart that Compose invocation. A second file with `engine:` returns `ENGINE_ALREADY_OWNED`.

For an engine supervised separately by systemd or Kubernetes, keep its five allowed workers in
the list-shaped `config.yaml`, start it directly, omit `engine:` from the Compose file, and supply
the existing URL:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii --config config.yaml
iii compose --namespace dev --engine ws://127.0.0.1:49134 --up --file worker-compose.yaml
```

Run those commands under separate supervisors. Explicit `--engine` has highest priority and may
override an `engine.url` in the Compose file. Without either value, Compose uses `III_URL`, then
defaults to `ws://127.0.0.1:49134`.

Verify the migration:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
iii trigger -n dev compose::status file=worker-compose.yaml
iii trigger engine::workers::list
iii trigger engine::triggers::list
```

## Common errors

* `UNSUPPORTED_CONFIG_WORKERS`: direct `config.yaml` still contains project workers.
* `UNSUPPORTED_ENGINE_WORKER`: `engine.workers` contains a project or unknown worker.
* `ENGINE_WORKER_IS_INJECTED`: remove an automatically supplied internal worker.
* `ENGINE_RESTART_REQUIRED`: restart Compose to apply a changed engine section.
* `ENGINE_ALREADY_OWNED`: start the second managed file in a separate Compose invocation.
* `ENGINE_SECTION_REQUIRES_MANAGED_START`: an external daemon was asked to load a managed file.
* `ENGINE_WORKER_IS_BUILTIN`: `compose::add` was given an engine package as a root.
* `CONTAINER_NAME_TAKEN`: another live process owns the same `(namespace, worker name)`.
