Skip to content

Run Cloud applications

Info

Learn the concepts and fundamentals of runs in the Explanation page.

A run is a single execution of an app against an instance. It is the basic functionality encompassed of receiving an input, running the app, and returning an output.

This how-to guide explains how to interact with runs using the methods available on the Application class. Go to the reference section to see all the available parameters for each method.

There are limits for submitting a new run and retrieving the results, however, all the run-related methods automatically take care of handling large payloads for you.

Start and get a run

Start a new run using the Application.new_run method. json input data can be passed using the input keyword argument as a Python dict. The method returns a run_id that can be used to retrieve information about the run, including metadata and results.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_id = app.new_run(
    input={"name": "world", "radius": 6378, "distance": 147.6},
    instance_id="latest",
)

nextmv.write({"run_id": run_id})
uv run main.py
{
  "run_id": "latest-jreoJsPDR"
}

Retrieve the run information using the run_id with the Application.run_information method. The result includes important metadata, such as the status.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_information = app.run_information(run_id="latest-jreoJsPDR")

nextmv.write(run_information.to_dict())
uv run main.py
{
  "description": "",
  "id": "latest-jreoJsPDR",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "created_at": "2026-07-28T16:34:27Z",
    "duration": 6170.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 5163.0,
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "initiated_at": "2026-07-28T16:34:27.599941Z",
    "input_size": 47.0,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "version",
          "value": "true"
        }
      ]
    },
    "output_size": 25099.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/latest-jreoJsPDR?view=details"
}

Use the .metadata.status_v2 field to determine the status of the run. Please read our documentation on run polling to learn how to wait for a run to finish and retrieve the results.

Once the run completes, you can retrieve the results using the run_id with the Application.run_result method. The method returns the output of the run, along with the run information.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.run_result(run_id="latest-jreoJsPDR")

result = run_result.to_dict()
result["output"].pop("assets", None)  # Assets are omitted here for a cleaner display.

nextmv.write(result)
uv run main.py
{
  "description": "",
  "id": "latest-jreoJsPDR",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "created_at": "2026-07-28T16:34:27Z",
    "duration": 6170.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 5163.0,
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "initiated_at": "2026-07-28T16:34:27.599941Z",
    "input_size": 47.0,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "version",
          "value": "true"
        }
      ]
    },
    "output_size": 25099.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/latest-jreoJsPDR?view=details",
  "output": {
    "options": {
      "details": true
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

Instead of manually polling for a run to complete, you can use the Application.new_run_with_result method and get the result as soon as the run is done. The method automatically takes care of polling, retries, exponential backoff with jitter and timeouts. Use the polling_options keyword argument, passing a PollingOptions object, to customize this behavior.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.new_run_with_result(
    input={"name": "world", "radius": 6378, "distance": 147.6},
    instance_id="latest",
)

result = run_result.to_dict()
result["output"].pop("assets", None)  # Assets are omitted here for a cleaner display.

nextmv.write(result)
uv run main.py
{
  "description": "",
  "id": "latest-7EK1JyPvg",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "created_at": "2026-07-28T16:35:12Z",
    "duration": 4839.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 4394.0,
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "initiated_at": "2026-07-28T16:35:12.567447Z",
    "input_size": 47.0,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "version",
          "value": "true"
        }
      ]
    },
    "output_size": 25099.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/latest-7EK1JyPvg?view=details",
  "output": {
    "options": {
      "details": true
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

You can also use the Application.run_result_with_polling method to wait for a run to complete when getting the results of a run that was already submitted.

Run with options

If an application is designed to accept options, you can pass them when starting runs using the run_options keyword argument. The format for the options is a dict[str, str].

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.new_run_with_result(
    input={"name": "world", "radius": 6378, "distance": 147.6},
    instance_id="latest",
    run_options={"details": "false"},
)

result = run_result.to_dict()
result["output"].pop("assets", None)  # Assets are omitted here for a cleaner display.

nextmv.write(result)
uv run main.py
{
  "description": "",
  "id": "latest-hy611yEvg",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "created_at": "2026-07-28T16:35:32Z",
    "duration": 4540.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 4223.0,
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "initiated_at": "2026-07-28T16:35:33.040824Z",
    "input_size": 47.0,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    },
    "options": {
      "active_options": {
        "details": "false"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "run",
          "value": "false"
        }
      ],
      "request_options": {
        "details": "false"
      }
    },
    "output_size": 25100.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/latest-hy611yEvg?view=details",
  "output": {
    "options": {
      "details": false
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

List runs

Use the Application.list_runs method to list all runs for an application. The method returns a list of Run objects.

import json
import os

from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

runs = app.list_runs()

print(json.dumps([run.to_dict() for run in runs[:3]], indent=2))
uv run main.py
[
  {
    "id": "latest-hy611yEvg",
    "user_email": "sebastian@nextmv.io",
    "name": "",
    "description": "",
    "created_at": "2026-07-28T16:35:32.718397Z",
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "execution_class": "6c9500mb870s",
    "runtime": "python-3_11",
    "status_v2": "succeeded",
    "queuing_priority": 6,
    "queuing_disabled": false,
    "options": {
      "details": "false"
    },
    "request_options": {
      "details": "false"
    },
    "options_summary": [
      {
        "name": "details",
        "value": "false",
        "source": "run"
      }
    ]
  },
  {
    "id": "latest-7EK1JyPvg",
    "user_email": "sebastian@nextmv.io",
    "name": "",
    "description": "",
    "created_at": "2026-07-28T16:35:12.197756Z",
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "execution_class": "6c9500mb870s",
    "runtime": "python-3_11",
    "status_v2": "succeeded",
    "queuing_priority": 6,
    "queuing_disabled": false,
    "options": {
      "details": "true"
    },
    "options_summary": [
      {
        "name": "details",
        "value": "true",
        "source": "version"
      }
    ]
  },
  {
    "id": "latest-jreoJsPDR",
    "user_email": "sebastian@nextmv.io",
    "name": "",
    "description": "",
    "created_at": "2026-07-28T16:34:27.357808Z",
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "execution_class": "6c9500mb870s",
    "runtime": "python-3_11",
    "status_v2": "succeeded",
    "queuing_priority": 6,
    "queuing_disabled": false,
    "options": {
      "details": "true"
    },
    "options_summary": [
      {
        "name": "details",
        "value": "true",
        "source": "version"
      }
    ]
  }
]

You can also filter runs by their status using the status keyword argument, which takes a StatusV2 value.

Get run logs

The Nextmv platform stores logs for applications that correctly implement logging. Each run that produces logs will have them available for inspection. You can retrieve the logs for a run using the Application.run_logs method.

import os

from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_log = app.run_logs(run_id="latest-jreoJsPDR")

print(run_log.log)
uv run main.py
Hello, world
You are 147.6 million km from the sun

If an application is still in a running state, you can use the Application.run_logs_with_polling method to stream the logs as they are produced, albeit with some delay. Use the verbose keyword argument to print the logs to the console as they arrive. This means that you can start a run, stream the logs as they are produced, and get the results once the run completes.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_id = app.new_run(
    input={"name": "world", "radius": 6378, "distance": 147.6},
    instance_id="latest",
)

app.run_logs_with_polling(run_id=run_id, verbose=True)

run_result = app.run_result(run_id=run_id)

result = run_result.to_dict()
result["output"].pop("assets", None)  # Assets are omitted here for a cleaner display.

nextmv.write(result)
uv run main.py
[2026-07-28 16:36:27.115723+00:00] Hello, world
You are 147.6 million km from the sun

{
  "description": "",
  "id": "latest-PLA-JyPDg",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "latest",
    "application_version_id": "",
    "created_at": "2026-07-28T16:36:22Z",
    "duration": 4645.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 4285.0,
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "initiated_at": "2026-07-28T16:36:23.112993Z",
    "input_size": 47.0,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "version",
          "value": "true"
        }
      ]
    },
    "output_size": 25099.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/latest-PLA-JyPDg?view=details",
  "output": {
    "options": {
      "details": true
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

Get the input of a run

As a DecisionOps platform, Nextmv is focused on reproducibility. This means that you can always retrieve the input of a run using the Application.run_input method.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_input = app.run_input(run_id="latest-jreoJsPDR")

nextmv.write(run_input)
uv run main.py
{
  "name": "world",
  "radius": 6378,
  "distance": 147.6
}

Cancel a run

You can cancel a run that is in these states using the Application.cancel_run method.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

app.cancel_run(run_id="latest-jreoJsPDR")
uv run main.py

Run multi-file inputs

Up to now in this how-to guide, we have been using the json content format for showing how to run applications. The Nextmv platform also supports the multi-file content format for running applications. When you run with the multi-file content format, input data is provided from one or more files.

To start a run with the multi-file content format, use the Application.new_run method with the input_dir_path keyword argument, pointing it to a directory containing input files, instead of the input keyword argument. For this example, assume the input data lives in a directory called inputs.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_id = app.new_run(
    instance_id="multi-file",
    input_dir_path="inputs",
)

nextmv.write({"run_id": run_id})
uv run main.py
{
  "run_id": "multi-file-vrzQJsPDR"
}

You can use all the same methods to get the run information, logs, and results as shown in previous sections of this how-to guide. The difference is that the output will be saved to a directory instead of being returned in memory.

Consider the Application.run_result method. You can get the result of the run directly, or customize the output location with the output_dir_path keyword argument. The method automatically untars and decompresses the output to the specified directory. For this example, assume the output data will be saved to a directory called outputs. The information of the run will still be returned by the method, but the output itself will be saved to the outputs directory instead of being included in the returned object.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.run_result(
    run_id="multi-file-vrzQJsPDR",
    output_dir_path="outputs",
)

result = run_result.to_dict()
result.pop("output", None)  # The output was already saved to the "outputs" directory.

nextmv.write(result)
uv run main.py
{
  "description": "",
  "id": "multi-file-vrzQJsPDR",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "multi-file",
    "application_version_id": "version-o5up1zix",
    "created_at": "2026-07-28T16:39:24Z",
    "duration": 3983.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 3538.0,
    "format": {
      "input": {
        "type": "multi-file"
      },
      "output": {
        "type": "multi-file"
      }
    },
    "initiated_at": "2026-07-28T16:39:24.738264Z",
    "input_size": 260.0,
    "metrics": {
      "metrics": {
        "message": "Hello, Patches",
        "value": 1.23
      }
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "version",
          "value": "true"
        }
      ]
    },
    "output_size": 158.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded"
  },
  "name": "",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/multi-file-vrzQJsPDR?view=details"
}

Normally, you specify the content format of the application in the app.yaml manifest, but you can also override it when starting a run with the configuration keyword argument. You give it a RunConfiguration object that specifies the multi-file ContentFormat for the input.

Clone a run

Keeping with the theme of reproducibility, you can clone a run to effectively recreate it with the same input and options. Use the Application.clone_run_with_result method (or Application.clone_run) to clone a run and poll for its result. The method returns a RunResult. Once you have the result, you can access all the same attributes that have been described in this how-to guide, such as the run information, logs, and result.

Here is an example of cloning a run where the original run has a run_id: multi-file-vrzQJsPDR.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.clone_run_with_result(
    cloned_run_id="multi-file-vrzQJsPDR",
    output_dir_path="outputs",
)

result = run_result.to_dict()
result.pop("output", None)  # The output was already saved to the "outputs" directory.

nextmv.write(result)
uv run main.py
{
  "description": "Clone of multi-file-vrzQJsPDR",
  "id": "multi-file-6LguJsPvR",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "multi-file",
    "application_version_id": "version-o5up1zix",
    "created_at": "2026-07-28T16:40:28Z",
    "duration": 3488.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 3032.0,
    "format": {
      "input": {
        "type": "multi-file"
      },
      "output": {
        "type": "multi-file"
      }
    },
    "initiated_at": "2026-07-28T16:40:29.123282Z",
    "input_size": 261.0,
    "metrics": {
      "metrics": {
        "message": "Hello, Patches",
        "value": 1.23
      }
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "run",
          "value": "true"
        }
      ],
      "request_options": {
        "details": "true"
      }
    },
    "output_size": 158.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded",
    "tracking": {
      "cloned_run_id": "multi-file-vrzQJsPDR"
    }
  },
  "name": "multi-file-vrzQJsPDR clone",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/multi-file-6LguJsPvR?view=details"
}

Cloned runs have the run_id of the original run in the .metadata.tracking.cloned_run_id field.

If you inspect the parameters of the Application.clone_run_with_result method, you will find striking similarities to the Application.new_run_with_result method. This is because you can overwrite as many aspects of the cloned run as you want. The default behavior is to use the exact same input, options, and configuration that the original run used. On top of this baseline, you may choose to override whatever you may need, such as the input, instance, etc.

Consider this example of cloning a run where the original run has a run_id: multi-file-vrzQJsPDR. We are going to override the input, instance, and options.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

run_result = app.clone_run_with_result(
    cloned_run_id="multi-file-vrzQJsPDR",
    instance_id="staging",
    input_dir_path="inputs-staging",
    run_options={"details": "false"},
    output_dir_path="outputs",
)

result = run_result.to_dict()
result.pop("output", None)  # The output was already saved to the "outputs" directory.

nextmv.write(result)
uv run main.py
{
  "description": "Clone of multi-file-vrzQJsPDR",
  "id": "staging-FkO91sEDg",
  "metadata": {
    "application_id": "uncanny-rodent",
    "application_instance_id": "staging",
    "application_version_id": "version-o5up1zix",
    "created_at": "2026-07-28T16:41:10Z",
    "duration": 3511.0,
    "error": "",
    "execution_class": "6c9500mb870s",
    "execution_duration": 3016.0,
    "format": {
      "input": {
        "type": "multi-file"
      },
      "output": {
        "type": "multi-file"
      }
    },
    "initiated_at": "2026-07-28T16:41:11.423233Z",
    "input_size": 262.0,
    "metrics": {
      "metrics": {
        "message": "Hello, Sirius",
        "value": 1.23
      }
    },
    "options": {
      "active_options": {
        "details": "false"
      },
      "options_summary": [
        {
          "name": "details",
          "source": "run",
          "value": "false"
        }
      ],
      "request_options": {
        "details": "false"
      }
    },
    "output_size": 157.0,
    "queuing_disabled": false,
    "queuing_priority": 6,
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "runtime": "python-3_11",
    "status_v2": "succeeded",
    "tracking": {
      "cloned_run_id": "multi-file-vrzQJsPDR"
    }
  },
  "name": "multi-file-vrzQJsPDR clone",
  "user_email": "sebastian@nextmv.io",
  "console_url": "https://cloud.nextmv.io/app/uncanny-rodent/run/staging-FkO91sEDg?view=details"
}

Compare runs

You can compare two or more runs to test out hypotheses around differences in metrics, results, or to test the effect of stochasticity or policies in your decision model. Use the Application.compare_runs method to perform a quick comparison of runs. Pass the run IDs to compare using the run_ids keyword argument, which takes an iterable of strings.

The method returns a RunComparison object. Use its to_flat_dict method to get a representation of the differences between the runs that is easy to inspect. Each compared field also carries a has_differences flag that you can use programmatically to detect which attributes changed across runs.

import os

import nextmv
from nextmv import cloud

client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
app = cloud.Application.get(client=client, id="uncanny-rodent")

comparison = app.compare_runs(
    run_ids=["multi-file-vrzQJsPDR", "staging-FkO91sEDg", "multi-file-6LguJsPvR"],
)

nextmv.write(comparison.to_flat_dict())
uv run main.py
{
  "run_ids": [
    "multi-file-vrzQJsPDR",
    "staging-FkO91sEDg",
    "multi-file-6LguJsPvR"
  ],
  "information": {
    "description": {
      "multi-file-vrzQJsPDR": "",
      "staging-FkO91sEDg": "Clone of multi-file-vrzQJsPDR",
      "multi-file-6LguJsPvR": "Clone of multi-file-vrzQJsPDR"
    },
    "id": {
      "multi-file-vrzQJsPDR": "multi-file-vrzQJsPDR",
      "staging-FkO91sEDg": "staging-FkO91sEDg",
      "multi-file-6LguJsPvR": "multi-file-6LguJsPvR"
    },
    "name": {
      "multi-file-vrzQJsPDR": "",
      "staging-FkO91sEDg": "multi-file-vrzQJsPDR clone",
      "multi-file-6LguJsPvR": "multi-file-vrzQJsPDR clone"
    },
    "user_email": {
      "multi-file-vrzQJsPDR": "sebastian@nextmv.io",
      "staging-FkO91sEDg": "sebastian@nextmv.io",
      "multi-file-6LguJsPvR": "sebastian@nextmv.io"
    }
  },
  "metadata": {
    "application_id": {
      "multi-file-vrzQJsPDR": "uncanny-rodent",
      "staging-FkO91sEDg": "uncanny-rodent",
      "multi-file-6LguJsPvR": "uncanny-rodent"
    },
    "application_instance_id": {
      "multi-file-vrzQJsPDR": "multi-file",
      "staging-FkO91sEDg": "staging",
      "multi-file-6LguJsPvR": "multi-file"
    },
    "application_version_id": {
      "multi-file-vrzQJsPDR": "version-o5up1zix",
      "staging-FkO91sEDg": "version-o5up1zix",
      "multi-file-6LguJsPvR": "version-o5up1zix"
    },
    "created_at": {
      "multi-file-vrzQJsPDR": "2026-07-28T16:39:24Z",
      "staging-FkO91sEDg": "2026-07-28T16:41:10Z",
      "multi-file-6LguJsPvR": "2026-07-28T16:40:28Z"
    },
    "duration": {
      "multi-file-vrzQJsPDR": 3983.0,
      "staging-FkO91sEDg": 3511.0,
      "multi-file-6LguJsPvR": 3488.0
    },
    "error": {
      "multi-file-vrzQJsPDR": "",
      "staging-FkO91sEDg": "",
      "multi-file-6LguJsPvR": ""
    },
    "execution_class": {
      "multi-file-vrzQJsPDR": "6c9500mb870s",
      "staging-FkO91sEDg": "6c9500mb870s",
      "multi-file-6LguJsPvR": "6c9500mb870s"
    },
    "execution_duration": {
      "multi-file-vrzQJsPDR": 3538.0,
      "staging-FkO91sEDg": 3016.0,
      "multi-file-6LguJsPvR": 3032.0
    },
    "content_format": {
      "multi-file-vrzQJsPDR": "multi-file",
      "staging-FkO91sEDg": "multi-file",
      "multi-file-6LguJsPvR": "multi-file"
    },
    "initiated_at": {
      "multi-file-vrzQJsPDR": "2026-07-28T16:39:24.738264Z",
      "staging-FkO91sEDg": "2026-07-28T16:41:11.423233Z",
      "multi-file-6LguJsPvR": "2026-07-28T16:40:29.123282Z"
    },
    "input_size": {
      "multi-file-vrzQJsPDR": 260.0,
      "staging-FkO91sEDg": 262.0,
      "multi-file-6LguJsPvR": 261.0
    },
    "output_size": {
      "multi-file-vrzQJsPDR": 158.0,
      "staging-FkO91sEDg": 157.0,
      "multi-file-6LguJsPvR": 158.0
    },
    "queuing_disabled": {
      "multi-file-vrzQJsPDR": false,
      "staging-FkO91sEDg": false,
      "multi-file-6LguJsPvR": false
    },
    "queuing_priority": {
      "multi-file-vrzQJsPDR": 6,
      "staging-FkO91sEDg": 6,
      "multi-file-6LguJsPvR": 6
    },
    "run_type": {
      "multi-file-vrzQJsPDR": "standard",
      "staging-FkO91sEDg": "standard",
      "multi-file-6LguJsPvR": "standard"
    },
    "runtime": {
      "multi-file-vrzQJsPDR": "python-3_11",
      "staging-FkO91sEDg": "python-3_11",
      "multi-file-6LguJsPvR": "python-3_11"
    },
    "status": {
      "multi-file-vrzQJsPDR": "succeeded",
      "staging-FkO91sEDg": "succeeded",
      "multi-file-6LguJsPvR": "succeeded"
    },
    "cloned_run_id": {
      "staging-FkO91sEDg": "multi-file-vrzQJsPDR",
      "multi-file-6LguJsPvR": "multi-file-vrzQJsPDR"
    }
  },
  "metrics": {
    "message": {
      "multi-file-vrzQJsPDR": "Hello, Patches",
      "staging-FkO91sEDg": "Hello, Sirius",
      "multi-file-6LguJsPvR": "Hello, Patches"
    },
    "value": {
      "multi-file-vrzQJsPDR": 1.23,
      "staging-FkO91sEDg": 1.23,
      "multi-file-6LguJsPvR": 1.23
    }
  },
  "options": {
    "details": {
      "multi-file-vrzQJsPDR": "true",
      "staging-FkO91sEDg": "false",
      "multi-file-6LguJsPvR": "true"
    }
  }
}

In the output above, values for the application_instance_id, options, and metrics fields are different across runs. You can iterate over comparison.information, comparison.metadata, comparison.metrics, and comparison.options directly to check the has_differences flag of each ComparisonValues entry, instead of using to_flat_dict.