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

# Progress

> Report and visualize user-defined progress indicators during job execution to provide visibility into completion status and the estimated remaining time.

Tilebox supports user-defined progress indicators during the execution of a job. This can be useful to provide visibility into the execution and the expected duration of a job, especially for longer running jobs.

<Frame>
  <img src="https://mintcdn.com/tilebox/9yPiIuCV-2WPK6fa/assets/workflows/progress/progress-light.png?fit=max&auto=format&n=9yPiIuCV-2WPK6fa&q=85&s=177011b0cf3caba02e5da324edba3a20" alt="Tilebox Workflows progress indicators" className="dark:hidden" width="361" height="203" data-path="assets/workflows/progress/progress-light.png" />

  <img src="https://mintcdn.com/tilebox/9yPiIuCV-2WPK6fa/assets/workflows/progress/progress-dark.png?fit=max&auto=format&n=9yPiIuCV-2WPK6fa&q=85&s=fb62a7940e1be4e8e94ae0599a39c2e4" alt="Tilebox Workflows progress indicators" className="hidden dark:block" width="358" height="201" data-path="assets/workflows/progress/progress-dark.png" />
</Frame>

## Tracking Progress

Progress indicators in Tilebox use a `done` / `total` model. Tasks can increase a `total` value to specify the total work to be done, and the same or any other task can increase a `done` counter to track the amount of work that has already been completed.

Progress tracking is always done at a task level. Each task can report its progress updates, as increases in `done` and `total` independently, and the job's total progress is the sum of all tasks' progress.

<CodeGroup>
  ```python Python theme={"system"}
  from tilebox.workflows import Task, ExecutionContext

  class ProgressRootTask(Task):
      n: int

      def execute(self, context: ExecutionContext) -> None:
          # report that 10 units of work need to be done
          context.progress().add(self.n)  # [!code ++]

          for _ in range(self.n):
              context.submit_subtask(ProgressSubTask())

  class ProgressSubTask(Task):
      def execute(self, context: ExecutionContext) -> None:
          # report that one unit of work has been completed
          context.progress().done(1)  # [!code ++]
  ```

  ```go Go theme={"system"}
  type ProgressRootTask struct {
  	N int
  }

  func (t *ProgressRootTask) Execute(ctx context.Context) error {
      // report that 10 units of work need to be done
  	err := workflows.DefaultProgress().Add(ctx, uint64(t.N)) // [!code ++]
  	if err != nil {
  		return err
  	}

  	for range t.N {
  		_, err := workflows.SubmitSubtask(ctx, &ProgressSubTask{})
  		if err != nil {
  			return err
  		}
  	}

  	return nil
  }

  type ProgressSubTask struct{}

  func (t *ProgressSubTask) Execute(ctx context.Context) error {
      // report that one unit of work has been completed
  	err := workflows.DefaultProgress().Done(ctx, 1) // [!code ++]
  	if err != nil {
  		return err
  	}

  	return nil
  }
  ```
</CodeGroup>

## Multiple Progress Indicators

A job can have multiple independent progress indicators. This is useful when a job consists of multiple steps, that each benefits from having its own progress indicator.
To create a new progress indicator, call `context.progress(name)` with a unique `name` for the indicator.

<CodeGroup>
  ```python Python lines focus={5-6,15-18,27-28,33-34,38-39} theme={"system"}
  class MultiProgressWorkflowRoot(Task):
      n: int

      def execute(self, context: ExecutionContext) -> None:
          # initialize a progress indicator for the finalize task
          context.progress("finalize").add(1)

          process = context.submit_subtask(Process(self.n))
          context.submit_subtask(Cleanup(), depends_on=[process])

  class Process(Task):
      n: int

      def execute(self, context: ExecutionContext) -> None:
          # initialize two progress indicators for the two steps,
          # with a total work of n for each
          context.progress("step1").add(self.n)
          context.progress("step2").add(self.n)

          # now submit N subtasks for the two steps
          for _ in range(self.n):
              step1 = context.submit_subtask(Step1())
              context.submit_subtask(Step2(), depends_on=[step1])

  class Step1(Task):
      def execute(self, context: ExecutionContext) -> None:
          # 1 unit of work for step1 is done
          context.progress("step1").done(1)


  class Step2(Task):
      def execute(self, context: ExecutionContext) -> None:
          # 1 unit of work for step2 is done
          context.progress("step2").done(1)

  class Finalize(Task):
      def execute(self, context: ExecutionContext) -> None:
          # finalize is done
          context.progress("finalize").done(1)
  ```

  ```go Go lines expandable focus={7,32,37,62,74,86} theme={"system"}
  type MultiProgressWorkflowRoot struct {
      N int
  }

  func (t *MultiProgressWorkflowRoot) Execute(ctx context.Context) error {
      // initialize a progress indicator for the finalize task
      err := workflows.Progress("finalize").Add(ctx, 1)
      if err != nil {
          return err
      }

      process, err := workflows.SubmitSubtask(ctx, &Process{N: t.N})
      if err != nil {
          return err
      }

      _, err = workflows.SubmitSubtask(ctx, &Cleanup{}, subtask.WithDependencies(process))
      if err != nil {
          return err
      }

      return nil
  }

  type Process struct {
      N int
  }

  func (t *Process) Execute(ctx context.Context) error {
  	// initialize two progress indicators for the two steps,
  	// with a total work of n for each
      err := workflows.Progress("step1").Add(ctx, uint64(t.N))
      if err != nil {
          return err
      }

      err = workflows.Progress("step2").Add(ctx, uint64(t.N))
      if err != nil {
          return err
      }

      // now submit N subtasks for the two steps
      for range t.N {
          step1, err := workflows.SubmitSubtask(ctx, &Step1{})
          if err != nil {
              return err
          }

          _, err = workflows.SubmitSubtask(ctx, &Step2{}, subtask.WithDependencies(step1))
          if err != nil {
              return err
          }
      }

      return nil
  }

  type Step1 struct{}

  func (t *Step1) Execute(ctx context.Context) error {
      // 1 unit of work for step1 is done
      err := workflows.Progress("step1").Done(ctx, 1)
      if err != nil {
          return err
      }

      return nil
  }

  type Step2 struct{}

  func (t *Step2) Execute(ctx context.Context) error {
      // 1 unit of work for step2 is done
      err := workflows.Progress("step2").Done(ctx, 1)
      if err != nil {
          return err
      }

      return nil
  }

  type Cleanup struct{}

  func (t *Cleanup) Execute(ctx context.Context) error {
      // finalize is done
      err := workflows.Progress("finalize").Done(ctx, 1)
      if err != nil {
          return err
      }

      return nil
  }
  ```
</CodeGroup>

## Querying Progress

At any time during a job's execution, you can query the current progress of a job using the `find` method on the job client. The returned job object contains a `progress` field that contains the current progress of the job.

<CodeGroup>
  ```python Python theme={"system"}
  job = job_client.find(job_id)
  for indicator in job.progress:
      print(f"{indicator.label}: {indicator.done}/{indicator.total}")
  ```

  ```go Go theme={"system"}
  job, err := client.Jobs.Get(ctx, jobID)
  if err != nil {
      slog.Error("Failed to get job", slog.Any("error", err))
      return
  }

  for _, indicator := range job.Progress {
      fmt.Printf("%s: %d/%d\n", indicator.Label, indicator.Done, indicator.Total)
  }
  ```
</CodeGroup>

```plaintext Output theme={"system"}
finalize: 1/1
step1: 4/4
step2: 4/4
```

### Progress Display in interactive environments

When running in an interactive environment such as a Jupyter notebook and the cell output is a Tilebox job object, the
job is automatically rendered, including its progress indicators.

<CodeGroup>
  ```python Python theme={"system"}
  job = job_client.find(job_id)
  job  # trigger notebook cell output
  ```
</CodeGroup>

<Frame>
  <img src="https://mintcdn.com/tilebox/7AgdzHHwTyCoIrsR/assets/workflows/progress/progress-cell-output-light.png?fit=max&auto=format&n=7AgdzHHwTyCoIrsR&q=85&s=07fe43d05425ac4a09d1052d90661b52" alt="Tilebox Workflows job object as Jupyter Cell output" className="dark:hidden" width="559" height="345" data-path="assets/workflows/progress/progress-cell-output-light.png" />

  <img src="https://mintcdn.com/tilebox/7AgdzHHwTyCoIrsR/assets/workflows/progress/progress-cell-output-dark.png?fit=max&auto=format&n=7AgdzHHwTyCoIrsR&q=85&s=349c835d638edcb683fedd264ac4bf6d" alt="Tilebox Workflows job object as Jupyter Cell output" className="hidden dark:block" width="554" height="346" data-path="assets/workflows/progress/progress-cell-output-dark.png" />
</Frame>

## Progress idempotency

Since tasks may fail and can subsequently be retried, it's possible that a task is executed more than once. This means that a task may report progress more than once.
To avoid double-counting such progress updates, Tilebox only considers the progress reported by the last execution of a task.
