# Agent and Step

`<Agent>` provides the model and tool configuration around its children. `<Step>` is one unit of model-backed work: one prompt, one agent loop, one result.

An `<Agent>` isn't a long-running or autonomous process. It doesn't run anything on its own: it's the context its steps run in, and the steps are what execute. Every later page builds on that: a workflow is a tree of steps, and the agents around them say how each one calls a model.

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

export default function Explain() {
  return (
    <Agent
      model={openai("gpt-5-mini")}
      instructions="You explain code to new contributors."
    >
      <Step id="explain" prompt="Explain what a React reconciler does" />
    </Agent>
  );
}
```

```sh
june run explain.tsx
```

One step runs: `explain` sends its prompt, with the agent's instructions, to `gpt-5-mini`, and settles with the reply. `june run` prints `▸ explain` when the step starts and `✓ explain` when it finishes, then the result under `── explain`.

## `<Agent>`

`<Agent>` takes the AI SDK's `ToolLoopAgent` settings, `model`, `instructions`, `tools`, `stopWhen`, `maxRetries`, `providerOptions` and the rest, except `output`, which belongs to each step. It renders an `agent` node on the [trace](https://june.dev/docs/workflows/traces.md) and provides its settings to every step inside it.

```tsx
import { openai } from "june/sdk/openai";
import { stepCountIs, tool } from "ai";
import { Agent, Step, Tool } from "june";
import { z } from "zod";

const readFile = tool({
  description: "Read a file in the repository",
  inputSchema: z.object({ path: z.string() }),
  execute: async ({ path }) => Bun.file(path).text(),
});

export default function Exports() {
  return (
    <Agent model={openai("gpt-5-mini")} stopWhen={stepCountIs(10)}>
      <Tool name="readFile" value={readFile} />
      <Step id="exports" prompt="Explain what src/index.ts exports" />
    </Agent>
  );
}
```

[`<Bash />`](https://june.dev/docs/workflows/tools.md) is a capability June ships, and `june run` asks before each command it runs:

```tsx
// inspect-package.tsx
import { openai } from "june/sdk/openai";
import { stepCountIs, type LanguageModel } from "ai";
import { Agent, Bash, Step } from "june";

export default function InspectPackage({
  cwd = process.cwd(),
  model = openai("gpt-5-mini"),
}: {
  cwd?: string;
  model?: LanguageModel;
}) {
  return (
    <Agent model={model} stopWhen={stepCountIs(8)}>
      <Bash cwd={cwd} />
      <Step
        id="inspect"
        prompt="List the files in this directory, read package.json, and say what the package is and which scripts it defines."
      />
    </Agent>
  );
}
```

- Tools are plain AI SDK `tool()`s. June doesn't wrap them. `<Tool>` declares one in the `<Agent>`'s children; see [Tools](#tools).
- A step uses the closest `<Agent>` above it. Agents don't merge: a nested `<Agent>` replaces every setting for the steps inside it, so repeat the ones you want to keep.
- A `<Step>` outside any `<Agent>` fails the run with `june: steps must be rendered inside an <Agent>`.
- `maxRetries` retries a single failed model call inside the AI SDK, before the step fails. Retrying a whole step is a [`<Catch>`](https://june.dev/docs/workflows/recovery.md).

## Tools

An agent's tools are declared in its children, next to the steps that use them. `<Tool name value>` gives every step in that `<Agent>` one AI SDK tool, and `value` reaches the model loop as it is: its schema, `execute`, `needsApproval` and provider options are unchanged.

The `tools` prop is the AI SDK's own setting and keeps working. It feeds the same scope as `<Tool>`, so use it when you already hold a `ToolSet`, and mix the two freely:

```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Step, Tool } from "june";
import { z } from "zod";

const readFile = tool({
  description: "Read a file in the repository",
  inputSchema: z.object({ path: z.string() }),
  execute: async ({ path }) => Bun.file(path).text(),
});
const deploy = tool({
  description: "Deploy to an environment",
  inputSchema: z.object({ env: z.enum(["staging", "prod"]) }),
  needsApproval: true,
  execute: async ({ env }) => `Deployed to ${env}`,
});

export default function Release() {
  return (
    <Agent model={openai("gpt-5-mini")} tools={{ readFile }}>
      <Tool name="deploy" value={deploy} />
      <Step id="release" prompt="Check the changelog, then deploy to staging" />
    </Agent>
  );
}
```

- **A tool's name is unique within an `<Agent>`.** Two tools with one name, from `<Tool>`s or from the `tools` prop, fail the run with an error naming both, instead of one replacing the other.
- **A nested `<Agent>` has its own tools.** It doesn't see the outer agent's, the same as the rest of its settings.
- **`<Tool>` goes directly in an `<Agent>`'s children,** or in a fragment or array there. Inside a component of your own, the agent can't see it, and rendering it fails with an error that says so.
- **The agent resolves its tools before any of its steps start,** so a step never runs with only some of them.

## `<Step>`

`<Step>` runs the closest agent's loop on `prompt` and settles with its result.

| Prop       |                                                                                                        |
| ---------- | ------------------------------------------------------------------------------------------------------ |
| `id`       | names the step; see [Step identity](https://june.dev/docs/workflows/step-identity.md)                                                |
| `prompt`   | a string, or an array of AI SDK `ModelMessage`s                                                        |
| `output`   | structured output, such as `Output.object({ schema })`                                                 |
| `stream`   | streams text into the step's `partialText` on the trace as it's generated                              |
| `children` | rendered once the step finishes; a function child receives the result                                  |

The result is the generated text, or the parsed object when `output` is set. A function child receives it with its type, which is how data flows through a workflow: whatever the child renders depends on this step and can use its result. [Composition and data flow](https://june.dev/docs/workflows/composition.md) builds on that.

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

const scored = Output.object({
  schema: jsonSchema<{ score: number }>({
    type: "object",
    properties: { score: { type: "number" } },
    required: ["score"],
    additionalProperties: false,
  }),
});

export default function Score({ diff }: { diff: string }) {
  return (
    <Agent model={openai("gpt-5-mini")}>
      <Step
        id="score"
        prompt={`Score this diff from 1 to 10:\n${diff}`}
        output={scored}
      >
        {({ score }) =>
          score < 5 && <Step id="feedback" prompt="Explain what to improve" />
        }
      </Step>
    </Agent>
  );
}
```

OpenAI's structured outputs are strict: with `jsonSchema()`, every object needs `additionalProperties: false` and must list all of its properties in `required`.

### What a step does

In order: render, queued or running, the model and tool loop, a result, then its children render.

1. It renders a `step` node with `status: "queued"` or `"running"`, depending on whether the run has a free [concurrency slot](https://june.dev/docs/workflows/parallelism.md#concurrency).
2. It runs the agent loop: model call, tool calls, model call, until the agent's `stopWhen` is met or the model stops calling tools. After each round, its tool calls, with their inputs and outputs, are added to the step's `toolCalls`.
3. If a tool needs [approval](https://june.dev/docs/workflows/approvals.md), the loop pauses until it's answered, then continues.
4. It settles: `done` with its `result`, or `failed` with its `error`.
5. Its children render. Until then they don't exist, so nothing in them runs.

A step's whole tool loop is one step: one trace node, one result, one concurrency slot, and one entry in a [snapshot](https://june.dev/docs/workflows/resume.md). June doesn't checkpoint inside it.

### Streaming

`stream` streams the step's text into `partialText` on its trace node, so an `onUpdate` handler or `june run` can show it as it's generated. The step's result and its children still wait for the final text, and a streamed step has the same result, usage and cache key as one that isn't.

## `useAgent()`

`useAgent()` returns an AI SDK `ToolLoopAgent` built from the closest `<Agent>`'s settings. Use it inside a [`useStep`](https://june.dev/docs/workflows/use-step.md) when you need the agent's settings for a call `<Step>` doesn't make, such as one with a different prompt shape per item. It must be called during render, like any hook.

## Next

- [Composition and data flow](https://june.dev/docs/workflows/composition.md) teaches dependencies: how a result flows to the steps that need it, how to branch, and how to build reusable workflow components.
- [Step identity](https://june.dev/docs/workflows/step-identity.md) teaches reuse: what `id` means, what the cache key covers, and when a step runs again.
