# Step identity

A step's `id` names it within a run, and its cache key records what it asks. Together they decide when a step runs, when it shares another step's result, and when it reuses a snapshot. Three rules cover most of it:

1. **An id identifies one logical piece of work within a run.**
2. **Derive a repeated step's id from stable input data,** such as a file path or a record id.
3. **Changing what a `<Step>` asks changes its cache key,** even when its id stays the same, so it runs again instead of reusing an old result. A `useStep`'s cache key is only the `cacheKey` you give it, so change that yourself when its work changes.

```tsx
import { openai } from "june/sdk/openai";
import { Agent, Step } from "june";

export default function Summaries({ files }: { files: string[] }) {
  return (
    <Agent model={openai("gpt-5-mini")}>
      {files.map((file) => (
        <Step key={file} id={`summary-${file}`} prompt={`Summarize ${file}`} />
      ))}
    </Agent>
  );
}
```

Each file gets its own id, derived from the file, so each summary is its own step.

## Choosing ids

- Derive them from data: an issue number, a file path, a topic. Not from an array index, which names whatever item is at that position, and not from `Date.now()` or a random value, which never matches a snapshot.
- Keep them stable across versions of the workflow unless you want the step to run again.
- Prefix them with what the step does, so `triage-12` and `summary-12` don't collide.

An index looks harmless until the list changes:

```tsx
import { Step } from "june";

declare const files: string[];

// Bad: after a file is added at the front, every `summary-${i}` names a
// different file, so none of them matches its snapshot entry and every
// summary runs again.
files.map((file, i) => (
  <Step key={i} id={`summary-${i}`} prompt={`Summarize ${file}`} />
));

// Good: the id follows the file wherever it moves in the list, so only the
// new file's summary runs.
files.map((file) => (
  <Step key={file} id={`summary-${file}`} prompt={`Summarize ${file}`} />
));
```

The cache key keeps an index id from reusing the wrong file's result, but it can't make the index useful: the work is identified by the file, so the id should be too.

React's `key` and June's `id` have different jobs. `key` tells React which element is which among siblings, so it can keep their state across renders. `id` names the step's work for the whole run and across runs: it decides sharing, snapshot reuse, and what `steps` is keyed by. Derive both from the same data, but they're not interchangeable: a `key` isn't seen by June, and an `id` isn't seen by React.

## The rules

| When two steps have                          | June                                                                                     |
| -------------------------------------------- | ---------------------------------------------------------------------------------------- |
| different ids                                | runs both, even if they're otherwise identical                                           |
| the same id and the same cache key           | runs one, and every place it's rendered gets the same result                             |
| the same id and different cache keys         | fails the render: `june: step <id> was rendered with two different definitions`          |

The same applies across runs through a [snapshot](https://june.dev/docs/workflows/resume.md): a snapshot entry is reused only by a step with its id and its cache key. Anything else runs again.

- **Ids are global to the run.** They aren't scoped to the component that renders them, so a reusable component should derive its ids from its props. `<Step>`, [`useStep`](https://june.dev/docs/workflows/use-step.md), `june/copy`'s `<Write>` and `june/train`'s components share one set of ids.
- **A render that fails on a duplicate id** fails the run, unless a [`<Catch>`](https://june.dev/docs/workflows/recovery.md) above it handles the error like any other.
- **A step that failed** can render again with a new definition after a `<Catch>` retry; the failed attempt doesn't hold its id.

## What a `<Step>`'s cache key covers

June derives a `<Step>`'s cache key from what it asks the model:

- the `prompt`, string or messages
- the model's provider and model id, such as `openai.responses` and `gpt-5-mini`
- the agent's other settings that are plain data: `instructions`, `temperature`, `providerOptions`, its `id`, and so on
- each tool's name, description, input schema and title
- the kind of `output` and its schema

It doesn't cover:

- functions, which June can't compare: a tool's `execute`, `stopWhen`, `prepareStep`, callbacks such as `onStepFinish`
- settings that change how a call is made, not what it asks: `maxRetries`, `timeout`, `telemetry` and `experimental_telemetry`
- anything about the model besides its provider and id, such as a provider's base URL or API key
- a tool's `needsApproval`
- the step's `stream` prop and its `children`

Inside settings, object keys are compared in sorted order, `undefined` is skipped, a `Date` is compared by its ISO string, and other class instances by their `toJSON()`, or by their class name when they have none.

So a step whose prompt, instructions or output schema changed runs again instead of reusing a snapshot, but one whose tool implementation or stop condition changed doesn't. To make it run again, change its id (`summary-v2`) or drop its entry from the snapshot.

A step with `output` or `tools` suspends once per run, the first time it renders, while the AI SDK resolves their schemas into its key.

## A `useStep`'s cache key

June can't see what a `useStep` function does, so its cache key is exactly the `cacheKey` option, `""` when you don't pass one. Change it when the function changes, so a snapshot from an earlier version isn't reused. [Custom steps](https://june.dev/docs/workflows/use-step.md#cache-keys) has examples.

A `<Step>`'s cache keys look like `step:1b0e5c3f9a27d4`. Treat them as opaque: compare them, don't parse them.

## Next

- [Custom steps with useStep](https://june.dev/docs/workflows/use-step.md): run any async work as a step.
- [Snapshots and resume](https://june.dev/docs/workflows/resume.md): how ids and cache keys decide what a resumed run reuses.
