# Running workflows

`run()` and `start()` both execute a workflow from code. Pick by whether your code has to act while the workflow runs.

- **`run()`** when you only need the outcome. It renders the workflow and resolves with every step's result, the run's usage and the final trace.
- **`start()`** when your code has to act while the workflow runs: answer [tool approvals](https://june.dev/docs/workflows/approvals.md), abort it, take a [snapshot](https://june.dev/docs/workflows/resume.md), or react to its status. It hands you a live handle to the run.

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

declare const workflow: ReactNode;
declare const signal: AbortSignal;

const { steps, tree, usage } = await run(workflow, {
  signal, // abort the run and its in-flight steps
  timeout: 120_000, // reject with a TimeoutError after two minutes
  concurrency: 8, // the most steps executing at once (the default)
  onUpdate: (tree) => console.log(tree), // every committed trace tree
});
```

From the terminal, `june run <file>` does this for a file's default export; [the CLI reference](https://june.dev/docs/cli.md#june-run) has its options.

## `run()` or `start()`

Use `run()` when nothing needs to happen while the workflow runs. It can't answer [tool approvals](https://june.dev/docs/workflows/approvals.md), so a tool that asks for one makes it reject with `june: <tool> needs approval, which run() can't give. Use start() to approve or deny tool calls.`

Use `start()` for approvals, for aborting from your own code, and for snapshots. It returns a handle:

| Member                | Is                                                                                                  |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| `done`                | a promise that resolves or rejects like `run()`                                                     |
| `status`              | `running`, `awaitingApproval`, `settled`, `failed` or `aborted`                                     |
| `requests`            | the tool calls waiting for an answer                                                                |
| `approve(id)`         | approves a request                                                                                  |
| `deny(id, reason?)`   | denies a request; the model is told it was denied, with the reason                                  |
| `abort(reason?)`      | aborts the run                                                                                      |
| `snapshot()`          | every finished step's result and cache key, as plain JSON                                           |
| `tree()`              | a copy of the [trace](https://june.dev/docs/workflows/traces.md) as last committed; once the run ends, the tree it ended with     |
| `on(status, fn)`      | calls `fn` with the handle each time the run enters `status`; returns a function that unsubscribes  |

`on("awaitingApproval", fn)` also calls `fn` whenever the pending requests change while the run is already waiting: when more arrive, or when one is answered or withdrawn and others remain. `on` doesn't replay a status the run reached before you subscribed, so subscribe right after `start()`.

`settled`, `failed` and `aborted` are final. A run is `failed` when an error rejected it and `aborted` when its signal, its timeout or `abort()` did.

`start()` throws, and `run()` rejects, before rendering anything when `concurrency` or `snapshot` is invalid.

## Options

`run()` and `start()` take the same options:

| Option        |                                                                                                     |
| ------------- | --------------------------------------------------------------------------------------------------- |
| `onUpdate`    | called with every committed [trace](https://june.dev/docs/workflows/traces.md) tree: the whole tree each time, not a progress event |
| `signal`      | aborts the run when it aborts                                                                       |
| `timeout`     | aborts the run with a `TimeoutError` after this many milliseconds                                   |
| `concurrency` | the most steps executing at once: 8 by default, or `Infinity`; see [Concurrency](https://june.dev/docs/workflows/parallelism.md#concurrency) |
| `snapshot`    | an earlier run's `snapshot()`, to [resume](https://june.dev/docs/workflows/resume.md) from                                         |

## When a run ends

A run settles once React has committed everything it can render and no step is in flight. It resolves with:

- `steps`: each finished step's result, keyed by id, including steps [resumed](https://june.dev/docs/workflows/resume.md) from a snapshot and steps a `<Catch>` fallback has since replaced
- `tree`: the final [trace](https://june.dev/docs/workflows/traces.md)
- `usage`: the tokens its steps reported, summed, or `undefined` when no step reported any. A step a `<Catch>` aborted or retried drops what it had reported, and a model call that throws reports nothing, so a run with failures can use more than this

It rejects with the first error no [`<Catch>`](https://june.dev/docs/workflows/recovery.md) handles, exactly as it was thrown: a step's error, or an error thrown while rendering. Steps still running are aborted first.

A failed or aborted run has no result, but `handle.tree()` still says what it reached: the tree as it was when the run ended, with each step's latest state. The step that failed shows as `failed` with its `error`, the steps that finished before it as `done`, and the steps it cut off as `running`. It holds what React had committed, so a step whose node never rendered before the run ended isn't in it:

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

declare const workflow: ReactNode;
declare function save(record: object): Promise<void>;

const handle = start(workflow);

try {
  await handle.done;
} catch (error) {
  await save({ error, tree: handle.tree(), snapshot: handle.snapshot() });
  throw error;
}
```

A timer or promise that isn't a step doesn't keep a run open. Waiting belongs in a [`useStep`](https://june.dev/docs/workflows/use-step.md#waiting).

## Cancellation and timeouts

A run aborts when its `signal` aborts, when its `timeout` passes, or when you call `handle.abort()`. Then:

- `done` rejects with the abort reason: the signal's reason, a `TimeoutError` (`The operation timed out.`) for a timeout, or the reason you passed to `abort()`.
- Every in-flight step's signal aborts. A `<Step>` passes it to the AI SDK, which stops its model call; a `useStep` gets it as `fn`'s first argument. Work that ignores the signal keeps going, but its result is thrown away.
- Queued steps never start, and pending approval requests are withdrawn.

Steps can hold the rejection briefly to clean up: a `<FineTune>` cancels its training job, and the run waits for that cancel to be sent, for up to 30 seconds. See [Training in workflows](https://june.dev/docs/training/workflows.md#cancellation).

Every step's signal also aborts once the run ends for any reason, so nothing a step started outlives it.

`june run` aborts on Ctrl-C (and exits at once on a second Ctrl-C), and takes a timeout with `--timeout <ms>`.

## Resources

A resource is a live value a run owns, such as a client with a connection to close. `useResource(key, create)` suspends until `create` returns `{ value, dispose }`, then returns `value`:

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

async function connect(signal: AbortSignal) {
  return { query: async (sql: string) => sql, close: async () => {} };
}

function Report() {
  const db = useResource("db", async (signal) => {
    const client = await connect(signal);
    return { value: client, dispose: () => client.close() };
  });
  return <div>{db.query.name}</div>;
}
```

- Components that use the same key share one value, and concurrent callers share one creation. The first `create` for a key defines it.
- The run stays open while a resource is being created, and nothing is created until a component asks.
- Before `done` settles, whether the run succeeded, failed, aborted or timed out, June disposes every resource once, in reverse order of creation, waiting up to 30 seconds. A disposer that fails doesn't stop the rest.
- If the run failed or aborted, `done` rejects with that error and a cleanup error is dropped. If the run succeeded, a cleanup error rejects `done` (an `AggregateError` when several failed).
- A resource whose creation finishes after the run began closing is disposed at once. Its `create` gets a signal that aborts when the run closes.
- Resources are never in a [snapshot](https://june.dev/docs/workflows/resume.md). A resumed run creates fresh ones for the work it runs.

## Where a run lives

A run lives in one process, in memory. There's no server, queue or database behind it: if the process exits, the run is gone, along with any step that hadn't finished. June isn't a durable orchestrator.

Two things reach past one process. A [snapshot](https://june.dev/docs/workflows/resume.md) lets a later run reuse the steps that finished. A [Job](https://june.dev/docs/training/jobs.md) represents work a backend owns, such as a training job, that a later process can find again.

## Next

- [Recovery](https://june.dev/docs/workflows/recovery.md): handle failures inside the workflow.
- [Approvals](https://june.dev/docs/workflows/approvals.md): pause for a person with `start()`.
