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

# Runtime plugins

> A runtime plugin tells the CLI how to launch an adapter. Registered plugins are not the same as production-backed curated runtime support.

## Module path

The runtime package is `worldflux.runtime` (singular). Sources:

```
src/worldflux/runtime/
├── aws_ec2.py           # AWS doctor and dry-run helpers
├── aws_ec2_artifacts.py # AWS artifact collection helpers
├── aws_ec2_cleanup.py   # orphan cleanup helpers
├── aws_ec2_executor.py  # EC2 launch/SSH/collect executor
├── aws_ec2_plan.py      # EC2 plan builder
├── aws_ec2_plugin.py    # experimental EC2 wrapper with cost guards
├── base.py              # Protocol + RuntimeContext + RuntimeResult
├── cpu_smoke.py         # `worldflux run` entrypoint (calls run_project)
├── dispatcher.py        # registry: get_runtime, list_runtimes, doctor_all
├── dreamer_style_light.py
├── local_command.py     # trusted external local command runtime
├── local_plugin.py      # curated local subprocess
├── modal_command.py     # trusted external Modal Sandbox command runtime
├── modal_plugin.py      # experimental Modal curated wrapper
├── model_runner.py      # shared model-runner helpers
├── mujoco_smoke.py
├── mujoco_policy_eval.py
├── remote_security.py   # env allowlist, secret redaction, staging guards
└── replicate_plugin.py  # production-disabled vendor wrapper
```

## The Protocol

```python theme={null}
# src/worldflux/runtime/base.py
from typing import Protocol, runtime_checkable

@runtime_checkable
class RuntimePlugin(Protocol):
    name: str

    def doctor(self) -> list[str]: ...

    def build_env(self, adapter, install_dir: Path) -> None: ...

    def run(self, ctx: RuntimeContext, command: tuple[str, ...]) -> RuntimeResult: ...

    def teardown(self, ctx: RuntimeContext) -> None: ...
```

Four methods. `doctor` returns a list of human-readable diagnostic lines. `build_env` materialises the venv (or vendor equivalent) for an adapter. `run` actually launches the command. `teardown` cleans up.

## Context and result

```python theme={null}
@dataclass(frozen=True)
class RuntimeContext:
    adapter_id: str
    task: str                  # e.g. "generate", "rollout", "eval"
    output_dir: Path
    install_dir: Path
    source_dir: Path
    venv_python: Path | None
    timeout: int | None
    env_vars: dict[str, str]
    extra_args: tuple[str, ...] = ()
    run_id: str = ""
    provider_env_allowlist: tuple[str, ...] = ()
    forward_provider_env: tuple[str, ...] = ()

@dataclass(frozen=True)
class RuntimeResult:
    exit_code: int
    duration_seconds: float
    log_path: Path
    artifacts: dict[str, Path] = field(default_factory=dict)
    cost_estimate_usd: float | None = None
    metadata: dict[str, str] = field(default_factory=dict)
```

`cost_estimate_usd` may be filled by remote wrappers for paid-instance experiments. `metadata` carries free-form key/value pairs that end up in the manifest.

## Built-in plugins

| `name`      | Module                             | When                                                                                                                         |
| ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `local`     | `local_plugin.LocalPlugin`         | Anything you can run on the local box.                                                                                       |
| `modal`     | `modal_plugin.ModalPlugin`         | Experimental wrapper; curated production runs stay disabled unless adapter metadata is `production-backed`.                  |
| `aws-ec2`   | `aws_ec2_plugin.AwsEc2Plugin`      | Experimental wrapper with cost guards; curated production runs stay disabled unless adapter metadata is `production-backed`. |
| `replicate` | `replicate_plugin.ReplicatePlugin` | Vendor wrapper; production share/run claims require live smoke evidence and adapter support metadata.                        |

`local_command.py` and `modal_command.py` are recipe-level external command
runtimes, not dispatcher plugins. They load an external manifest, build a
minimal environment, and require explicit provider env allowlists before secrets
can be forwarded.

The dispatcher registers them at import time:

```python theme={null}
# src/worldflux/runtime/dispatcher.py
_register(LocalPlugin())
_register(ModalPlugin())
_register(AwsEc2Plugin())
_register(ReplicatePlugin())
```

`get_runtime("aws-ec2")` returns the singleton. `list_runtimes()` returns the sorted name list. `doctor_all()` runs every plugin's `doctor()` and returns a `dict[str, list[str]]`.

## Runtime CLI

The `runtime` sub-tree exposes the dispatcher to the CLI:

```bash theme={null}
worldflux runtime list
worldflux runtime doctor
```

`worldflux runtime list` prints registered plugin names. `worldflux runtime doctor` runs every plugin's `doctor()` and prints the result. The same diagnostics also surface in `worldflux doctor`.

## Writing a plugin

<Steps>
  <Step title="Drop the file">
    Create `src/worldflux/runtime/<name>_plugin.py`. Define a class with the four Protocol methods and a `name: str` class attribute.
  </Step>

  <Step title="Register it">
    In `src/worldflux/runtime/dispatcher.py`, import the class and call `_register(MyPlugin())` at module bottom.
  </Step>

  <Step title="Add a doctor probe">
    `doctor()` returns one line per check. Lines starting with `error:` block runs; everything else is informational. Common checks: binary on `$PATH`, env vars set, GPU visible, vendor SDK importable.
  </Step>

  <Step title="Implement run()">
    Use `RuntimeContext.venv_python` (or the equivalent runtime entrypoint) to launch the command. Capture stdout/stderr to `RuntimeContext.output_dir / "logs" / f"{run_id}.log"` and pass that path back as `RuntimeResult.log_path`.
  </Step>

  <Step title="Tests">
    Add `tests/runtime/test_<name>_plugin.py`. Reuse provider fakes rather than mocking ad hoc.
  </Step>
</Steps>

## What lands in the manifest

Anything the plugin writes via `RuntimeResult.metadata` becomes a leaf under `manifest.runtime`. The recipe-level metrics live under `manifest.metrics`. Logs are referenced by path (the manifest carries the path, the bytes ride the artifact channel).
