# 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 (
    <Agent model={model} instructions="You write release notes for developers.">
      <Step
        id="changes"
        prompt={`Group these commits into user-facing changes:\n${commits.join("\n")}`}
      >
        {(changes) => (
          <Step
            id="notes"
            prompt={`Write release notes from these changes:\n${changes}`}
          />
        )}
      </Step>
    </Agent>
  );
}
```

```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 [`<Parallel>`](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 (
    <Agent model={model} instructions="You are a senior code reviewer.">
      <Step
        id="score"
        prompt={`Score this diff from 1 to 10:\n${diff}`}
        output={verdict}
      >
        {({ score, reason }) =>
          score < 7 ? (
            <Step
              id="changes"
              prompt={`Ask for changes to this diff. Your concern: ${reason}\n${diff}`}
            />
          ) : (
            <Step
              id="approval"
              prompt={`Write a short approval. Your reasoning: ${reason}`}
            />
          )
        }
      </Step>
    </Agent>
  );
}
```

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 (
    <Step id={`sources-${topic}`} prompt={`List the key sources on ${topic}`}>
      {(sources) => (
        <Step
          id={`brief-${topic}`}
          prompt={`Write a one-paragraph brief on ${topic} from:\n${sources}`}
        >
          {children}
        </Step>
      )}
    </Step>
  );
}

export default function Brief() {
  return (
    <Agent model={openai("gpt-5-mini")}>
      <Research topic="Suspense">
        {(brief) => <Step id="title" prompt={`Title this brief: ${brief}`} />}
      </Research>
    </Agent>
  );
}
```

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 `<Agent>`. 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(<ReleaseNotes commits={["Add --json"]} />);
```

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.
