# June: the complete docs
> A declarative runtime for AI workflows. Compose agents and async work with React on top of the AI SDK, run them from the CLI, and inspect the rendered execution tree.
53 documents, complete and in reading order. Each starts after a header between lines of `=`, with its title, section, page URL and Markdown URL. For one page at a time, start at https://june.dev/llms.txt.
================================================================================
title: June Docs
section: Start
url: https://june.dev/docs
markdown: https://june.dev/docs.md
================================================================================
# Overview
June is a declarative runtime for AI workflows. React is the workflow runtime. Components express dependencies, JavaScript expresses control flow, and the rendered tree *is* the execution trace.
June sits on top of the AI SDK rather than replacing it. The AI SDK owns models, tools, structured output and the agent loop. June owns how that work composes, runs, recovers, pauses, resumes and is observed.
```tsx
{(summary) => (
)}
```
`summary` runs first. Its result is the argument to its children, so `risks` and `tests` can't start until it finishes. They're inside ``, so then they start together. There's no separate graph to define: the tree you wrote is the workflow, and the tree React renders is the run's trace.
## What June adds
| The AI SDK | June adds |
| ------------------------------------------ | ----------------------------------------------------------- |
| models and providers, through its packages | composition: dependencies, branches and parallel work |
| tools, and the agent loop inside each step | execution: concurrency, recovery, tool approvals and resume |
| auth, retries and errors for each provider | observation: the trace of what ran, and the tokens it used |
June passes your AI SDK model straight through. There's no June provider layer to configure.
## When to use it
Use June when an agent task is more than one call and the shape of the work matters:
- steps depend on each other's results, like a summary that its risks and tests build on;
- independent work fans out, like triaging every issue in a list;
- a failure should take an alternate path, or a tool call should wait for a person to approve it;
- a piece of the workflow is worth reusing as its own component;
- you need a trace of what ran, while it runs and after.
Use the AI SDK on its own when one call or one agent loop does the job. June is a small runtime on purpose, and it leaves durable execution, queues, schedules and hosting to your app: [What June doesn't do](https://june.dev/docs/workflows.md#what-june-doesnt-do) has the full list.
## The modules
Start with `june`, the runtime and the CLI. The rest are optional subpaths of the same package, for problems you may have later:
- [`june/chat`](https://june.dev/docs/chat.md): when a model or a workflow should be a persistent conversation, with feedback on its replies.
- [`june/copy`](https://june.dev/docs/copy.md): when drafts should sound like a particular person. It builds a voice from their real writing and learns from their edits.
- [`june/train`](https://june.dev/docs/training.md): when you need evidence that a change is better. It builds datasets, runs evals, compares candidates, and orchestrates fine-tunes through a trainer.
## Start here
1. [Installation](https://june.dev/docs/installation.md): Bun, the packages, and a provider key.
2. [Quickstart](https://june.dev/docs/quickstart.md): write a workflow and run it with `june run`.
3. [React as the workflow](https://june.dev/docs/workflows.md): the components and how a run works.
Then reach for [Chat](https://june.dev/docs/chat.md), [Copy and voice](https://june.dev/docs/copy.md) or [Evals and training](https://june.dev/docs/training.md) when you need them. The [CLI](https://june.dev/docs/cli.md) and [Package surfaces](https://june.dev/docs/reference/packages.md) are reference.
================================================================================
title: Installation
section: Start
url: https://june.dev/docs/installation
markdown: https://june.dev/docs/installation.md
================================================================================
# Installation
June runs on [Bun](https://bun.sh) 1.2 or later. Install `june` with React, the AI SDK, and whichever AI SDK provider you already use:
```sh
bun add june react ai @ai-sdk/openai
```
Import the provider's models from June: `june/sdk/openai`, `june/sdk/anthropic`, `june/sdk/google` or `june/sdk/xai`. Each one is a thin wrapper over the provider package you installed, and needs only that one. Run one with Bun without its package and June's error names the package to add; a bundler reports the missing package when it builds.
If you already use the AI SDK, keep the same provider package and environment variable. Models from `@ai-sdk/*` directly work too, and that's where provider-specific options live.
## Set a provider key
Your provider package reads its own API key from the environment. For `@ai-sdk/openai`, that's:
```sh
export OPENAI_API_KEY=sk-...
```
Other providers read their own, such as `ANTHROPIC_API_KEY` for `@ai-sdk/anthropic` and `GOOGLE_GENERATIVE_AI_API_KEY` for `@ai-sdk/google`. Bun also loads a `.env` file in the directory you run from.
## Check it
```sh
june doctor
```
`june doctor` checks your Bun version and which provider keys are set, without calling a model. You're ready when Bun and your provider's key both show `✓`:
```
✓ bun 1.4.3
· ANTHROPIC_API_KEY is not set (anthropic)
✓ OPENAI_API_KEY is set (openai)
…
```
If `june` isn't on your `PATH`, run it as `bunx june doctor`. Give it a workflow file and it checks only the keys that file's providers need, and fails if one is missing:
```sh
june doctor review.tsx
```
```
✓ bun 1.4.3
✗ OPENAI_API_KEY is not set (openai)
```
A key that's set but wrong only shows up when you run.
## Requirements
| Package | Version | |
| ------------------- | ---------- | ----------------------------------------------------- |
| Bun | 1.2+ | the runtime; the `june` CLI requires it |
| `june` | 0.1 | the runtime and the CLI |
| `react` | 19.3+ | a peer dependency: workflows are React components |
| `ai` | 7.0.137+ | a peer dependency: the AI SDK runs every model call |
| `@ai-sdk/openai` | 4.0+ | an optional peer dependency, for `june/sdk/openai` |
| `@ai-sdk/anthropic` | 4.0+ | an optional peer dependency, for `june/sdk/anthropic` |
| `@ai-sdk/google` | 4.0+ | an optional peer dependency, for `june/sdk/google` |
| `@ai-sdk/xai` | 5.0+ | an optional peer dependency, for `june/sdk/xai` |
| Other providers | for `ai` 7 | any AI SDK provider package, imported directly |
June is ESM only. Write workflows as `.tsx` files: Bun runs TypeScript and JSX as they are, with no build step.
## Next
Write and run your first workflow in the [Quickstart](https://june.dev/docs/quickstart.md). The [CLI reference](https://june.dev/docs/cli.md) covers every command.
================================================================================
title: Quickstart
section: Start
url: https://june.dev/docs/quickstart
markdown: https://june.dev/docs/quickstart.md
================================================================================
# Quickstart
In five minutes you'll write a component, run it from the terminal, and see how nesting becomes a dependency and `` becomes independent work. Start after [Installation](https://june.dev/docs/installation.md).
## Check your setup
With `OPENAI_API_KEY` set, run:
```sh
june doctor
```
```text
✓ bun 1.3.0
· ANTHROPIC_API_KEY is not set (anthropic)
…
✓ OPENAI_API_KEY is set (openai)
· XAI_API_KEY is not set (xai)
```
If `OPENAI_API_KEY` shows `not set`, set it before going on.
## Write a workflow
A workflow is a React component that a file default exports. Save this as `brief.tsx`:
```tsx
import { openai } from "june/sdk/openai";
import { Agent, Parallel, Step } from "june";
export default function Brief({ topic }: { topic: string }) {
return (
{(outline) => (
)}
);
}
```
Read it as a tree. `outline` must finish before its children can render, because they're a function of its result. `brief` and `questions` are independent inside ``, so they start together. There's no separate graph to define.
- `` sets the model and instructions for every step inside it. The model is an AI SDK model, passed straight through.
- Each `` is one call to the agent, named by its `id`: a single model call here, or a whole tool loop when the agent has tools.
- `topic` is an ordinary component prop. A workflow's props are its input.
## Run it
```sh
june run brief.tsx --input '{"topic":"React Suspense"}'
```
`june run` prints progress to stderr as steps start (`▸`) and finish (`✓`):
```
▸ outline
✓ outline
▸ brief
▸ questions
✓ questions
✓ brief
```
Then it prints each step's result to stdout, under its id, in the order the steps finished:
```
── outline
…
── questions
…
── brief
…
```
`outline` ran first, then `brief` and `questions` ran at the same time. Which of those two finishes first depends on the model.
## Use the output
The plain text is for people. To hand the run to a script, add `--json`:
```sh
june run brief.tsx --input '{"topic":"React Suspense"}' --json > brief.result.json
```
stdout is then one JSON object: `steps` (each step's result, by id), `usage` (the tokens the run used), and `tree` (the run's trace). Progress still goes to stderr, so stdout stays clean to parse or pipe into the next program.
## Pass input from a file
`--input` takes the workflow's props as a JSON object. For anything longer, put them in a file and pass `--input-file`:
```json
{ "topic": "React Suspense" }
```
```sh
june run brief.tsx --input-file brief.json
```
June doesn't echo the input, even in errors, and refuses anything that isn't a JSON object. Input your workflow puts in a prompt or a result still shows up in its output.
## If it doesn't run
- **`OpenAI API key is missing`**: the step fails with a `✗` and `june run` exits 1. Set `OPENAI_API_KEY`, or put it in a `.env` file where you run `june`.
- **`june: command not found`**: run it as `bunx june`.
- **A model error from the provider**: `openai(...)` takes OpenAI model ids. For another provider, install its package and use its model function.
[Installation](https://june.dev/docs/installation.md) and the [CLI reference](https://june.dev/docs/cli.md) cover the rest.
## Next
Read [React as the workflow](https://june.dev/docs/workflows.md) next: branches, fan-out, recovery, approvals and resuming, with the same components. The [CLI](https://june.dev/docs/cli.md) is the reference for every command and option.
================================================================================
title: CLI
section: Start
url: https://june.dev/docs/cli
markdown: https://june.dev/docs/cli.md
================================================================================
# CLI
The `june` command runs the workflow or chat a file default exports, and `june doctor` checks your setup. Bun imports `.tsx` and `.ts` files directly, so there's no build step, and a file's models, providers and stores are whatever its code builds: there are no provider or model flags.
| Need | Command |
| --------------------------------- | --------------------------------------------- |
| Run a workflow | [`june run review.tsx`](#june-run) |
| Open an interactive conversation | [`june chat chat.ts`](#june-chat) |
| Ask once, or script a chat | [`june ask chat.ts "…"`](#june-ask) |
| Check Bun and provider keys | [`june doctor`](#june-doctor) |
The CLI comes with the package as `node_modules/.bin/june` and requires [Bun](https://bun.sh) 1.2 or later. Run it as `bunx june` if it isn't on your `PATH`; [Install](https://june.dev/docs/workflows.md#install) covers setup.
## `june run`
```sh
june run review.tsx
```
Renders the component the file default exports and runs it to completion. The [Quickstart](https://june.dev/docs/workflows.md#quickstart) walks through a first run, and [Running workflows](https://june.dev/docs/workflows/running.md) covers what a run does. With options:
```sh
june run triage.tsx --input '{"repo":"johno/june","issues":[12,15]}'
june run triage.tsx --input-file ./triage.json
june run review.tsx --json > review.json
june run review.tsx --timeout 60000
```
- **The file** must default export a workflow component. `june: cannot find ` if it doesn't exist, and `june: must default export a workflow component` if its default export isn't a function.
- **Progress** goes to stderr for each ``, `` and `` as it starts (`▸ id`), calls a tool (`↳ tool {input}`), streams text, and finishes (`✓ id`) or fails (`✗ id: message`). A `useStep` or `` prints no progress, only its result.
- **Results** go to stdout once every step has finished: each step's result under `── id`, in the order the steps finished. A failed run prints no results, only the error on stderr.
- **Tools.** The CLI doesn't add tools to a workflow: it runs the tools the workflow's own `` declares, including [``](https://june.dev/docs/workflows/tools.md).
- **Approvals.** When a step asks to run a tool that needs approval, `june run` asks on stderr, `? Allow {input} (step )? [y/N]`, and reads the answer from stdin. `y` or `yes` approves; anything else, or the end of stdin, denies.
### Options
- **`--input `** passes a JSON object to the component as its props, as `run()` would. It must be an object: `june: --input must be a JSON object of the workflow's props` for anything else, and `june: --input is not valid JSON` when it doesn't parse. June never prints the input itself, even in errors; input your workflow puts in a prompt or a result shows up wherever those do, including `--json`'s `tree`.
- **`--input-file `** reads the props from a JSON file, relative to the current directory. Use one of `--input` and `--input-file`, not both. Without either, the props are `{}`.
- **`--json`** prints the whole result to stdout as one JSON object once the run succeeds (a failed run prints nothing to stdout): `steps` (each result, by step id), `usage` (the tokens the run used, when steps reported any) and `tree` (the run's trace). Progress stays on stderr, so stdout parses. Use it for anything that reads the output: the plain text format may change, the JSON follows the [compatibility](https://june.dev/docs/reference/compatibility.md) rules.
- **`--timeout `** aborts the run after that many milliseconds, a positive number. A run that times out fails with `june: The operation timed out`.
### Exit status
- `0` when every step finished.
- `1` when a step failed, the run timed out, or the file or options were wrong. The message is on stderr, after `june: `.
- `130` when you pressed Ctrl-C. The first Ctrl-C aborts the run; a second exits at once.
## `june chat`
```sh
june chat chat.ts
```
An interactive chat in your terminal with the chat a file default exports, such as one made with `createChat()` from `june/chat`. [Chat](https://june.dev/docs/chat.md) covers what a chat is and how sessions work.
```ts
import { openai } from "june/sdk/openai";
import { createChat, fileStore } from "june/chat";
export default createChat({
model: openai("gpt-5-mini"),
instructions: "You are helping build June.",
store: fileStore(".june/chat"),
});
```
```text
$ june chat chat.ts
session 4f9c…e1
/new starts a session, /session prints its id, /feedback up|down rates the last reply, /quit exits
> What is June?
June is a declarative runtime for AI workflows…
> Say that in five words.
Declarative AI workflows, in React.
>
```
A chat in the terminal's normal scrollback, not a full-screen app. Replies stream to stdout; the `> ` prompt, the session id and status go to stderr. A blank line separates turns.
- **`--session `** reopens a saved session: `june chat chat.ts --session `. It needs a persistent store, such as `fileStore(".june/chat")`. With the default `memoryStore()`, a session ends with its process, and `--session` fails with `no chat session`.
- **Commands.** `/new` starts a new session, `/session` prints the current id, `/feedback up` or `/feedback down` rates the last reply, and `/quit` or Ctrl-D exits.
- **Ctrl-C** while a reply streams cancels that turn (it's recorded as canceled and left out of later context) and returns to the prompt. Ctrl-C at the prompt exits with status 130.
- **Errors.** A failed turn prints its error and returns to the prompt.
- **Approvals.** When a workflow chat's step asks to run a tool that needs approval, it asks on stderr, like `june run`: `y` approves, anything else denies.
[Sessions and stores](https://june.dev/docs/chat/sessions.md) covers what a session stores and how it reopens.
## `june ask`
```sh
june ask chat.ts "Explain this package"
```
One turn with the same chat, for scripts. The prompt is the remaining arguments, or stdin when there are none:
```sh
june ask chat.ts --session "And its tests?"
git diff | june ask chat.ts
june ask chat.ts --json "Explain this package" > reply.json
june ask chat.ts -- "-h means help?"
```
The reply streams to stdout, followed by a newline, and `session ` goes to stderr, so `--session` can continue the conversation later. With `--json`, the id is the object's `sessionId` instead.
- **`--session `** continues a saved session.
- **`--json`** prints exactly one JSON object to stdout once the turn completes, and nothing else.
- **`--`** ends the options, so a prompt can start with `-`.
- **Approvals** are asked on stderr and answered on stdin, so a prompt piped on stdin leaves nothing to answer with and the call is denied.
```json
{
"sessionId": "4f9c…e1",
"turnId": "b2d1…07",
"text": "June is a declarative runtime for AI workflows…",
"metadata": {
"responder": { "kind": "model" },
"model": { "provider": "openai.responses", "modelId": "gpt-5-mini" },
"usage": { "inputTokens": 24, "outputTokens": 31, "totalTokens": 55 },
"finishReason": "stop"
}
}
```
`metadata` is the turn's [provenance](https://june.dev/docs/chat/responders.md#provenance). A failed turn exits 1 with the error on stderr, and Ctrl-C cancels it and exits 130.
## `june doctor`
```sh
june doctor
june doctor review.tsx
```
Checks that Bun is 1.2 or later and which provider API keys are set, without calling a model.
- **With a file**, it finds the providers the file and its local imports use: each `june/sdk/` or `@ai-sdk/` import, and a gateway model string such as `"anthropic/claude-sonnet-5-5"`. It reports the key each needs and exits 1 if one is missing.
- **Without a file**, it lists every key it knows and exits 1 only if none is set.
| Provider | Key |
| ------------------- | ------------------------------ |
| `@ai-sdk/openai` | `OPENAI_API_KEY` |
| `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
| `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
| `@ai-sdk/groq` | `GROQ_API_KEY` |
| `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
| `@ai-sdk/xai` | `XAI_API_KEY` |
| AI Gateway | `AI_GATEWAY_API_KEY` |
It doesn't check that a key is valid, or that the model a file names exists. Those show up when you run.
## Help and version
- **`--help`**, `-h`, `june help` or `june` alone prints the usage, options and examples.
- **`--version`**, `-v` prints the installed version.
- An unknown command exits 1 with `june: unknown command , run 'june --help' for usage`.
================================================================================
title: React as the workflow
section: Workflows
url: https://june.dev/docs/workflows
markdown: https://june.dev/docs/workflows.md
================================================================================
# june
**Declarative AI workflows.**
June is React for AI workflows: a runtime where the component tree is the program. Nesting creates dependencies, JavaScript controls what renders, independent work can run in parallel, and the rendered tree is the execution trace.
The AI SDK still owns models, tools, structured output and the agent loop. June adds the workflow around them.
## Why June
One model call, or one agent looping over its tools, is already solved: that's the AI SDK. The hard part starts when work depends on other work, branches on a result, fans out over a list, fails, pauses for a person, resumes after a restart, or needs to be observed while it runs.
June is that layer, without another graph DSL to learn:
- **Dependencies** are nesting: a step's children run once it finishes, with its result.
- **Branches** are JavaScript: only what renders runs.
- **Fan-out** is `.map()` inside ``, bounded by the run's concurrency limit.
- **Failures** go to a `` boundary, which can fall back or retry.
- **People** answer the AI SDK's tool approvals while the rest of the run keeps going.
- **Restarts** reuse the steps that finished, from a snapshot.
- **Observation** is the trace: the same tree React renders, as JSON.
React isn't the UI for the workflow. **React is the workflow runtime.**
## Install
```sh
bun add june react ai @ai-sdk/openai
```
june runs on [Bun](https://bun.sh) 1.2 or later, and the `june` CLI requires Bun. `react` (19.3+) and `ai` (7.0.137+) are peer dependencies, and models come from any AI SDK provider. The package is ESM only.
`june/sdk/openai`, `june/sdk/anthropic`, `june/sdk/google` and `june/sdk/xai` give you each provider's models, so `import { openai } from "june/sdk/openai"` is all a workflow needs. The provider packages are optional: install the ones you use, like `@ai-sdk/openai` above. Run one you haven't installed with Bun and June's error names the package to add; a bundler reports the missing package when it builds. For anything else a provider offers, such as `openai.chat()` or provider options, import its `@ai-sdk/*` package directly. See [Package surfaces](https://june.dev/docs/reference/packages.md#junesdk).
## Quickstart
```tsx
// review.tsx
import { openai } from "june/sdk/openai";
import { Agent, Parallel, Step } from "june";
const diff = await Bun.$`git diff`.text();
export default function Review() {
return (
{(summary) => (
)}
);
}
```
```sh
june doctor review.tsx # checks Bun and OPENAI_API_KEY
june run review.tsx # prints progress, then each step's result
june run review.tsx --json > review.json
```
`summary` runs first. Once it finishes, `risks` and `tests` start together with its result.
### Workflow input
A workflow is a component, so its input is its props. `june run` passes a JSON object as props with `--input`, or reads one from a file with `--input-file` (relative to the current directory):
```tsx
// triage.tsx
export default function Triage({
repo,
issues,
}: {
repo: string;
issues: number[];
}) {
return (
{issues.map((n) => (
))}
);
}
```
```sh
june run triage.tsx --input '{"repo":"johno/june","issues":[12,15]}'
june run triage.tsx --input-file ./triage.json
```
The input must be a JSON object, and June never prints it. From code, render the component with props: `run()`.
## Your program is the workflow
There's no separate graph to define: React and JavaScript already express the control flow. Nesting is a dependency: a step's children render, and run, once it finishes, with its result. The Quickstart's `summary → risks, tests` is that rule and nothing more.
A conditional is a branch, and only the branch that renders runs:
```tsx
{(score) =>
Number(score) < 5 ? (
) : (
)
}
```
A `.map()` is dynamic fan-out. Give each step a stable id derived from its data; the run's concurrency limit keeps a long list from starting every model call at once:
```tsx
{files.map((file) => (
))}
```
A component is a reusable subworkflow. It holds its own steps, takes props, and composes like any other component:
```tsx
function Research({ topic }: { topic: string }) {
return (
{(sources) => (
)}
);
}
;
```
Any async work can be a step with `useStep`. The component suspends until it resolves, and the result is reused like any other step's:
```tsx
function Issue({ number }: { number: number }) {
const issue = useStep(
`issue-${number}`,
(signal) =>
fetch(`https://api.github.com/repos/johno/june/issues/${number}`, {
signal,
}).then((r) => r.json()),
{ cacheKey: "issue-v1" },
);
return ;
}
```
## The API
| Export | |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `` | provides the AI SDK's `ToolLoopAgent` settings to the steps inside it. [Agent and Step](https://june.dev/docs/workflows/agent-and-step.md) |
| `` | runs the closest agent on a prompt; a function child receives the result. [Agent and Step](https://june.dev/docs/workflows/agent-and-step.md#step) |
| `` | marks its children as independent. [Parallelism and concurrency](https://june.dev/docs/workflows/parallelism.md) |
| `` | gives the steps in the closest agent an AI SDK tool. [Capabilities](https://june.dev/docs/workflows/capabilities.md) |
| `` | an error boundary for steps, with a fallback and `retry`. [Recovery](https://june.dev/docs/workflows/recovery.md) |
| `useStep(id, fn, { cacheKey })` | runs any async function as a step. [Custom steps](https://june.dev/docs/workflows/use-step.md) |
| `useAgent()` | the closest `` as an AI SDK `ToolLoopAgent`. [Agent and Step](https://june.dev/docs/workflows/agent-and-step.md#useagent) |
| `run(element, options)` | renders a workflow and resolves with its results. [Running workflows](https://june.dev/docs/workflows/running.md) |
| `start(element, options)` | the same, with a handle for approvals, aborting and snapshots. [Running workflows](https://june.dev/docs/workflows/running.md#run-or-start) |
## Running workflows from code
`run()` resolves with every step's result, the run's token usage, and the final trace tree once nothing is in flight. It rejects with the first error no `` handles.
```ts
const { steps, usage, tree } = await run(, {
signal, // abort in-flight model calls
timeout: 120_000, // rejects with a TimeoutError
concurrency: 8, // the most steps executing at once (the default)
onUpdate: (tree) => render(tree), // every commit: step status, tool calls, partial text
});
```
Tools with the AI SDK's `needsApproval` pause their step until a person answers, so use `start()`, which returns a handle to approve or deny them. `june run` asks on stdin. See [Approvals](https://june.dev/docs/workflows/approvals.md).
```tsx
const handle = start();
handle.on("awaitingApproval", ({ requests }) => {
for (const request of requests) {
if (confirm(`Run ${request.toolName}?`)) handle.approve(request.id);
else handle.deny(request.id, "Not now");
}
});
await handle.done;
```
`snapshot()` returns every finished step's result and cache key as JSON. Pass it back to skip those steps after a crash or restart. Resume is completed-step reuse, not durable execution: a step that hadn't finished runs again from the start. See [Snapshots and resume](https://june.dev/docs/workflows/resume.md).
```ts
await save(handle.snapshot());
// later
await run(, { snapshot: await load() });
```
## Learn the runtime
The Workflows docs go from the first step to the exact semantics:
1. [Agent and Step](https://june.dev/docs/workflows/agent-and-step.md): models, tools, prompts, structured output and streaming.
2. [Capabilities](https://june.dev/docs/workflows/capabilities.md): declaring tools in JSX, scope, and reusable bundles.
3. [Composition and data flow](https://june.dev/docs/workflows/composition.md): results, input, branches and reusable components.
4. [Parallelism and concurrency](https://june.dev/docs/workflows/parallelism.md): ``, fan-out, fan-in and the concurrency limit.
5. [Step identity](https://june.dev/docs/workflows/step-identity.md): ids, cache keys, and when a step runs.
6. [Custom steps](https://june.dev/docs/workflows/use-step.md): `useStep` for any async work, and reporting its model calls.
7. [Running workflows](https://june.dev/docs/workflows/running.md): `run()`, `start()`, cancellation and timeouts.
8. [Recovery](https://june.dev/docs/workflows/recovery.md): ``, failure and retry.
9. [Approvals](https://june.dev/docs/workflows/approvals.md): pausing for a person.
10. [Snapshots and resume](https://june.dev/docs/workflows/resume.md): reusing finished steps after a restart.
11. [Traces and usage](https://june.dev/docs/workflows/traces.md): the trace tree, its fields, and token usage.
Runnable versions of their examples, with tests, are in [src/examples/workflows](https://github.com/johno/june/tree/main/packages/june/src/examples/workflows).
## Beyond workflows
The same package has optional subpaths for problems you may have later. Start with the runtime; add these when you need them.
- **[Chat](https://june.dev/docs/chat.md)** (`june/chat`): a workflow runs once and finishes; a chat persists. It turns a model or one of your workflows into a conversation, with feedback on its replies. `june chat chat.ts` opens one in your terminal, and `june ask` scripts it.
- **[Tools](https://june.dev/docs/workflows/tools.md)** (`june/tools`): tools June ships, as plain AI SDK tools. `` lets an agent run shell commands, after you approve each one.
- **[Copy and voice](https://june.dev/docs/copy.md)** (`june/copy`): builds a voice from a person's real writing and learns from their edits.
- **[Evals and training](https://june.dev/docs/training.md)** (`june/train`, `june/trainers`): turns feedback into evidence. Build datasets, run evals, compare candidates, and fine-tune through a trainer only when evaluation says you need to.
A chat is a file that default exports one:
```ts
// chat.ts
import { openai } from "june/sdk/openai";
import { createChat, fileStore } from "june/chat";
export default createChat({
model: openai("gpt-5-mini"),
instructions: "You are helping build June.",
store: fileStore(".june/chat"),
});
```
```sh
june chat chat.ts # an interactive chat in your terminal
june ask chat.ts "Explain this package" # one reply on stdout, for scripts
```
See also: the chat guides on [sessions and stores](https://june.dev/docs/chat/sessions.md), [turns](https://june.dev/docs/chat/turns.md), [responders](https://june.dev/docs/chat/responders.md), [context](https://june.dev/docs/chat/context.md) and [feedback](https://june.dev/docs/chat/feedback.md); [learning from chat](https://june.dev/docs/chat/learning.md), which turns sessions into evidence; and [local](https://june.dev/docs/training/local.md) and [managed](https://june.dev/docs/training/managed.md) training with `trl()` and `vertex()`.
## What June doesn't do
June is a small runtime on purpose. Reach for something else when you need:
- **Durable execution.** Resume reuses finished steps but doesn't checkpoint in-flight work, so a crash mid-step repeats that step. If work must survive the process exactly once, use a durable workflow engine, or a [Job](https://june.dev/docs/training/jobs.md) for work a backend owns.
- **Storage, queues, schedules and hosting.** A run lives in one process, and a snapshot is JSON you save yourself. Your app decides where runs happen and when.
- **A visual builder.** Workflows are components, not a graph you draw.
- **Memory, retrieval and MCP abstractions.** Use the AI SDK's tools, or your own.
- **Rate limiting.** The concurrency limit counts steps, not tokens or requests; use the agent's `maxRetries` or your provider's limits.
## Testing
Use `scriptedModel` from `june/test`, so tests don't need API keys or a network. It answers each call with whatever your function returns: text, an object as JSON for a step with an `output`, or tool calls from `callTools()`. The agent runs the tools and calls the model again with their results.
```tsx
import { Agent, Step, Tool, run } from "june";
import { callTools, scriptedModel } from "june/test";
const model = scriptedModel((call) =>
call.prompt.at(-1)?.role === "user"
? callTools({ toolName: "readFile", input: { path: "a.ts" } })
: "Looks good",
);
const { steps } = await run(
,
);
expect(steps.review).toBe("Looks good");
```
A scripted model is the AI SDK's `MockLanguageModelV4` from `ai/test`, so `model.doGenerateCalls` records every call June made, and `doStreamCalls` those of a step with `stream`. A reply that throws fails the call. For anything it can't script, such as reasoning or provider metadata, build a `MockLanguageModelV4` yourself.
================================================================================
title: Agent and Step
section: Workflows
url: https://june.dev/docs/workflows/agent-and-step
markdown: https://june.dev/docs/workflows/agent-and-step.md
================================================================================
# Agent and Step
`` provides the model and tool configuration around its children. `` is one unit of model-backed work: one prompt, one agent loop, one result.
An `` 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 (
);
}
```
```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`.
## ``
`` 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 (
);
}
```
[``](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 (
);
}
```
- Tools are plain AI SDK `tool()`s. June doesn't wrap them. `` declares one in the ``'s children; see [Tools](#tools).
- A step uses the closest `` above it. Agents don't merge: a nested `` replaces every setting for the steps inside it, so repeat the ones you want to keep.
- A `` outside any `` fails the run with `june: steps must be rendered inside an `.
- `maxRetries` retries a single failed model call inside the AI SDK, before the step fails. Retrying a whole step is a [``](https://june.dev/docs/workflows/recovery.md).
## Tools
An agent's tools are declared in its children, next to the steps that use them. `` gives every step in that `` 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 ``, 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 (
);
}
```
- **A tool's name is unique within an ``.** Two tools with one name, from ``s or from the `tools` prop, fail the run with an error naming both, instead of one replacing the other.
- **A nested `` has its own tools.** It doesn't see the outer agent's, the same as the rest of its settings.
- **`` goes directly in an ``'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.
## ``
`` 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 (
{({ score }) =>
score < 5 &&
}
);
}
```
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 ``'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 `` 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.
================================================================================
title: Tools
section: Workflows
url: https://june.dev/docs/workflows/tools
markdown: https://june.dev/docs/workflows/tools.md
================================================================================
# Tools
June ships tools so you don't have to write them. Each one is a plain AI SDK `tool()` underneath, declared in JSX like any other [capability](https://june.dev/docs/workflows/capabilities.md): `` in an ``'s children gives its steps the tool. Approvals, traces and cancellation work the way they do for your own tools. There is no June tool type, registry or protocol.
The tools themselves are exported from `june/tools` as functions that return an AI SDK tool, for when you hold a `ToolSet` or build your own capability. Today there is one: `bashTool()`.
## ``
```tsx
import { openai } from "june/sdk/openai";
import { Agent, Bash, Step } from "june";
export default function InspectPackage() {
return (
);
}
```
```sh
june run inspect-package.tsx
```
```
▸ inspect
? Allow bash {"command":"ls"} (step inspect)? [y/N]
```
`` adds a tool named `bash` to the agent it's in, built by `bashTool()` with the props you give it. The same tool works as a plain AI SDK tool, which is how you use it without the component:
```tsx
import { openai } from "june/sdk/openai";
import { Agent, Step } from "june";
import { bashTool } from "june/tools";
const bash = bashTool();
export default function InspectPackage() {
return (
);
}
```
Declare it one way or the other, not both: a second `bash` in one agent is an error.
The model's input is only `{ command: string }`. The command runs with `bash -lc`, so pipes, redirects and `&&` work as they do in a terminal. The tool returns a structured result, and the model reads it as the tool's output:
```json
{ "exitCode": 0, "stdout": "package.json\n", "stderr": "" }
```
A non-zero `exitCode` is a result, not an error: a failing test run or a `grep` with no match is something the model should see. The tool throws only when the command can't run (the `cwd` isn't a directory, Bash can't start) or the call is aborted.
### Options
```tsx
import { Agent, Bash } from "june";
import { openai } from "june/sdk/openai";
export const agent = (
);
```
`bashTool()` takes the same options as an object.
| Option / prop | Default | |
| ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cwd` | `process.cwd()` | where the command runs; it must be an existing directory |
| `env` | none | overlaid on the June process's environment. A value of `undefined` removes that variable from the command's environment |
| `maxOutputBytes` | 64 KiB | the most bytes kept of each of stdout and stderr, as a positive integer. Output beyond it is read and dropped, and `stdoutTruncated` or `stderrTruncated` is `true` |
| `needsApproval` | `true` | whether each call pauses for an [approval](https://june.dev/docs/workflows/approvals.md) |
Output is cut on a UTF-8 character boundary, keeping the beginning. Stdin is closed, so a command that waits for input gets end of file. Each call is a fresh shell: nothing carries over between calls. When the shell exits, anything it started that still holds stdout or stderr open is stopped after a short grace period, so a background process can't hang the call or outlive it.
### Cancellation
The AI SDK's abort signal reaches the command. If the run is aborted by Ctrl-C, `--timeout`, `handle.abort()` or a canceled chat turn, June kills the command's whole process group, including anything it started, and the call rejects with an `AbortError`. A call whose signal is already aborted never starts.
### Where it runs
**`` is not a sandbox.** Read this before you give it to a model.
- The command runs on the local machine, with the permissions of the June process. It can read, write and delete whatever you can, and reach whatever network you can.
- It inherits the June process's environment, including API keys and tokens, except for the keys you override or remove in `env`.
- The command and its output are tool input and output: they appear in [traces](https://june.dev/docs/workflows/traces.md) and `june run --json`, and they go to the model on the next call.
- `needsApproval: true` is a gate for an operator, not a security boundary. It lets a person look at a command before it runs, but June doesn't read the command, and doesn't claim to know whether one is safe. A command that looks harmless can still do harm, and an approval doesn't make it safe.
- Use `needsApproval: false` only when you trust the model and the task, or have constrained the environment outside June, such as in a container, a disposable machine or a restricted account.
If you need to limit what the model can do, give it narrower tools of your own instead of a shell.
### Hosts
The CLI doesn't add Bash to a workflow. A workflow has Bash because it declares `` in its own ``, and `june run`, `june chat` and `june ask` only host it, asking for approval the way they do for any tool.
For a chat, declare it in the workflow a [workflow-backed responder](https://june.dev/docs/chat/responders.md) renders. `modelResponder()`, which `createChat({ model })` uses, makes one model call and has no tools.
```tsx
import { openai } from "june/sdk/openai";
import { agentResponder, createChat } from "june/chat";
import { Agent, Bash, Step } from "june";
export default createChat({
responder: agentResponder({
id: "shell",
render: ({ messages, input }) => (
),
reply: (result) => String(result.steps.reply),
}),
});
```
`june ask chat.tsx "Why do the tests fail?"` asks for approval on stderr and reads the answer from stdin. With the prompt piped on stdin there is nothing left to answer with, so every call is denied; Bash is not an exception.
### Not included
Interactive terminals, stdin, persistent shells, sandboxing, command allowlists and timeouts. Each one is a larger decision than this tool makes for you. Abort the run to stop a command.
================================================================================
title: Capabilities
section: Workflows
url: https://june.dev/docs/workflows/capabilities
markdown: https://june.dev/docs/workflows/capabilities.md
================================================================================
# Capabilities
A capability is something an `` can do: call a tool, today. June declares capabilities in JSX, as children of the agent they belong to, so the shape of an agent reads straight off its source:
```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",
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => Bun.file(path).text(),
});
const writeFile = tool({
description: "Write a file",
inputSchema: z.object({ path: z.string(), text: z.string() }),
execute: async ({ path, text }) => Bun.write(path, text).then(() => "ok"),
});
const search = tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `results for ${query}`,
});
const deploy = tool({
description: "Deploy to an environment",
inputSchema: z.object({ env: z.enum(["staging", "prod"]) }),
needsApproval: true,
execute: async ({ env }) => `Deployed to ${env}`,
});
const model = openai("gpt-5-mini");
export default function Release() {
return (
);
}
```
Capabilities end in ordinary AI SDK tools. The agent hands its steps one `ToolSet`, and approvals, traces and the model loop work as they do for any tool. June doesn't add a tool type or a plugin system: composition decides who sees what.
A few rules explain all of it:
1. **JSX structure defines scope.** What an agent can do is what is declared in its own children.
2. **Agents encapsulate.** A nested `` has its own capabilities and doesn't see its parent's.
3. **Capabilities cross only when named.** `` is how a tool goes into a nested agent, and a composite tool or callable agent keeps everything it uses to itself.
4. **Wrappers transform what they contain.** `` and `` apply to the declarations inside them, and nowhere else.
5. **Execution ends in ordinary AI SDK tools.** Declaring a capability doesn't change how a tool runs, is approved or is traced.
6. **The workflow stays dynamic.** Capabilities are resolved per ``, when that agent renders. A step that renders a new agent later gets that agent's capabilities then.
## Scope
**An agent's capabilities are the ones in its own children.** The agent finds them before it renders anything else, resolves them, and only then runs its steps, so a step never sees a partial set. This happens once per ``, when it appears; a step that renders a new agent later resolves that agent's capabilities then. Nothing here compiles the workflow ahead of time.
**A nested `` starts empty.** It doesn't see the outer agent's capabilities, in the same way it doesn't inherit the outer agent's other settings.
```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",
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => Bun.file(path).text(),
});
const writeFile = tool({
description: "Write a file",
inputSchema: z.object({ path: z.string(), text: z.string() }),
execute: async ({ path, text }) => Bun.write(path, text).then(() => "ok"),
});
const search = tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `results for ${query}`,
});
const deploy = tool({
description: "Deploy to an environment",
inputSchema: z.object({ env: z.enum(["staging", "prod"]) }),
needsApproval: true,
execute: async ({ env }) => `Deployed to ${env}`,
});
const model = openai("gpt-5-mini");
export default function Nested() {
return (
);
}
```
`inner` can call `search` and not `readFile`. To let it call `readFile` too, say so with [``](#inheriting-a-parents-tools).
**Capabilities go directly in the agent's children,** or in a fragment, an array or a bundle there. A capability inside a component of your own isn't visible to the agent, and rendering one there fails with an error that says so. Conditions work as usual: `{writable && }`.
**Names are unique.** Two capabilities that expose one tool name, including one from the `tools` prop, fail the run with an error that names both declarations and the bundles they came from. Nothing silently replaces anything.
## Bundles
`defineCapabilities` makes a reusable group, so a package can export what an agent needs and a workflow can add it with one line:
```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Step, Tool, defineCapabilities } from "june";
import { z } from "zod";
const readFile = tool({
description: "Read a file",
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => Bun.file(path).text(),
});
const writeFile = tool({
description: "Write a file",
inputSchema: z.object({ path: z.string(), text: z.string() }),
execute: async ({ path, text }) => Bun.write(path, text).then(() => "ok"),
});
const search = tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `results for ${query}`,
});
const deploy = tool({
description: "Deploy to an environment",
inputSchema: z.object({ env: z.enum(["staging", "prod"]) }),
needsApproval: true,
execute: async ({ env }) => `Deployed to ${env}`,
});
const model = openai("gpt-5-mini");
export const Files = defineCapabilities(
({ writable = false }: { writable?: boolean }) => (
<>
{writable && }
>
),
{ name: "Files" },
);
export default function Fix() {
return (
);
}
```
- **A bundle is transparent.** It adds what it composes to the agent it's in. It isn't a boundary and has no scope of its own.
- **Bundles nest.** A bundle can return other bundles, ``s, fragments and conditions on its props.
- **A bundle is a pure function of its props.** June runs it while the agent renders to see what it declares, so don't use hooks, state or side effects in it. Anything that does work belongs in the capability it returns.
- **A bundle contains only capabilities.** Returning a `` or a component of your own is an error. Put those in the ``'s children.
- **A bundle can't contain itself.** A cycle fails with the chain, for example ` > > `.
- **`name` is for errors.** Pass it, or give the function a name, so a collision says `` and not ``.
## Inheriting a parent's tools
A capability crosses into a nested agent only when you name it. `` in the nested agent passes down the enclosing agent's tools of those names:
```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Inherit, Step, Tool } from "june";
import { z } from "zod";
const readFile = tool({
description: "Read a file",
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 Summarize() {
return (
);
}
```
`summarize` can call `readFile` and not `deploy`.
- **Nothing is inherited unless named.** There's no "everything from the parent".
- **The parent is the agent directly around.** A grandchild can reach the child's tools, not the grandparent's, and no agent can reach a sibling's. To pass something down two levels, each level inherits it.
- **A name the parent doesn't have fails the run,** with the names it does have. So does a name listed twice, and an `` with no agent around it.
- **What crosses is the parent's own tool,** as it stands in the parent's scope, approvals included. A grant can't loosen what the parent's tool needs.
- **A granted tool is a declaration like any other,** so a nested agent can't also declare a tool of the same name, and the error says which `` brought it.
- **A grant carries the parent's tool as the parent sees it,** after any wrapper around it has applied. A tool the parent wrapped with `` or renamed with `` arrives that way.
- **Only tools cross.** A nested agent's model, instructions and conversation are its own settings, set on that ``.
## Wrappers
A wrapper applies to the capabilities declared inside it, so a policy or a naming scheme is visible in the source, right around what it governs:
```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Namespace, RequireApproval, Step, Tool } from "june";
import { z } from "zod";
const searchIssues = tool({
description: "Search GitHub issues",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `issues for ${query}`,
});
const searchDocs = tool({
description: "Search the docs",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `docs for ${query}`,
});
const closeIssue = tool({
description: "Close a GitHub issue",
inputSchema: z.object({ number: z.number() }),
execute: async ({ number }) => `closed #${number}`,
});
export default function Triage() {
return (
);
}
```
The agent's tools are `github_search`, `github_close` and `docs_search`, and only `github_close` waits for approval.
- **``** makes every tool inside it wait for approval through the AI SDK's own `needsApproval`. It can only add the requirement: a tool inside that says `needsApproval: false`, like ``, still waits, and nothing inside it can remove it.
- **``** puts `name_` in front of the name of every tool inside it, so tools from different sources can share an agent. `name` is letters, digits and hyphens, starting with a letter. A collision error still names the original tool and the namespace.
- **Wrappers nest, innermost first.** The outer wrapper has the last word on what comes out: `` gives `a_b_tool`, and a `` around anything applies to it whatever is inside.
- **A wrapper only governs what it contains,** including bundles and fragments inside it. It doesn't reach tools declared outside it, or in a nested agent.
- **A wrapper holds only capabilities,** like a bundle, and changes the tools by returning new ones. It never alters an agent that has already resolved.
- **They aren't a security boundary.** `` puts a person in front of a tool call. It doesn't decide what the tool is allowed to do.
## Composite tools
One tool can be a whole workflow. Give `` a `description`, an `input` schema and a function as its child, and each call renders that function's workflow with the model's input:
```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Step, Tool } from "june";
import { z } from "zod";
const searchWeb = tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `results for ${query}`,
});
export default function Report() {
return (
{({ question }) => (
)}
);
}
```
The outer agent can call `research` and nothing else: it can't see `search`. When it calls `research`, June runs the workflow as a run of its own and gives the model what the workflow produced.
- **The workflow's agents are the ones you write inside it,** with their own models and tools. Their scope is private by the same rule as any nested ``. To let one use a tool of the calling agent, say so with `` inside it: the calling agent is its parent.
- **Steps in the workflow need an `` of their own,** as everywhere. They don't borrow the caller's.
- **`output` picks what the model gets.** By default that's the result of the workflow's only step, or an object of every step's result when there are several. Pass `output={(result) => ...}` to choose, for example `result.steps.findings`. A string goes back as text and anything else as JSON.
- **The trace keeps the whole run.** The tool call's `output` holds `{ output, trace, usage }`: what the model got, the workflow's own trace tree, and the tokens it used. The model never sees the trace.
- **Approvals go through the calling run.** A tool inside the workflow with `needsApproval` asks whoever answers the outer run, for example `june run`'s prompt or `handle.approve()`, and its request names the step as `research:findings`. To gate entering the tool at all, wrap it in ``.
- **Cancellation and errors carry across.** Aborting the call, or the run, aborts the workflow and waits for its cleanup. A failure inside becomes a tool error the model can see: `june: the tool "research" failed: `.
## Callable agents
A nested `` is a scope for the steps inside it, not something an agent can call. `defineAgent` makes one that can be called: a reusable agent that other agents declare as a capability and use as a single tool.
```tsx
import { openai } from "june/sdk/openai";
import { tool } from "ai";
import { Agent, Step, Tool, defineAgent } from "june";
import { z } from "zod";
const searchWeb = tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => `results for ${query}`,
});
const Researcher = defineAgent({
name: "researcher",
description: "Researches a question and reports what it found",
input: z.object({ question: z.string() }),
model: openai("gpt-5-mini"),
instructions: "You research carefully and cite what you find.",
prompt: ({ question }) => `Research: ${question}`,
capabilities: () => ,
});
export default function Article() {
return (
);
}
```
The writer sees one tool, `researcher`. Calling it runs the researcher as a run of its own, with its own model, instructions and `search` tool, and the writer's model gets its answer.
`defineAgent` takes an agent's settings (`model`, `instructions`, `stopWhen` and the rest) and:
- **`name`, `description` and `input`** are what the calling model sees, as for any [composite tool](#composite-tools).
- **`prompt`** turns the input into the agent's prompt. It's built from the input alone: the caller's conversation never crosses, so the prompt has to say what the agent needs to know.
- **`capabilities`** returns the agent's private capabilities as JSX, which can be tools, bundles, wrappers, other defined agents, and `` to take named tools from the caller. Nothing else of the caller's is visible.
- **`output`** is an AI SDK output such as `Output.object({ schema })`. The caller gets the parsed object as JSON. Without it, the caller gets the answer as text.
Defined agents compose: `capabilities` can return another defined agent, which then is a single tool to this one. Everything a composite tool does applies: approvals the agent's tools need are asked through the calling run, aborting the caller aborts the agent, a failure is a tool error the caller's model sees, and the agent's trace is kept in the tool call's `output`.
## The tools prop and `bashTool()`
`tools={{ ... }}` on an `` is the AI SDK's own setting, and `bashTool()` from `june/tools` returns a plain AI SDK tool. Both keep working and are the low-level interface: use them when you already hold a `ToolSet`, or build tools outside a component. They feed the same scope as `` and ``, so they follow the same rules: one name once, nothing inherited into a nested agent, and the same trace.
## Capabilities that wait
Declarations resolve before the agent's steps start, and a capability may take time to resolve: connecting to a service and listing its tools, for example. The run stays open while it waits, a failure fails the run (or a [``](https://june.dev/docs/workflows/recovery.md) that retries it), and aborting the run aborts the wait. Several agents that declare the same thing in one run share one resolution. This is the shape a capability backed by a live resource, such as a connection to an MCP server, takes: the agent sees its tools as any other tools, and what keeps the connection alive is a separate concern from which agents can use it.
## Seeing what an agent has
An agent that declares capabilities records on its trace node where each tool came from. `capabilities` is a list of plain data:
```json
[
{ "name": "bash", "source": " in " },
{
"name": "github_search",
"source": " in "
},
{
"name": "research",
"source": "",
"composite": true
},
{
"name": "files",
"source": "",
"granted": " in the enclosing "
}
]
```
`source` is the declaration, followed by the bundles and wrappers it came through, outermost first. `granted` says what an `` took the tool from, and `composite` marks a tool that runs a workflow of its own. It never holds a tool, a function, or the props of a component, so a command's `cwd` or `env` doesn't end up in a trace. An agent that only uses the `tools` prop has nothing to explain and records no `capabilities`.
June Studio shows it in an agent's inspector under Capabilities, with what an agent inherited under Inherited. Studio reads the trace; the run works it out.
The same answers are in the error when two declarations collide, which names both with their bundles and wrappers.
================================================================================
title: Composition and data flow
section: Workflows
url: https://june.dev/docs/workflows/composition
markdown: https://june.dev/docs/workflows/composition.md
================================================================================
# Composition and data flow
**Nesting is dependency.** A step's result is passed to what it renders, so its children can't exist, and can't run, until it finishes. There's no graph to declare: the component tree is the dependency graph, and ordinary JavaScript decides what renders next.
## Passing results down
A step's function child receives its result. Anything rendered there depends on the step:
```tsx
// release-notes.tsx
import { openai } from "june/sdk/openai";
import type { LanguageModel } from "ai";
import { Agent, Step } from "june";
export default function ReleaseNotes({
commits,
model = openai("gpt-5-mini"),
}: {
commits: string[];
model?: LanguageModel;
}) {
return (
{(changes) => (
)}
);
}
```
```sh
june run release-notes.tsx --input '{"commits":["Add --json to june run","Fix a crash on empty input"]}'
```
`changes` runs first. `notes` doesn't exist until `changes` finishes; then it renders with the result in its prompt and runs. The edge `changes → notes` exists only because `notes` is rendered from `changes`' result; nothing else declares it.
- **Results flow down.** A result reaches the steps rendered inside its step's function child, through props and closures like any React value. A step can't read a sibling's result; to use several results together, see [fan-in](https://june.dev/docs/workflows/parallelism.md#fan-in).
- **Results flow out.** [`run()`](https://june.dev/docs/workflows/running.md) resolves with `steps`, every finished step's result keyed by id, and `june run --json` prints it as `steps`, next to `usage` and `tree`.
- **Nesting is the only dependency.** Two steps that aren't nested don't wait for each other. What if two children are independent? Mark them with [``](https://june.dev/docs/workflows/parallelism.md), and the run's concurrency limit decides how many execute at once.
## Branching
A branch is JavaScript. Only what renders runs:
```tsx
// pr-review.tsx
import { openai } from "june/sdk/openai";
import { Output, jsonSchema, type LanguageModel } from "ai";
import { Agent, Step } from "june";
const verdict = Output.object({
schema: jsonSchema<{ score: number; reason: string }>({
type: "object",
properties: {
score: { type: "number", description: "1 to 10" },
reason: { type: "string" },
},
required: ["score", "reason"],
additionalProperties: false,
}),
});
export default function PullRequestReview({
diff,
model = openai("gpt-5-mini"),
}: {
diff: string;
model?: LanguageModel;
}) {
return (
{({ score, reason }) =>
score < 7 ? (
) : (
)
}
);
}
```
A score below 7 renders `changes`; anything else renders `approval`. The step that isn't rendered never runs, never appears on the trace, and has no entry in `steps`.
Any JavaScript that decides what to render works: `&&`, a ternary, an early `return`, a `switch` in a helper function, or a `.map()` over a result. Loops over data are [fan-out](https://june.dev/docs/workflows/parallelism.md#fan-out).
## Reusable workflow components
A component that renders steps is a reusable subworkflow. It takes props, can expose its result through a function child of its own, and composes like any other component:
```tsx
import type { ReactNode } from "react";
import { openai } from "june/sdk/openai";
import { Agent, Step } from "june";
export function Research({
topic,
children,
}: {
topic: string;
children?: (brief: string) => ReactNode;
}) {
return (
{(sources) => (
{children}
)}
);
}
export default function Brief() {
return (
{(brief) => }
);
}
```
Step ids are global to the run, not scoped to the component that renders them. Derive them from the component's props, as `Research` does with `topic`, so two instances don't collide. Two instances with the same props render the same ids and definitions, so they share one execution; that's how [fan-in](https://june.dev/docs/workflows/parallelism.md#fan-in) works. [Step identity](https://june.dev/docs/workflows/step-identity.md) has the rules.
A component doesn't need its own ``. Its steps use the closest one where it's rendered, so the caller chooses the model.
## Input
A workflow is a component, so its input is its props. `june run` passes a JSON object as the default export's props with `--input`, or reads one from a file with `--input-file`; from code, render it with props:
```tsx
import type { ReactNode } from "react";
import { run } from "june";
declare function ReleaseNotes(props: { commits: string[] }): ReactNode;
const { steps } = await run();
```
The examples in these docs take their model as a prop too, with a default, so a test can pass a mock model without touching the workflow. See [Testing](https://june.dev/docs/workflows.md#testing).
## React, as it is
Everything on this page is ordinary React and JavaScript: props, closures, conditionals, `.map()` and components. The workflow is a real React tree, rendered by a custom renderer instead of the DOM, so what you know about React carries over. Context, `useState`, `useMemo` and `useEffect` work as usual, and effects run when their part of the tree commits.
One difference from a UI: a run ends once no step is in flight and React has nothing left to render. A timer or a promise that isn't a step doesn't keep it open. Make waiting a step with [`useStep`](https://june.dev/docs/workflows/use-step.md), as the [retry example](https://june.dev/docs/workflows/recovery.md#retry-with-a-delay) does.
## Next
- [Parallelism and concurrency](https://june.dev/docs/workflows/parallelism.md): independent work, dynamic fan-out, fan-in, and the run's concurrency limit.
- [Step identity](https://june.dev/docs/workflows/step-identity.md): why ids derived from data matter.
================================================================================
title: Parallelism and concurrency
section: Workflows
url: https://june.dev/docs/workflows/parallelism
markdown: https://june.dev/docs/workflows/parallelism.md
================================================================================
# Parallelism and concurrency
`` says "these children don't depend on each other." The run's concurrency limit says how many ready steps may execute at once. They're separate controls: one is about dependency, the other about an execution limit.
The tree already says which is which:
- **A nested child is a dependency.** It renders, and runs, after its parent finishes.
- **Siblings in `` are independent.** They're eligible to run together, as soon as whatever they're nested in finishes.
```tsx
import { openai } from "june/sdk/openai";
import { Agent, Parallel, Step } from "june";
export default function Review({ diff }: { diff: string }) {
return (
{(summary) => (
)}
);
}
```
`risks` and `tests` both depend on `summary` and not on each other, so they start together once it finishes. The [Code Review Agent](https://github.com/johno/june/blob/main/apps/code-review-agent/README.md) is this shape at full size: one plan, then specialists in parallel.
## ``
Wrap work that doesn't depend on its siblings in ``. Each child can then start, finish and render what depends on it without waiting for the others. It doesn't raise the concurrency limit; the limit still decides how many of their steps execute at a time.
### How it works
`` wraps each child in its own Suspense boundary and renders a `parallel` node on the trace (with an optional `id` prop to name it). Each child commits, to the trace and to `onUpdate`, as soon as its own work is ready, and work that depends on it can go on without waiting for its siblings.
Without ``, siblings share the closest Suspense boundary. React may still render ahead into a suspended sibling and start its steps, but nothing in a shared boundary commits until all of it is ready: the trace doesn't show it, and effects in it, such as a [retry](https://june.dev/docs/workflows/recovery.md#retry-with-a-delay), don't run. Don't rely on React rendering ahead; mark independent work with ``.
`` and `` each put their own children in a boundary, so what a step renders never holds back that step's siblings. The difference shows with components that suspend themselves: a component that calls [`useStep`](https://june.dev/docs/workflows/use-step.md), or a `` with `output` or `tools`, which suspends briefly while the AI SDK resolves its schemas.
## Fan-out
A `.map()` over data is dynamic fan-out. Give each step an id derived from its item:
```tsx
// triage.tsx
import { openai } from "june/sdk/openai";
import type { LanguageModel } from "ai";
import { Agent, Parallel, Step } from "june";
export interface Issue {
number: number;
title: string;
}
export default function Triage({
repo,
issues,
model = openai("gpt-5-mini"),
}: {
repo: string;
issues: Issue[];
model?: LanguageModel;
}) {
return (
{issues.map((issue) => (
))}
);
}
```
```sh
june run triage.tsx --input-file ./issues.json
```
- **`key` is React's; `id` is June's.** Derive both from the item, not its position. An id made from an array index names whatever item is at that position, so after the list changes a [snapshot](https://june.dev/docs/workflows/resume.md)'s entries no longer line up with the items and their steps run again. Two lists that both use ids like `item-${i}` collide and fail the run. [Step identity](https://june.dev/docs/workflows/step-identity.md) has the rules.
- **Long lists are fine.** Rendering 500 steps renders 500 nodes, but only the [concurrency limit](#concurrency) executes at once; the rest wait as `queued`.
## Concurrency
A run executes at most 8 steps at once. Set `concurrency` on [`run()` or `start()`](https://june.dev/docs/workflows/running.md) to change it, or pass `Infinity` for no limit. Anything else, such as `0` or `2.5`, fails with `june: concurrency must be a positive integer or Infinity`. `june run` always uses the default.
```tsx
import { run } from "june";
import type { ReactNode } from "react";
declare const workflow: ReactNode;
await run(workflow, { concurrency: 4 });
```
### What the limit counts
- **Steps, not model calls.** A ``'s whole tool loop holds one slot, however many model and tool calls it makes, and so does a `useStep` that makes several calls itself. `june/copy`'s `` and `june/train`'s `` and `` are steps too.
- **Executing steps, not rendered ones.** Rendering isn't throttled. A step that's ready past the limit is `queued` on the trace, and queued steps start in the order they became ready as slots free up. `startedAt` and `durationMs` start when a step first gets a slot, not when it's queued, though time it spends waiting on an approval still counts.
- **Not waiting on people.** A step waiting on a [tool approval](https://june.dev/docs/workflows/approvals.md) gives up its slot until it's answered, then queues for one again.
- **Not reused results.** A step [resumed from a snapshot](https://june.dev/docs/workflows/resume.md) takes no slot, and steps that share an id take one between them.
Each run has its own limit; two runs in one process don't share slots. The limit isn't a rate limiter: it doesn't know about tokens or a provider's rate limits. Lower it, or use the agent's `maxRetries` to retry rate-limited calls.
## Fan-in
Fan-in means: wait for a group of independent steps to settle, then render the work that needs all of their results.
A result flows only to what its step renders, so a step that needs several results has to be rendered where all of them are in scope. Nest the steps it reads, and render them once more inside `` so they start together. Steps with the same id and definition share one execution, so each runs once:
```tsx
// research.tsx
import type { ReactNode } from "react";
import { openai } from "june/sdk/openai";
import type { LanguageModel } from "ai";
import { Agent, Parallel, Step } from "june";
export function Research({
topic,
children,
}: {
topic: string;
children?: (brief: string) => ReactNode;
}) {
return (
{(sources) => (
{children}
)}
);
}
export default function Compare({
a,
b,
model = openai("gpt-5-mini"),
}: {
a: string;
b: string;
model?: LanguageModel;
}) {
return (
{(first) => (
{(second) => (
)}
)}
);
}
```
The first `` starts both topics at once. The chain below it renders the same steps again: it waits for each step in turn, all of them already running, and `comparison` runs once both briefs are in. The model is called five times, not nine. The shared steps appear on the trace once per place they're rendered, with the same state.
For a list of any length, the chain can be recursive:
```tsx
import type { ReactNode } from "react";
import { Step } from "june";
interface Issue {
number: number;
title: string;
}
function Label({
issue,
children,
}: {
issue: Issue;
children?: (label: string) => ReactNode;
}) {
return (
{children}
);
}
export function Labels({
issues,
labels = [],
children,
}: {
issues: Issue[];
labels?: string[];
children: (labels: string[]) => ReactNode;
}) {
const [first, ...rest] = issues;
if (!first) return children(labels);
return (
);
}
```
Render `