# Follow a run's events

> Get each step of a run as it starts and ends, from your app (TypeScript, Python, Java or Scala) or the CLI, through to the run's end.

A run's events are its journal as it's written: the run starting, each step starting and ending, waits, and the run's end. Follow them to show progress, or to react when the run ends without polling.

Ask your coding agent: "I want my app to show a run's progress as it happens." For reference, the code looks like this. It starts a run in the background, then prints each event until the run ends:

- **TypeScript**

  `scripts/follow-a-run.ts`

  ```ts
  import { createClient } from '@kindgi/sdk/client';

  const kindgi = createClient();

  const run = await kindgi.runs.start({
    flow: 'acme.reserve-order',
    input: { orderId: 'A-400' },
    options: { wait: false },
  });

  for await (const event of kindgi.runs.follow(run.id)) {
    console.log(event.sequence, event.kind, event.nodeId ?? '');
  }
  // The loop ends after run.completed, run.failed or run.cancelled.
  ```

  ```text
  0 run.started
  2 run.step-started reserve
  3 run.step-retry-scheduled reserve
  4 run.step-retry-scheduled reserve
  5 run.step-completed reserve
  7 run.completed
  ```

- **Python**

  `scripts/follow_a_run.py`

  ```python
  from kindgi.client import Kindgi

  kindgi = Kindgi()

  run = kindgi.runs.start(
      flow="acme.reserve-order", input={"orderId": "A-400"}, options={"wait": False}
  )

  for event in kindgi.runs.follow(run.id):
      print(event.sequence, event.kind, event.node_id or "")
  # The loop ends after run.completed, run.failed or run.cancelled.
  ```

  ```text
  0 run.started
  2 run.step-started reserve
  3 run.step-retry-scheduled reserve
  4 run.step-retry-scheduled reserve
  5 run.step-completed reserve
  7 run.completed
  ```

- **Java**

  `src/main/java/com/acme/app/FollowRun.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.EventStream;
  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.Run;
  import com.kindgi.client.models.RunEvent;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;
  import java.util.Objects;

  public final class FollowRun {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create();

      Run run = kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.reserve-order")
          .input(Map.of("orderId", "A-400"))
          .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
          .build());

      try (EventStream<RunEvent> events = kindgi.runs().follow(run.id())) {
        for (RunEvent event : events) {
          System.out.println(event.sequence() + " " + event.kind() + " " + Objects.toString(event.nodeId(), ""));
        }
      }
      // The loop ends after run.completed, run.failed or run.cancelled.
    }
  }
  ```

  ```text
  0 run.started
  2 run.step-started reserve
  3 run.step-retry-scheduled reserve
  4 run.step-retry-scheduled reserve
  5 run.step-completed reserve
  7 run.completed
  ```

- **Scala**

  `src/main/scala/acme/app/FollowRun.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import com.kindgi.client.models.StartRunBody
  import scala.jdk.CollectionConverters._
  import scala.util.Using

  object FollowRun {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create()

      val run = kindgi.runs().start(StartRunBody.WithFlow.builder()
        .flow("acme.reserve-order")
        .input(Map("orderId" -> "A-400").asJava)
        .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
        .build())

      Using.resource(kindgi.runs().follow(run.id)) { events =>
        events.forEachRemaining { event =>
          println(s"${event.sequence} ${event.kind} ${Option(event.nodeId).getOrElse("")}")
        }
      }
      // The loop ends after run.completed, run.failed or run.cancelled.
    }
  }
  ```

  ```text
  0 run.started
  2 run.step-started reserve
  3 run.step-retry-scheduled reserve
  4 run.step-retry-scheduled reserve
  5 run.step-completed reserve
  7 run.completed
  ```

The stream starts from the run's first event, whenever you open it, so you don't miss what happened before. It ends after `run.completed`, `run.failed` or `run.cancelled`; the last event's `payload.output` is the run's output.

## The events

Each event has the run's id, a `kind`, a `sequence` (its place in the journal), a `timestamp`, the step's `nodeId` for step events, and a `payload`:

| `kind`                                             | When                                                                                                                      | `payload`                                 |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `run.started`                                      | The run started.                                                                                                          | `input`                                   |
| `run.step-started`                                 | A step started.                                                                                                           | `input`                                   |
| `run.step-completed`                               | A step ended.                                                                                                             | `output`                                  |
| `run.step-failed`                                  | A step failed.                                                                                                            | `message`                                 |
| `run.step-retry-scheduled`                         | A step failed and will be [retried](https://docs.kindgi.com/v0.1.10/guides/flows/retry-a-step.md).                        | `attempt`, `nextDelayMs`, `previousError` |
| `run.iteration-started`, `run.iteration-completed` | A pass of a [loop](https://docs.kindgi.com/v0.1.10/guides/flows/loop.md).                                                 | `iteration`, …                            |
| `run.wait-suspended`, `run.wait-resumed`           | The run stopped to wait, and went on ([approvals](https://docs.kindgi.com/v0.1.10/guides/flows/wait-for-an-approval.md)). |                                           |
| `run.completed`                                    | The run completed.                                                                                                        | `output`                                  |
| `run.failed`                                       | The run failed.                                                                                                           | `message`                                 |
| `run.cancelled`                                    | The run was cancelled.                                                                                                    |                                           |

Edge decisions are in the [journal](https://docs.kindgi.com/v0.1.10/guides/runs/read-the-journal.md), not in the stream.

## Reconnecting

The server ends a stream after 5 minutes; a run that waits longer needs a new one. Each event's id is `<run id>:<sequence>`, and a new stream opened with the last id you saw (the `Last-Event-Id` header) starts after it.

`runs.follow` does this for you, in TypeScript, Python, Java and Scala (the Java client's `runs().follow`): it reconnects after a dropped connection or the server's limit, and ends only with the run. `runs.followProgress` (Python: `runs.follow_progress`; Java: `runs().followProgress`) follows the [progress stream](https://docs.kindgi.com/v0.1.10/guides/runs/follow-from-the-browser.md) (no payloads) the same way.

- In Python, `runs.stream` is the plain call: it ends when the server closes the stream, and `last_event_id=f"{run.id}:{event.sequence}"` opens it again after that event.
- In Java and Scala, `runs().stream(runId)` is the plain call too, and `RunsStreamParams.builder().lastEventId(…)` opens it again after an event.
- In TypeScript, `runs.stream` still follows the run to its end, as `follow` does, but it's deprecated: in a later release, announced in advance, it becomes the plain call, as in Python.

## From the CLI

- **TypeScript**

  ```sh
  pnpm exec kindgi runs stream <run-id>
  ```

- **Python**

  ```sh
  uv run kindgi runs stream <run-id>
  ```

- **Java**

  ```sh
  ./kindgiw runs stream <run-id>
  ```

- **Scala**

  ```sh
  ./kindgiw runs stream <run-id>
  ```

It prints one event per line, as JSON:

```text excerpt
{"eventId":"28795fe7-a357-4d67-8740-1bda88f3b85c:0","runId":"28795fe7-a357-4d67-8740-1bda88f3b85c",…,"kind":"run.started","sequence":0,"payload":{"input":{"orderId":"A-300"}}}
{"eventId":"28795fe7-a357-4d67-8740-1bda88f3b85c:2",…,"kind":"run.step-started","sequence":2,"nodeId":"reserve","payload":{"input":{"orderId":"A-300"}}}
…
{"eventId":"28795fe7-a357-4d67-8740-1bda88f3b85c:7",…,"kind":"run.completed","sequence":7,"payload":{"output":{"attempt":3,"orderId":"A-300","reserved":true}}}
```

> **Note**
>
> In this release, `kindgi runs stream` prints the events when the run ends, all at once. To watch them arrive, use the client.

To show a run's progress in a browser, without giving the page your API token, see [Follow a run from the browser](https://docs.kindgi.com/v0.1.10/guides/runs/follow-from-the-browser.md).
