# Running a Multistep Workflow Locally Sophios can run Python-authored workflows locally through supported CWL runners. The default runner is `cwltool`. The other supported local runner is `toil-cwl-runner`. This page uses a three-step workflow so the runner configuration is easy to see: ```text touch.cwl -> append.cwl -> cat.cwl -> workflow output "result" ``` A runnable version lives in [examples/scripts/multistep_runner_pyapi.py](https://github.com/PolusAI/sophios/blob/main/examples/scripts/multistep_runner_pyapi.py). ## Build the Workflow ```python from pathlib import Path from sophios.apis.python.workflow import Step, Workflow ADAPTERS = Path("cwl_adapters") touch = Step(ADAPTERS / "touch.cwl") touch.inputs.filename = "empty.txt" append = Step(ADAPTERS / "append.cwl") append.inputs.file = touch.outputs.file append.inputs.str = "Hello" cat = Step(ADAPTERS / "cat.cwl") cat.inputs.file = append.outputs.file workflow = Workflow([touch, append, cat], "multistep_runner_pyapi") workflow.outputs.result = cat.outputs.output ``` The workflow code does not change when you switch local runners. Runner choice belongs to execution configuration, not to the workflow graph. ## Run With the Default Runner ```python workflow.run() ``` This compiles the workflow, writes the generated CWL and job inputs, and runs the workflow locally with `cwltool`. ## Configure `cwltool` Pass local-run settings through `run_args_dict`: ```python workflow.run( basepath="autogenerated/multistep_cwltool", run_args_dict={ "cwl_runner": "cwltool", "container_engine": "docker", "copy_output_files": "yes", }, ) ``` Common Sophios local-run settings include: - `cwl_runner`: either `cwltool` or `toil-cwl-runner`. - `container_engine`: the container command used by the runner, usually `docker` or `podman`. - `copy_output_files`: use `"yes"` to copy primary outputs into `outdir/` after a successful local run. - `cachedir`: override the cache directory used by local execution. - `generate_run_script`: use `"yes"` to write `run.sh` for inspection instead of invoking the runner. ## Run With Toil To run the same workflow with Toil, change only the runner configuration: ```python workflow.run( basepath="autogenerated/multistep_toil", run_args_dict={ "cwl_runner": "toil-cwl-runner", "container_engine": "docker", "logLevel": "INFO", }, ) ``` Runner-specific key/value options can be included in `run_args_dict`. Sophios uses the options it recognizes for local setup and passes the remaining key/value options to the selected CWL runner. In the Toil example above, `"logLevel": "INFO"` is passed to Toil as a runner option. ## Inspect the Runner Command When you want to see the generated command without executing the runner, ask Sophios to write a run script: ```python workflow.run( run_args_dict={ "cwl_runner": "cwltool", "container_engine": "docker", "generate_run_script": "yes", }, ) ``` This still compiles the workflow and prepares local execution artifacts, but it stops before invoking the selected runner. ## Run the Example Script ```bash python examples/scripts/multistep_runner_pyapi.py ``` The script is intentionally a normal Python build/run script, not a separate CLI app. The runner configuration is near the top: ```python RUN_ARGS = { "cwl_runner": "cwltool", "container_engine": "docker", "copy_output_files": "yes", } ``` To use Toil, change the same dictionary: ```python RUN_ARGS = { "cwl_runner": "toil-cwl-runner", "container_engine": "docker", "logLevel": "INFO", } ``` That keeps the build script readable and portable as Python source while Sophios generates the portable CWL workflow for execution.