Skip to main content
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.
state.ts

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:
state-diff.json
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.
state.ts
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.

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:
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:
environment.ts
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.