Skip to main content
An environment is a simulated world your agent can act inside: the data it operates on, the tools it may call, the objectives it is given, and the checks that decide whether it succeeded. It is ordinary TypeScript and JSON in your repository. Silo scaffolds it, loads it, and runs agents against it — but you own it, and you are expected to edit it.

The shape of a world

Five parts, each with one job:
Data defines the world. State gives that world behaviour. Tools expose controlled access. Tasks define objectives. Verifiers decide whether the objective was achieved.
That separation is the whole design. Data holds facts and nothing else. State turns those facts into a typed world with domain helpers. Tools are the only surface the agent can touch. Tasks state what to accomplish without saying how. Verifiers grade the world the rollout left behind.

Where it lives

Every environment has this shape, whether it models a CRM, an ERP, or something you invent. There is no per-domain architecture — a new domain changes the contents, never the structure.

The contract

Silo loads an environment through three exports and nothing else:
index.ts
The environment const is not required at runtime. It exists so TypeScript fails the build if the module drifts from the contract, rather than failing at run time.
createState() must return a newly created object, normally via structuredClone. Imported JSON modules are cached by Node for the life of the process — return one directly and the second rollout inherits the first one’s mutations. It presents as flaky grading, not as an obvious bug.
Tasks are not exported. They are discovered from tasks/*.json, so adding a task is adding a file.

Magic for data, never for code

Tasks and datasets are discovered from disk. Tools and verifiers are explicitly registered in their barrel files. That asymmetry is deliberate: a JSON file appearing in data/ is inert until something reads it, but a TypeScript file appearing in tools/ would otherwise become a new capability the agent can invoke. A file landing on disk must never silently widen what an agent can do.

The manifest

silo.environment.json

Type checking

silo init writes .silo/tsconfig.json covering environments/**/*.ts, and never overwrites one you already have.
This runs the real TypeScript compiler over the environment, not just a file-presence check. That distinction matters more than it sounds: type-only imports are erased before execution, so an environment can load and run perfectly while being uncompilable. “It ran” is not evidence it is correct.

Next

Data

Facts only — never a value you can derive.

State

The typed world and its domain helpers.

Tools

The only surface the agent can touch.

Tasks

Objectives, without a solution path.

Verifiers

Deterministic grading of the final world.

Validation

Catching a broken environment before a run.