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 asRecord<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
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
Domain helpers earn their place twice
Helpers instate.ts are used by tools and by verifiers. That is deliberate and it is what keeps grading honest:
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
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.
Silo