Running shadow tests¶
Info
Learn the concepts and fundamentals of shadow tests in the Explanation page.
A shadow test is an experiment that runs in the background and compares the results of two instances: baseline vs. candidate. When the shadow test has started, any run made on the baseline instance will trigger a run on the candidate instance using the same input and options. The results of the shadow test are often used to determine if a new version of a model is ready to be promoted to production.
Included in this how-to guide are instructions on how to interact with the
actions under the nextmv cloud shadow command tree.
Go the reference section or use the --help option to see all the available
options for each command.
Create a shadow test¶
Use the nextmv cloud shadow create command to
create a new shadow test for an application. A shadow test requires comparisons
to be defined, via the --comparisons option. Additionally, you must specify
the maximum number of runs that the test will run for, using the
--termination-maximum-runs flag.
Comparisons are defined as a JSON object. The keys of the comparisons object
are the baseline instance IDs, and the values are the candidate lists of
instance IDs to compare against the respective baseline. Consider the following
example for doing a simple comparison of the staging vs. production
instances:
Here is an example command that uses the comparison defined above to create a new shadow test for an application.
⏳ Creating shadow test in draft mode...
{
"id": "shadow-7olanfsl",
"name": "shadow-7olanfsl",
"description": "",
"app_id": "uncanny-rodent",
"created_at": "2026-07-26T20:35:53.150310Z",
"updated_at": "2026-07-26T20:35:53.150310Z",
"status": "draft",
"comparisons": [
{
"baseline_instance_id": "production",
"candidate_instance_ids": [
"staging"
]
}
],
"start_events": {},
"termination_events": {
"maximum_runs": 10
}
}
The command above will create a random ID, and use the same identifier for the
shadow test's name. The name of the test is used as a human-readable
You can use the --shadow-test-id and/or --name options to
specify a custom ID and name for the shadow test. For example:
⏳ Creating shadow test in draft mode...
{
"id": "fluffy-shadow-test",
"name": "Fluffy Shadow Test",
"description": "",
"app_id": "uncanny-rodent",
"created_at": "2026-07-26T20:37:02.568386Z",
"updated_at": "2026-07-26T20:37:02.568386Z",
"status": "draft",
"comparisons": [
{
"baseline_instance_id": "production",
"candidate_instance_ids": [
"staging"
]
}
],
"start_events": {},
"termination_events": {
"maximum_runs": 10
}
}
The nextmv cloud shadow create command creates a
shadow test in draft mode. Once the test is created, you must start it.
Start a shadow test¶
As mentioned above, when a shadow test is created, it is in draft mode. A shadow test must be started so that it can begin executing runs and collecting data. There are two ways to start a shadow test:
- Using the
--start-timeoption with thenextmv cloud shadow createcommand. This will start the shadow test at the scheduled time. - Using the
nextmv cloud shadow startcommand. This will start the shadow test as soon as the command is executed.
As an example, here is how to start a shadow test using the nextmv cloud
shadow start command:
Once a shadow test has started, you can stop it at any time or wait for it to complete based on the termination events defined when the test was created.
Get a shadow test¶
Info
The best way to view and interact with shadow test results is in the Nextmv Console.
Use the nextmv cloud shadow metadata command to
retrieve the metadata for a shadow test, using the shadow test ID.
Once the status of the shadow test is completed, you can get the
results using the nextmv cloud shadow get
command. The output includes the runs that were made for the test. If the test
hasn't completed, you can still get partial results.
A shadow test that has already started can be stopped at any time, or
it will stop automatically when the termination events are fulfilled. Once a
shadow test is stopped, then its status will move to completed.
⏳ Getting shadow test...
{
"id": "fluffy-shadow-test",
"name": "Fluffy Shadow Test",
"description": "",
"app_id": "uncanny-rodent",
"created_at": "2026-07-26T20:37:02.568386Z",
"updated_at": "2026-07-26T20:49:09.223748Z",
"status": "started",
"comparisons": [
{
"baseline_instance_id": "production",
"candidate_instance_ids": [
"staging"
]
}
],
"start_events": {},
"termination_events": {
"maximum_runs": 10
},
"grouped_distributional_summaries": [
{
"group_keys": [
"instanceID",
"versionID"
],
"group_values": [
"staging",
"v0.0.2"
],
"indicator_keys": [
"value"
],
"indicator_distributions": {
"value": {
"min": 1.23,
"max": 1.23,
"count": 2,
"mean": 1.23,
"std": 0,
"shifted_geometric_mean": {
"value": 1.2300000000000022,
"shift": 10
},
"percentiles": {
"p01": 1.23,
"p05": 1.23,
"p10": 1.23,
"p25": 1.23,
"p50": 1.23,
"p75": 1.23,
"p90": 1.23,
"p95": 1.23,
"p99": 1.23
}
}
},
"number_of_runs_total": 2
},
{
"group_keys": [
"instanceID",
"versionID"
],
"group_values": [
"production",
"v0.0.2"
],
"indicator_keys": [
"value"
],
"indicator_distributions": {
"value": {
"min": 1.23,
"max": 1.23,
"count": 2,
"mean": 1.23,
"std": 0,
"shifted_geometric_mean": {
"value": 1.2300000000000022,
"shift": 10
},
"percentiles": {
"p01": 1.23,
"p05": 1.23,
"p10": 1.23,
"p25": 1.23,
"p50": 1.23,
"p75": 1.23,
"p90": 1.23,
"p95": 1.23,
"p99": 1.23
}
}
},
"number_of_runs_total": 2
}
],
"runs": [
{
"id": "production-02ENubPDR",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:01.904853Z",
"application_id": "uncanny-rodent",
"application_instance_id": "production",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "succeeded",
"queuing_priority": 6,
"queuing_disabled": false,
"metrics": {
"status": "succeeded",
"indicators": [
{
"name": "value",
"value": 1.23
},
{
"name": "metadata.duration",
"value": 6.175
}
]
},
"options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "version"
}
]
},
{
"id": "staging-S-ENubPDg",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:02.055645Z",
"application_id": "uncanny-rodent",
"application_instance_id": "staging",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "succeeded",
"queuing_priority": 6,
"queuing_disabled": false,
"metrics": {
"status": "succeeded",
"indicators": [
{
"name": "value",
"value": 1.23
},
{
"name": "metadata.duration",
"value": 6.183
}
]
},
"options": {
"details": "true"
},
"request_options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "run"
}
]
},
{
"id": "production-19QNXxPDg",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:05.425988Z",
"application_id": "uncanny-rodent",
"application_instance_id": "production",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "succeeded",
"queuing_priority": 6,
"queuing_disabled": false,
"metrics": {
"status": "succeeded",
"indicators": [
{
"name": "value",
"value": 1.23
},
{
"name": "metadata.duration",
"value": 7.363
}
]
},
"options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "version"
}
]
},
{
"id": "staging-eklNXxPvR",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:05.599041Z",
"application_id": "uncanny-rodent",
"application_instance_id": "staging",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "succeeded",
"queuing_priority": 6,
"queuing_disabled": false,
"metrics": {
"status": "succeeded",
"indicators": [
{
"name": "value",
"value": 1.23
},
{
"name": "metadata.duration",
"value": 7.575
}
]
},
"options": {
"details": "true"
},
"request_options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "run"
}
]
},
{
"id": "production-o5jHuxEvg",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:09.007326Z",
"application_id": "uncanny-rodent",
"application_instance_id": "production",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "succeeded",
"queuing_priority": 6,
"queuing_disabled": false,
"metrics": {
"status": "succeeded",
"indicators": [
{
"name": "value",
"value": 1.23
},
{
"name": "metadata.duration",
"value": 4.773
}
]
},
"options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "version"
}
]
},
{
"id": "staging-a--CNubEvg",
"user_email": "sebastian@nextmv.io",
"name": "",
"description": "",
"created_at": "2026-07-26T20:49:09.235929Z",
"application_id": "uncanny-rodent",
"application_instance_id": "staging",
"application_version_id": "v0.0.2",
"run_type": {
"definition_id": "",
"reference_id": ""
},
"execution_class": "6c9500mb870s",
"runtime": "python-3_11",
"status_v2": "running",
"queuing_priority": 6,
"queuing_disabled": false,
"statistics": {
"status": "unknown",
"indicators": [
{
"name": "metadata.duration",
"value": 0
}
]
},
"options": {
"details": "true"
},
"request_options": {
"details": "true"
},
"options_summary": [
{
"name": "details",
"value": "true",
"source": "run"
}
]
}
]
}
You can list all shadow tests in the application using the nextmv cloud
shadow list command.
⏳ Listing shadow tests...
[
{
"id": "fluffy-shadow-test",
"name": "Fluffy Shadow Test",
"description": "",
"app_id": "uncanny-rodent",
"created_at": "2026-07-26T20:37:02.568386Z",
"updated_at": "2026-07-26T20:49:09.223748Z",
"status": "started",
"comparisons": [
{
"baseline_instance_id": "production",
"candidate_instance_ids": [
"staging"
]
}
],
"start_events": {},
"termination_events": {
"maximum_runs": 10
}
},
{
"id": "shadow-7olanfsl",
"name": "shadow-7olanfsl",
"description": "",
"app_id": "uncanny-rodent",
"created_at": "2026-07-26T20:35:53.150310Z",
"updated_at": "2026-07-26T20:35:53.150310Z",
"status": "draft",
"comparisons": [
{
"baseline_instance_id": "production",
"candidate_instance_ids": [
"staging"
]
}
],
"start_events": {},
"termination_events": {
"maximum_runs": 10
}
}
]
Stop a shadow test¶
A shadow test will continue to execute until it stops, which is equivalent to it being completed. There are two ways to stop a shadow test:
- Using the
--termination-maximum-runsand/or--termination-timeoptions with thenextmv cloud shadow createcommand. Termination events will cause the shadow test to stop. - Using the
nextmv cloud shadow stopcommand. This will stop the shadow test as soon as the command is executed.
When using the nextmv cloud shadow stop command, you must use the --intent
flag to track if you want to complete or cancel the shadow test. As an example,
here is how to stop a shadow test and mark it as completed:
Update a shadow test¶
You can update attributes of a shadow test with the
nextmv cloud shadow update command, such as its:
- Name
- Description
Please use the --help option on the nextmv cloud shadow
update command to see all the available options for
updating a shadow test.
You cannot update the ID of a shadow test.
⏳ Updating shadow test...
✅ Shadow test fluffy-shadow-test updated successfully in application uncanny-rodent.
{
"id": "fluffy-shadow-test",
"name": "Updated Shadow Test Name",
"description": "Updated description for the shadow test",
"created_at": "2026-07-26T20:37:02.568386Z",
"updated_at": "2026-07-26T22:34:21.663911Z"
}
Delete a shadow test¶
Warning
Deleting a shadow test is irreversible. All the data associated with the shadow test will be permanently deleted.
Delete a shadow test using the nextmv cloud shadow
delete command. The CLI will prompt you to confirm
deletion but you can override it with the --yes option.