# Read a run's journal

> Read every step a run took, with its input and output, the branches it followed, and the agent turns inside it.

This guide reads a run's journal: every step it took, with its input and output, the branches it followed, and the agent turns inside it.

The journal is the ordered record of a run, written before the run moves on. [Runs, the journal and durability](https://docs.kindgi.com/v0.1.9/concepts/runs.md#the-journal) explains what it holds and why.

## Before you start

- **A run's id,** on a runtime such as `kindgi dev`, with Kindgi 0.1's CLI. [List runs](https://docs.kindgi.com/v0.1.9/guides/runs/list-runs.md) finds one.
- **For the app's half:** Node 22.12 or later, Python 3.11 or later, or Java 17 or later, with Kindgi 0.1's client, and `KINDGI_API_URL` and `KINDGI_API_TOKEN` set.

## Read it from the CLI

The journal shows what the run did, step by step:

- **TypeScript**

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

- **Python**

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

- **Java**

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

- **Scala**

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

`<run-id>` is the run's id. For a run of `acme.review-order` with order `A-200`, it prints:

```json excerpt
{
  "data": [
    {
      "sequence": 0,
      "kind": "run.started",
      "payload": { "input": { "orderId": "A-200" } },
      "timestamp": "2026-10-03T20:45:08.120Z"
    },
    { "sequence": 1, "kind": "edge.evaluated", "payload": { "edgeId": "e1", "decision": true }, … },
    { "sequence": 2, "kind": "step.started", "nodeId": "order", "payload": { "input": { "orderId": "A-200" } }, … },
    {
      "sequence": 3,
      "kind": "step.completed",
      "nodeId": "order",
      "payload": {
        "output": {
          "items": [{ "sku": "desk", "quantity": 1 }, { "sku": "lamp", "quantity": 2 }],
          "total": 1250,
          "orderId": "A-200",
          "customer": "grace@example.com"
        }
      },
      …
    },
    { "sequence": 4, "kind": "edge.evaluated", "payload": { "edgeId": "e2", "decision": true }, … },
    { "sequence": 5, "kind": "edge.evaluated", "payload": { "edgeId": "e3", "decision": false }, … },
    { "sequence": 6, "kind": "step.started", "nodeId": "hold", "payload": { "input": { "reason": "Over 1000 USD", "orderId": "A-200" } }, … },
    { "sequence": 7, "kind": "step.completed", "nodeId": "hold", "payload": { "output": { "status": "held", "orderId": "A-200" } }, … },
    { "sequence": 8, "kind": "edge.evaluated", "payload": { "edgeId": "e4", "decision": true }, … },
    { "sequence": 9, "kind": "run.completed", "payload": { "output": { "status": "held", "orderId": "A-200" } }, … }
  ],
  "hasMore": false
}
```

Read it top to bottom: the input, the order looked up, the branch taken, the hold, and the result. `e2` fired and `e3` didn't, so `confirm` never ran.

To read only what's new since you last looked, add `--since=<sequence>`, where `<sequence>` is the last entry you read. It returns the entries after that one.

To page it, add `--limit=<n>`, then `--cursor=<nextCursor>` from the page before.

## The entries

| `kind`                                                                |                                                                                                                                   |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `run.started`, `run.completed`, `run.failed`, `run.cancelled`         | The run's start, with its input, and its end, with its output or why it failed.                                                   |
| `step.started`, `step.completed`, `step.failed`                       | A step, with the input it got after mapping, its output, or its error.                                                            |
| `edge.evaluated`                                                      | An edge and its `decision`.                                                                                                       |
| `step.retry-scheduled`                                                | A failed attempt that will be retried.                                                                                            |
| `iteration.started`, `iteration.completed`                            | A pass of a loop; the body's steps carry a `loopContext`.                                                                         |
| `fanout.dispatched`, `fanout.branch-completed`, `fanout.converged`, … | The branches of a fanout step.                                                                                                    |
| `wait.suspended`, `wait.resumed`                                      | The run stopped to wait, and went on.                                                                                             |
| `value.recorded`                                                      | A decision a step made once and keeps when it runs again after a wait: which approval an approval gate waits on, or a clock read. |

## Agent turns

An agent step runs the agent's turn as a run of its own. When an agent step answered something you didn't expect, the turn's journal shows why.

The turn's id is in the step's output, as `runId`. The turn's run has the flow's run as its `parentRunId`, so [List runs](https://docs.kindgi.com/v0.1.9/guides/runs/list-runs.md#the-runs-inside-a-run) finds it too. Read its journal:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs journal <the turn's run id>
  ```

- **Python**

  ```sh
  uv run kindgi runs journal <the turn's run id>
  ```

- **Java**

  ```sh
  ./kindgiw runs journal <the turn's run id>
  ```

- **Scala**

  ```sh
  ./kindgiw runs journal <the turn's run id>
  ```

`<the turn's run id>` is the turn's `runId`. Its journal has the model calls and the tool calls. Each model call has the messages sent, the answer, and its tokens and cost.

## From your app

Your app reads a journal with `runs.journal`. Ask your coding agent: "I want my app to show every step a run took." For reference, this function prints a run's journal, one entry a line:

- **TypeScript**

  `app/print-journal.ts`

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

  const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN

  type JournalEntry = { sequence: number; kind: string; nodeId?: string; payload?: unknown };

  /** Prints a run's journal, one entry a line. */
  export async function printJournal(runId: RunId) {
    const { data } = await kindgi.runs.journal(runId);
    for (const entry of data as JournalEntry[]) {
      console.log(entry.sequence, entry.kind, entry.nodeId ?? '');
    }
  }
  ```

- **Python**

  `app/print_journal.py`

  ```python
  from kindgi.client import Kindgi

  kindgi = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN


  def print_journal(run_id: str) -> None:
      """Prints a run's journal, one entry a line."""
      for entry in kindgi.runs.journal(run_id).data:
          print(entry.sequence, entry.kind, entry.node_id or "")
  ```

- **Java**

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

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.RunJournalPage;
  import java.util.Objects;
  import java.util.UUID;

  public final class PrintJournal {
    private static final Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN

    /** Prints a run's journal, one entry a line. */
    public static void printJournal(UUID runId) {
      for (RunJournalPage.DataItem entry : kindgi.runs().journal(runId).data()) {
        String nodeId = Objects.toString(entry.nodeId(), "");
        System.out.println(entry.sequence() + " " + entry.kind() + " " + nodeId);
      }
    }

    private PrintJournal() {}
  }
  ```

  Each entry is a record: `sequence()`, `kind()`, `nodeId()`, `payload()`, `timestamp()`.

- **Scala**

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

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import java.util.UUID
  import scala.jdk.CollectionConverters._

  object PrintJournal {
    private val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN

    /** Prints a run's journal, one entry a line. */
    def printJournal(runId: UUID): Unit =
      for (entry <- kindgi.runs().journal(runId).data.asScala) {
        println(s"${entry.sequence} ${entry.kind} ${Option(entry.nodeId).getOrElse("")}")
      }
  }
  ```

  Each entry is a Java record: `sequence`, `kind`, `nodeId`, `payload`, `timestamp`.

For a run of `acme.escalate-order` that waited for an approval, it prints:

```text
0 run.started
1 edge.evaluated
2 step.started order
3 step.completed order
4 edge.evaluated
5 step.started hold
6 wait.suspended hold
7 wait.resumed hold
8 step.started hold
9 step.completed hold
10 edge.evaluated
11 edge.evaluated
12 step.started email
13 step.completed email
14 edge.evaluated
15 run.completed
```

`runs.journal` returns the whole journal in one answer. To read a long one in pages, pass `limit` (1 to 100): each page has `hasMore` (`has_more` in Python), and `nextCursor` (`next_cursor`) while more remain, which you pass as `cursor` for the next page. Paging needs a runtime of 0.1.8 or later.

## Next

- [See why runs failed](https://docs.kindgi.com/v0.1.9/guides/runs/see-why-runs-failed.md)
- [Trace an answer](https://docs.kindgi.com/v0.1.9/guides/observability/trace-an-answer.md)
- [List runs](https://docs.kindgi.com/v0.1.9/guides/runs/list-runs.md)
