Read a run's journal
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 explains what it holds and why.
Before you start
Section titled “Before you start”- A run's id, on a runtime such as
kindgi dev, with Kindgi 0.1's CLI. List runs 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_URLandKINDGI_API_TOKENset.
Read it from the CLI
Section titled “Read it from the CLI”The journal shows what the run did, step by step:
pnpm exec kindgi runs journal <run-id>uv run kindgi runs journal <run-id>./kindgiw runs journal <run-id>./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:
{ "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
Section titled “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
Section titled “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 finds it too. Read its
journal:
pnpm exec kindgi runs journal <the turn's run id>uv run kindgi runs journal <the turn's run id>./kindgiw runs journal <the turn's run id>./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
Section titled “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:
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 ?? ''); }}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 "")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().
package acme.app
import com.kindgi.client.Kindgiimport java.util.UUIDimport 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:
0 run.started1 edge.evaluated2 step.started order3 step.completed order4 edge.evaluated5 step.started hold6 wait.suspended hold7 wait.resumed hold8 step.started hold9 step.completed hold10 edge.evaluated11 edge.evaluated12 step.started email13 step.completed email14 edge.evaluated15 run.completedruns.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.