# Traces and usage

The trace is the tree React rendered, as plain JSON: a node for each agent, step and `<Parallel>`, with each step's live state in its props.

```tsx
import { run } from "june";
import type { ReactNode } from "react";

declare const workflow: ReactNode;

const { tree, usage } = await run(workflow, {
  onUpdate: (tree) => console.log(JSON.stringify(tree)),
});
```

A trace is structured execution state, not a log stream. `onUpdate` gives you the latest tree after each commit, and `run()` resolves with the settled tree as `tree`, next to `usage`, the run's total. From the terminal, [`june run --json`](https://june.dev/docs/cli.md#june-run) prints `steps`, `usage` and `tree` together.

## Reading the trace

Take the workflow from the [Quickstart](https://june.dev/docs/workflows.md#quickstart): `summary`, then `risks` and `tests` inside `<Parallel>`. Midway through, once `summary` is done, its trace looks like this (abridged):

```json
{
  "type": "ROOT",
  "props": {},
  "children": [
    {
      "type": "agent",
      "props": { "model": "gpt-5-mini", "tools": [] },
      "children": [
        {
          "type": "step",
          "props": {
            "id": "summary",
            "status": "done",
            "result": "Adds --json to june run.",
            "durationMs": 63,
            "usage": { "inputTokens": 10, "outputTokens": 5, "totalTokens": 15 }
          },
          "children": [
            {
              "type": "parallel",
              "props": {},
              "children": [
                {
                  "type": "step",
                  "props": { "id": "risks", "status": "running" },
                  "children": []
                },
                {
                  "type": "step",
                  "props": { "id": "tests", "status": "running" },
                  "children": []
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

The nesting is the workflow's dependencies: `risks` and `tests` are inside `summary` because they were rendered from its result, and they didn't exist until it finished. The statuses say where the run is: `summary` is `done` with its `result`, timing and `usage`, and both children are `running` at once because they're inside `<Parallel>`. A step past the [concurrency limit](https://june.dev/docs/workflows/parallelism.md#concurrency) would show as `queued`, and a failed one as `failed` with its `error`.

`summarize` walks a trace and prints one line per step. It counts any node with a `status` as a step, so it covers `<Write>` and the training components too:

```ts
// trace-summary.ts
import type { StepState, TraceNode } from "june";

export function summarize(tree: TraceNode): string[] {
  const lines: string[] = [];

  const visit = (node: TraceNode) => {
    const state = node.props as Partial<StepState> & { id?: string };
    if (state.status) {
      const parts = [`${state.status} ${state.id}`];
      if (state.resumed) parts.push("(resumed)");
      if (state.durationMs !== undefined) parts.push(`${state.durationMs}ms`);
      if (state.usage?.totalTokens !== undefined) {
        parts.push(`${state.usage.totalTokens} tokens`);
      }
      if (state.error)
        parts.push(`${state.error.name}: ${state.error.message}`);
      lines.push(parts.join(" "));
    }
    node.children.forEach(visit);
  };

  visit(tree);
  return lines;
}
```

On the [`changes → notes`](https://june.dev/docs/workflows/composition.md#passing-results-down) workflow, resumed from a snapshot that already had `changes`, it prints:

```text
done changes (resumed)
done notes 812ms 412 tokens
```

- **Copy what you keep.** The tree `onUpdate` receives is the live tree, changed in place by later commits. `run()`'s resolved `tree` is a copy.
- **Expect batching.** React batches commits, so `onUpdate` isn't called once per change: two steps can finish in one update, and streamed text arrives in chunks.
- **Read a failed run's tree from its handle.** A failure that no `<Catch>` handles unmounts the tree, so the last update before the run rejects is an empty tree, and `onUpdate` isn't called after the run aborts. [`handle.tree()`](https://june.dev/docs/workflows/running.md#when-a-run-ends) keeps the tree the run ended with.
- **Parse JSON, not the CLI's text.** `june run`'s progress output may change in a minor version ([Compatibility](https://june.dev/docs/reference/compatibility.md#less-stable)); read `--json` instead.

## Nodes

Every node is `{ type, props, children }`. The root's `type` is `ROOT`.

| `type`      | From                    | Props                                                                                                                                              |
| ----------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent`     | `<Agent>`               | `id`, `model` (the model id), `tools` (their names), and `capabilities` when the agent [declares some](https://june.dev/docs/workflows/capabilities.md#seeing-what-an-agent-has) |
| `step`      | `<Step>`                | `id`, `prompt`, and the [step fields](#step-fields)                                                                                                |
| `parallel`  | `<Parallel>`            | `id`                                                                                                                                               |
| `write`     | `june/copy`'s `<Write>` | `id`, `voice`, `request`, and the step fields                                                                                                      |
| `fine-tune` | `<FineTune>`            | `id`, `model`, `trainer`, and the step fields                                                                                                      |
| `evaluate`  | `<Evaluate>`            | `id`, `suite`, and the step fields                                                                                                                 |

- A [`useStep`](https://june.dev/docs/workflows/use-step.md) has no node. The component that calls it renders whatever it likes.
- A string your components render becomes a `TEXT` node with a `value`, and any other host element they render becomes a node with its props.
- Children are in render order. A step renders its children inside its own node once it finishes, so the tree's nesting is the workflow's dependencies.
- Props that are `undefined`, such as an agent without an `id`, are left out of the JSON.
- A step rendered in two places, as in [fan-in](https://june.dev/docs/workflows/parallelism.md#fan-in), has two nodes with the same state.
- When a [`<Catch>`](https://june.dev/docs/workflows/recovery.md) falls back, its children's nodes are replaced by the fallback's.

## Step fields

The fields every step node has, typed as `StepState` in `june`. All of them are plain JSON.

| Field                  |                                                                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `status`               | `queued`, `running`, `done` or `failed`                                                                                    |
| `toolCalls`            | each tool call's `toolCallId`, `toolName`, `input`, and `output` once it has one                                           |
| `result`               | the step's result, once `done`                                                                                             |
| `error`                | `{ name, message }`, once `failed`                                                                                         |
| `startedAt`, `endedAt` | ISO 8601 timestamps from when the step first started running to when it settled, not time spent `queued` before it started |
| `durationMs`           | the time between them, from a monotonic clock, including any time spent waiting on an approval                             |
| `partialText`          | text streamed so far, with [`stream`](https://june.dev/docs/workflows/agent-and-step.md#streaming)                                                       |
| `model`                | `{ provider, modelId }` of the last model call                                                                             |
| `usage`                | tokens, summed across every model call the step made                                                                       |
| `finishReason`         | why the last model call stopped, such as `stop` or `length`                                                                |
| `calls`                | model calls a step [reported](https://june.dev/docs/workflows/use-step.md#reporting-model-calls) itself                                                  |
| `resumed`              | `true` when the result came from a [snapshot](https://june.dev/docs/workflows/resume.md)                                                                 |
| `job`, `progress`      | a training step's job and progress; see [Training in workflows](https://june.dev/docs/training/workflows.md#trace)                                       |

`status` and `toolCalls` are always there. The rest appear as they apply:

- A `queued` step has only `status` and `toolCalls`. `startedAt` is set when it starts running.
- `model`, `usage` and `finishReason` are set on a `<Step>` each time its agent loop returns, whether it finished or paused for an approval, and on any step each time it reports a call.
- A resumed step is `done` with its `result` and `resumed: true`, and has no timing, `model` or `usage`.

A trace holds your data: prompts, results, and tool inputs and outputs. Treat a saved trace as being as sensitive as the prompts in it. [Security and privacy](https://june.dev/docs/reference/security.md) covers the rest.

## Usage

`usage` has the AI SDK's token counts, without the provider's raw usage: `inputTokens`, `outputTokens` and `totalTokens`, plus `inputTokenDetails` (`noCacheTokens`, `cacheReadTokens`, `cacheWriteTokens`) and `outputTokenDetails` (`textTokens`, `reasoningTokens`). A count the provider doesn't report is `undefined`.

- A `<Step>`'s `usage` covers its whole tool loop, including the calls an approval splits it into.
- A `useStep`'s covers the calls it [reported](https://june.dev/docs/workflows/use-step.md#reporting-model-calls), and nothing else: June can't see its model calls.
- The run's `usage` is the sum of its steps', including steps that have no node, such as a `useStep`'s. Resumed steps add nothing, since they didn't run, and neither do steps a `<Catch>` aborted or failed attempts a `retry` cleared. It's `undefined` when no step reported usage.

## Next

- [React as the workflow](https://june.dev/docs/workflows.md): back to the overview.
- [Running workflows](https://june.dev/docs/workflows/running.md): `onUpdate` and the other run options.
