# How run.py Dispatches Methods to Subprocess via Conda in LingBot-Map

> Learn how LingBot-Map's run.py dispatches methods to subprocess via conda. Explore isolated Conda environments and the run_worker.py script for efficient execution.

- Repository: [Robbyant/lingbot-map](https://github.com/Robbyant/lingbot-map)
- Tags: how-to-guide
- Published: 2026-07-02

---

**In the LingBot-Map benchmark, [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) dispatches methods to isolated Conda environments by checking for an `env` key in the method configuration and executing [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) via `conda run` if present.**

[`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) serves as the entry point for the **run phase** of the LingBot-Map benchmark, orchestrating the execution of method-dataset combinations defined in [`configs/base.yaml`](https://github.com/Robbyant/lingbot-map/blob/main/configs/base.yaml). When a method requires dependencies isolated from the main environment, [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) dispatches it to a subprocess via Conda, ensuring clean dependency management while maintaining centralized orchestration logic.

## Method Configuration and Environment Detection

### Declaring Isolated Environments in base.yaml

Each method entry in [`configs/base.yaml`](https://github.com/Robbyant/lingbot-map/blob/main/configs/base.yaml) can optionally specify an `env` key to trigger subprocess dispatch. This declaration binds the method to a specific Conda environment without modifying the runner code.

```yaml
methods:
  - name: cut3r
    method_class: benchmark.methods.cut3r.Cut3R
    params:
      # method-specific parameters

    env: cut3r_env        # ← triggers subprocess dispatch

```

### Detection Logic in run.py

During the main execution loop, [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) inspects every method configuration. At lines 94-96, it checks for the presence of an environment specifier:

```python
env = method_cfg.get('env')
if env:
    rc = run_via_subprocess(env, config_path, method_name, dataset_name, scene, force, logger)
else:
    # Execute in-process

    rc = run_in_process(...)

```

This conditional branch determines whether to execute the method within the current Python process or dispatch it to an isolated subprocess.

## Subprocess Dispatch via run_via_subprocess

The `run_via_subprocess` function, implemented at lines 91-112 in [`benchmark/run.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/run.py), constructs and executes a `conda run` command that spawns [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) inside the target environment.

```python
def run_via_subprocess(env: str, config_path: str, method_name: str,
                       dataset_name: str, scene: str | None, force: bool,
                       logger: logging.Logger) -> int:
    """Dispatch a (method, dataset[, scene]) job to run_worker.py via conda run."""
    cmd = [
        'conda', 'run', '-n', env, '--no-capture-output',
        'python', 'run_worker.py',
        '--config', config_path,
        '--method', method_name,
        '--dataset', dataset_name,
    ]
    if scene:
        cmd += ['--scene', scene]
    if force:
        cmd += ['--force']

    logger.info(f"Dispatching to env '{env}': {' '.join(cmd)}")
    result = subprocess.run(cmd)
    return result.returncode

```

The function builds a command list starting with `conda run -n {env} --no-capture-output`, ensuring stdout/stderr flow back to the parent process unbuffered. It passes the configuration file path, method name, and dataset name as arguments to [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py), with optional `--scene` and `--force` flags for debug mode and overwrite behavior respectively.

## Result Handling and Exit Codes

After the subprocess completes, [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) inspects the return code at lines 126-133. An exit code of `0` indicates that all scenes for the method-dataset combination succeeded, while any non-zero value is recorded as a failure. This binary outcome allows the main loop to aggregate results across multiple environments without parsing detailed logs.

## The Worker Process: run_worker.py

[`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) acts as the isolated execution engine inside the Conda environment. It receives the same arguments passed by `run_via_subprocess` and performs the following steps (lines 84-98):

1. Parses arguments using `argparse` to mirror the flags passed by [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py)
2. Loads the configuration via `ConfigManager`
3. Dynamically loads the method class using `ClassLoader.load_method` from [`benchmark/core/registry.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/core/registry.py)
4. Iterates over scenes and calls `run_scene` for each

At lines 176-182, it exits with status `0` if every scene succeeded, or `1` if any scene failed. This exit code propagates back to [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py), closing the dispatch loop.

## Practical Dispatch Examples

### Manual Conda Invocation

To manually replicate what [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) constructs automatically:

```bash
conda run -n cut3r_env --no-capture-output python run_worker.py \
    --config configs/base.yaml \
    --method cut3r \
    --dataset 7scenes_s10 \
    --scene chess/seq-01 \
    --force

```

### Python Subprocess Call

To programmatically dispatch a method as [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) does:

```python
import subprocess

def dispatch_method():
    cmd = [
        "conda", "run", "-n", "cut3r_env", "--no-capture-output",
        "python", "run_worker.py",
        "--config", "configs/base.yaml",
        "--method", "cut3r",
        "--dataset", "7scenes_s10",
    ]
    result = subprocess.run(cmd)
    print(f"Exit code: {result.returncode}")
    return result.returncode == 0

dispatch_method()

```

## Summary

- **Configuration-driven dispatch**: Methods declare their Conda environment via the `env` key in [`configs/base.yaml`](https://github.com/Robbyant/lingbot-map/blob/main/configs/base.yaml), enabling isolation without code changes.
- **Conditional execution**: [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) checks for the `env` key at lines 94-96 to decide between in-process execution and subprocess dispatch.
- **Conda wrapper**: The `run_via_subprocess` function at lines 91-112 builds a `conda run -n {env}` command that executes [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) with the same arguments.
- **Exit code propagation**: [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) returns `0` for success or `1` for failure, which [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) interprets at lines 126-133 to track benchmark progress.
- **Dependency isolation**: This architecture keeps the main runner lightweight while allowing methods with conflicting dependencies to execute in their own managed environments.

## Frequently Asked Questions

### Why does LingBot-Map use subprocess dispatch instead of importing methods directly?

Direct imports would force all method dependencies into the same Python environment, causing version conflicts when methods require incompatible libraries (e.g., different PyTorch or CUDA versions). By dispatching via `conda run`, each method executes with its own dependency tree while the main [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) orchestrator remains dependency-light.

### How does exit code handling work between run.py and run_worker.py?

[`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) aggregates scene-level results and exits with `0` only if all scenes succeed (lines 176-182). [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) captures this via `subprocess.run()` and checks the return code at lines 126-133. A non-zero code marks that specific method-dataset combination as failed in the benchmark results, allowing the main loop to continue with other combinations.

### Can I debug a single scene using the subprocess dispatch?

Yes. When [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) is invoked with the `--scene` flag, it passes this argument through to [`run_worker.py`](https://github.com/Robbyant/lingbot-map/blob/main/run_worker.py) via the `run_via_subprocess` function (lines 104-105 in [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py)). The worker then processes only that specific scene, making it easier to debug method-specific issues within the isolated environment without running the full dataset.

### What happens if the specified Conda environment does not exist?

The `conda run` command will fail immediately with an environment not found error, and `subprocess.run()` will return a non-zero exit code. [`run.py`](https://github.com/Robbyant/lingbot-map/blob/main/run.py) will record this as a failure for the method-dataset combination and continue to the next iteration, as implemented in the result handling logic at lines 126-133.