> ## 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.

# State

> The typed world, and the helpers that read it

`state.ts` declares the shape of your world and the pure helpers that read it. No data lives here, and nothing here builds a world — that is [`environment.ts`](/silo/environments/data).

```typescript state.ts theme={null}
export type State = {
  now: string;
  users: Record<string, User>;
  accounts: Record<string, Account>;
  opportunities: Record<string, Opportunity>;
  stages: Record<string, PipelineStage>;
  auditLog: AuditEvent[];
};
```

## Keep the name `State`

Every scaffolded tool and verifier imports the state type by that name, and `silo tool add` detects it when generating a stub. Renaming it to something domain-specific means editing every generated file for no benefit — ERP's `ErpState` was renamed to `State` for exactly this reason.

Grow the type in place. The name is the contract; the contents are yours.

## Collections keyed by id

Store entities as `Record<string, T>`, not arrays. Tools look rows up constantly, and `diffState` reports changes per collection, so a keyed map is what makes `state-diff.json` readable:

```json state-diff.json theme={null}
{
  "collections": {
    "opportunities": { "added": [], "removed": [], "changed": ["OPP-005", "OPP-006"] }
  }
}
```

Append-only logs are the exception — an audit trail is naturally an array. Silo diffs arrays whose rows carry a string `id`, so entries written to one still show up.

## Derived values: at load, or on read?

This is the distinction that decides whether your grading stays correct.

**Derive at load** when every tool that mutates the inputs also recomputes the result. ERP does this with money: `update_vendor_invoice_line` changes a quantity and immediately recalculates `lineTotal`, `subtotal`, `taxAmount` and `totalAmount`. The stored value is never stale because nothing can change an input without the tool fixing it up.

**Expose a helper** when the value depends on something the agent changes freely. A CRM opportunity's weighted value depends on its stage, and the agent moves stages — a value baked in at load would be wrong one tool call later.

```typescript state.ts theme={null}
/** Win probability, read from the opportunity's stage. */
export function probabilityOf(state: State, opportunity: Opportunity): number {
  if (opportunity.status === "won") return 100;
  if (opportunity.status === "lost") return 0;

  return stageOf(state, opportunity)?.probability ?? 0;
}

/**
 * Amount weighted by stage probability.
 *
 * A helper rather than a stored field: both the amount and the stage change
 * during a rollout, and a stored copy would be wrong immediately afterwards.
 */
export function weightedAmount(state: State, opportunity: Opportunity): Money {
  return money(
    (opportunity.amount.amount * probabilityOf(state, opportunity)) / 100,
    opportunity.amount.currency,
  );
}
```

<Warning>
  When in doubt, use a helper. A stale derived field fails silently — the number looks plausible, the run passes or fails for the wrong reason, and nothing points at the cause.
</Warning>

## Domain helpers earn their place twice

Helpers in `state.ts` are used by tools *and* by verifiers. That is deliberate and it is what keeps grading honest:

```typescript theme={null}
export function isStale(state: State, opportunity: Opportunity, threshold: number): boolean {
  return opportunity.status === "open" && daysInStage(state, opportunity) > threshold;
}
```

The `stale_opportunities` tool uses it to answer the agent. `VER-005` uses it to work out what the right answer was. Neither hardcodes a list of stale deals, so editing a date in `data/` changes both sides together.

Write helpers as **pure functions of state**. A helper that reaches outside the world it is given — a real clock, a network call, a module-level cache — breaks reproducibility.

## Simulated time

If your domain has any notion of overdue, stale, or expiring, put the clock in state:

```typescript theme={null}
export type State = {
  now: string;
  // ...
};
```

```typescript environment.ts theme={null}
/**
 * Fixed rather than `new Date()` so that "stale", "past due" and every forecast
 * figure are reproducible. A real clock would make grading depend on when the
 * test ran.
 */
export const SIMULATION_NOW = "2026-03-16";
```

Expose it to the agent through a tool — CRM has `get_current_date` — because the agent cannot read state directly and would otherwise be reasoning against the real calendar while your verifiers reason against the simulated one.

## What does not belong here

* **Data.** Facts live in `data/*.json`.
* **World construction.** Assembly and cloning happen in `environment.ts`.
* **Mutations.** Helpers read; tools write.
* **Anything impure.** No `Date.now()`, no randomness without a seeded source, no I/O.
