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

# Progress reporting

> Report the progress of your recipe to Adaptive

## Overview

Progress reporting allows you to track and display the progress of your recipe's execution in Adaptive Engine. This is useful for long-running recipes where you want to monitor how much work has been completed and how much remains.

Progress reporting consists of two main components:

1. **Define stages**: Define logical phases of your recipe execution up-front
2. **Update progress**: Report completion status for each stage

## Defining stages

Use `ctx.job.register_stages()` to define the stages of your recipe:

```python theme={null}
# Register recipe stages
from adaptive_harmony.runtime import recipe_main, RecipeContext


@recipe_main
async def main(ctx: RecipeContext):

    stages = ["Generating completions", "Evaluating completions"]
    ctx.job.register_stages(stages)
```

Stages are displayed in the order they are registered and represent logical phases of your recipe execution.

## Reporting progress

### Generic notifier

Adaptive Engine provides a simple interface to report progress, which you can access via `RecipeContext` (generally shown as the `ctx` input parameter):

```python theme={null}
# for training progress report
stage = "Generating grades"
total = 1000
current = 50
ctx.job.report_progress(stage, total, current)
```

This will update the Adaptive run details UI to show you are now at 5% completion on the "Generating grades" stage (50/1000 are done).

### Stage-specific notifier

If you don't want to constantly pass a "stage" parameter, you can create a `StageNotifier`, which is a copy of `ctx.job` that holds a default stage. All[Training classes](/v0.14/harmony/training#trainers) take in an optional `stage_notifier` parameter, which - as the name indicates - must be a stage-bounded notifier. This means you can follow the progress of training in # training samples processed from the Adaptive UI without having to edit the Trainer classes.

```python theme={null}
from adaptive_harmony.common import GRPO
stage_notifier = ctx.job.stage_notifier(stage="GRPO Training")

trainer = GRPO(
    ...,
    stage_notifier=stage_notifier,
    ...
)
```

### Single event notifications

Finally, it's frequent that you just want to report that something happened in your recipe, not necessarily that "# X out of a total # Y" samples/steps have been processed. A good example is reporting that a model has been spawned, which is a milestone in the recipe, but is a single action. For these cases, to decrease code verbosity you can use the `SimpleProgressNotifier` progress manager:

```python theme={null}
from adaptive_harmony.runtime import SimpleProgressNotifier
from adaptive_harmony.parameters import Model

async with SimpleProgressNotifier(ctx.job.stage_notifier("Spawning judge model")):
    judge_builder = await Model(model_key="llama-3.3-70b").to_builder(ctx)
    judge = await judge_builder.spawn_inference("judge")
```

<Info>
  `SimpleProgressNotifier` works in both synchronous and asynchronous contexts.
</Info>

## Linking training monitoring dashboards

When using a metric logger (W\&B, MLflow, TensorBoard), you can link the logger's monitoring dashboard directly to your progress stages. This allows users to click through from the Adaptive UI to view detailed training metrics.

```python theme={null}
from adaptive_harmony.common.grpo import GRPO
from adaptive_harmony.metric_logger import get_prod_logger

logger = get_prod_logger()

# The logger's monitoring link is automatically attached to progress reporting
trainer = GRPO(
    dataset=train_dataset,
    model=policy_model,
    grader=safety_grader,
    logger=logger,
    stage_notifier=ctx.job.stage_notifier("GRPO Training")
)
await trainer.run()
```

All training classes automatically link their logger's `training_monitoring_link` to their stage notifier. This means when users view the run's progress in Adaptive, they can click a link to view detailed metrics in W\&B, MLflow, or TensorBoard.

### Manual monitoring link configuration

You can also manually set or override monitoring links in two ways:

from adaptive\_harmony.metric\_logger import get\_prod\_logger

logger = get\_prod\_logger()

**1. Set a global monitoring link for all reported stages of a job:**

```python theme={null}
# Set monitoring link for the entire job (get it from your logger)
ctx.job.set_monitoring_link(logger.training_monitoring_link)


# All subsequent progress reports will use this link
ctx.job.report_progress(stage="Training", total=100, current=50)
```

**2. Pass a monitoring link to a specific progress report:**

```python theme={null}
# Override the monitoring link for a specific stage
ctx.job.report_progress(
    stage="Training",
    total=100,
    current=50,
    monitoring_link=logger.training_monitoring_link
)
```

This is useful when:

* You're not using a training class but still want to link to external dashboards
* You want different stages to link to different monitoring dashboards
* You're using a custom monitoring solution not covered by the built-in loggers

<Info>
  See [Log job metrics](/v0.14/harmony/logging) to learn about configuring metric loggers and accessing monitoring dashboards.
</Info>

## Visualizing progress on the Adaptive UI

Once you have define your progress reporting strategy, uploaded and launched your recipe, the stages and progress will be displayed as below in the adaptive Engine:

<Frame caption={<span>Progress visualization of a run on the Adaptive Engine</span>}>
  <img src="https://mintcdn.com/adaptiveml/nxrXfjE5HXjTB4Su/static/run_progress.png?fit=max&auto=format&n=nxrXfjE5HXjTB4Su&q=85&s=a256cdda2048d0a8803d94ad609127c5" width="1274" height="324" data-path="static/run_progress.png" />
</Frame>

The example above illustrate the reporting defined with the code below:

```python theme={null}
from adaptive_harmony.runtime import recipe_main, RecipeContext


@recipe_main
async def recipe(config: EvalConfig, ctx: RecipeContext):
    # load dataset...
    # spawn models...

    # Register recipe stages
    inference_stage, eval_stage = "Generating completions", "Evaluating completions"

    stages = [inference_stage, eval_stage]
    ctx.job.register_stages(stages)

    batch_size = 20

    # batch method not implemented here
    inference_batches = batch(dataset, batch_size=batch_size)
    completions = []
    for i, batch in inference_batches:
        completions.extend([model.generate(thread) for thread in batch])
        ctx.job.report_progress(inference_stage, len(dataset), i * batch_size + len(batch))

    eval_batches = batch(completions, batch_size=batch_size)
    grades = []
    for i, batch in eval_batches:
        grades.extend([grader.grade(thread) for thread in batch])
        ctx.job.report_progress(
            eval_stage,
            len(completions),
            i * batch_size + len(batch)
        )

    print("Recipe finished")
```
