> ## Documentation Index
> Fetch the complete documentation index at: https://docs.burn0.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Rollout Lifecycle

> What happens between silo run and a score

A rollout is the same sequence every time. Knowing it makes failures legible — most confusing results come from misunderstanding one of these steps.

<Steps>
  <Step title="Build a fresh world">
    `createState()` is called. It must return a newly created object, normally via `structuredClone`, because Node caches imported JSON modules for the life of the process.
  </Step>

  <Step title="Snapshot it">
    A copy is kept as the initial state. This is what the verifier later compares against, and what it derives expected answers from.
  </Step>

  <Step title="Bind the tools">
    `bindTools(state)` produces the callable tools, closed over the live world.
  </Step>

  <Step title="Load the agent">
    The module at `--agent` is imported. It must default-export a function, or the run fails immediately.
  </Step>

  <Step title="Hand over control">
    The agent is called once with the task instruction, the tool schemas, `callTool`, and an `AbortSignal`. Everything it does happens inside this call.
  </Step>

  <Step title="Take the output">
    Whatever it returns as `output` is captured. A string is used as-is; anything else is JSON-stringified.
  </Step>

  <Step title="Diff the world">
    The final state is compared against the snapshot to produce `state-diff.json`.
  </Step>

  <Step title="Grade">
    The task's verifier receives the final world, the initial world, and the agent's output, and returns checks.
  </Step>

  <Step title="Write artifacts">
    The trace, result, diff and run metadata are written to `.silo/runs/<runId>/`.
  </Step>
</Steps>

## Isolation

The agent holds no reference to the world. It receives tool *schemas* — name, description, input shape — not the tools themselves, and certainly not state.

Two consequences worth internalizing:

**What no tool exposes, the agent cannot know.** If a task requires knowing which users are inactive and no tool reports that, the task is unsolvable regardless of how capable the model is. Missing tools look like agent failures.

**Tool output is cloned on the way out.** A tool returning a slice of state hands the agent a copy, so nothing the agent does with that value can reach back into the world. The only way to change state is to call a tool that changes it.

## A tool call, step by step

Each `callTool` goes through the same path:

1. **Abort check.** If the run has already exceeded a limit, the call does not execute.
2. **Lookup.** An unknown name returns `tool_not_found` as a value.
3. **Validation.** Arguments are checked against `inputSchema`. A hallucinated property is rejected before `run` executes, with a message naming the real parameters.
4. **Execution.** `run(state, input)` operates on the live world.
5. **Cloning.** The return value is cloned.
6. **Recording.** A `tool_call` and a `tool_result` are written to the trace, sharing a `callId`, with the call's `durationMs`.

Failures are returned, never thrown to the agent. A rejected call is an observation the agent can act on, not the end of the rollout — which is what makes self-correction measurable.

## Reading a trace

Because the sequence is fixed, a trace tells you exactly where things went wrong:

```
run_start        task, config, every tool name offered
tool_call        callId 1  list_opportunities  { ownerId: "USR-004", status: "open" }
tool_result      callId 1  2 results
tool_call        callId 2  reassign_opportunity { opportunityId: "OPP-005", ... }
tool_result      callId 2  updated
agent_output     "Moved 2 open opportunities (OPP-005, OPP-006)..."
run_end          completed, 3 calls, 0 errors
verifier_result  passed, reward 1
```

| Symptom                                         | Likely cause                                               |
| ----------------------------------------------- | ---------------------------------------------------------- |
| No `tool_call` at all                           | The agent never reached its model, or never decided to act |
| Repeated identical calls                        | A loop with no exit condition                              |
| `tool_result` with `isError` repeatedly         | The model is not reading the error messages                |
| `agent_output` present, empty `state-diff.json` | The agent described work it never did                      |

That last one is the failure Silo exists to catch: a confident answer with nothing behind it.

## Timing

`run.json` records `startedAt`, `finishedAt` and `durationMs`, and each `tool_result` carries its own `durationMs`. Because environments are in-memory simulations, tool calls are typically sub-millisecond — so almost all of a real run's wall time is the model thinking, which makes the split useful when tuning.
