Hand-written tools say yes to everything.
A mocked payments API accepts a second full refund of the same charge. The agent learns a move that fails against the real system.
refund(ch_81) → ok
refund(ch_81) → ok real system: rejected
Open-source plan / October 2026
We plan to open-source deep simulations of business software (service desks, ledgers, payments, approvals) where every change has to pass real business rules. Drop one into any Harbor task as a side container. Your verifier reads the world's state.
Status: plan. Nothing is released yet. We want feedback before we build.
refund ch_81 $50.00
applied
refunds $50 ≤ charge $50
refund ch_81 $50.00
rejected
charge already fully refunded
post JE-204 Dr 900 / Cr 850
rejected
debits must equal credits
close INC-4411
rejected
2 child tasks still open
close INC-4411
applied
child tasks resolved first
Why
A mocked payments API accepts a second full refund of the same charge. The agent learns a move that fails against the real system.
refund(ch_81) → ok
refund(ch_81) → ok real system: rejected
Each environment writes its own tickets, invoices and approval flows, with different rules in each. The same system behaves one way in one benchmark and another way in the next.
task_a/tools.py close_ticket()
task_b/tools.py close_ticket() different rules
Canned responses leave nothing real to inspect after the run. With a world, every record and every change is there for your verifier to read.
mock: 200 OK nothing persisted
world: refunds(ch_81) = [$50]
Example shapes
Each shape models how a category of software behaves: its records, its operations, and the rules every change must pass. Shapes are modeled on the category, not cloned from one vendor's API.
incidents, child tasks, assignment groups, SLAs, routing rules
EXAMPLE TASK"INC-4411 breached its SLA. Find out why and fix the routing."
accounts, journal entries, periods, entities, intercompany
EXAMPLE TASK"The March trial balance is off by $1,240. Find it and fix it."
requests, approvers, policies, budgets, purchase orders
EXAMPLE TASK"Get this $48K hardware order approved under policy."
customers, charges, refunds, invoices, disputes
EXAMPLE TASK"Refund what is still owed on order 8812."
employees, managers, teams, time off, compensation changes
EXAMPLE TASK"Move Dana to the Berlin team, effective May 1."
accounts, contacts, opportunities, stages, owners
EXAMPLE TASK"Merge the duplicate Acme accounts without losing open deals."
Inside one shape
What one shape would contain. Syntax is illustrative and will change.
Exposed as MCP tools and as a REST API. Both go through the same rules.
list_charges(customer)
get_charge(id)
capture_charge(id)
refund_charge(id, amount)
open_dispute(id, reason)
create_invoice(customer, lines)
apply_payment(invoice, amount)
Declared once per shape. A write that breaks one is rejected and nothing changes.
rule refund_within_captured
on refund.create
require sum(charge.refunds) + amount
<= charge.captured
rule no_refund_while_disputed
on refund.create
require charge.dispute.status != "open"
invariant invoice_paid_means_covered
invoice.status == "paid"
implies sum(invoice.payments) >= invoice.total
A clear error, like a real system would return. The agent can recover and try something valid.
{
"error": "rule_violation",
"rule": "refund_within_captured",
"message": "Refund of $50.00 exceeds the
$0.00 still refundable on ch_81",
"state_changed": false
}
Read-only. Every record, plus every attempted change during the run, including rejected ones.
GET /state/charges/ch_81
{ "captured": 5000,
"refunds": [{ "id": "re_1", "amount": 5000 }],
"dispute": null }
GET /state/changes?since=run_start
[ { "op": "refund.create", "target": "ch_81",
"result": "applied" },
{ "op": "refund.create", "target": "ch_81",
"result": "rejected",
"rule": "refund_within_captured" } ]
Why the rules hold
Rules are enforced where state changes, not in a tool wrapper. Agents often have a shell. One that skips the MCP tools and calls the API directly runs into exactly the same rules.
agent ── MCP tool ───┐
agent ── REST call ──┼──▶ rules ──▶ state
agent ── curl/shell ─┘ (one engine, every path)
A world's history is generated by running months of simulated operations through the same rules, not by writing rows straight into a database. Every starting record is one the rules could have produced: no refund larger than its charge, no entry in a closed period.
seed 0042 ──▶ 90 days of operations ──▶ rules ──▶ history
(charges, refunds, (same engine
tickets, approvals…) as the agent's)
A seed fixes the whole history, so every run of a task starts from the identical state. Reset between runs, or fork a world mid-run to try two different actions from the same point.
Seeds can plant the situations agents get wrong: a partly refunded charge, a dispute opened last week, a ticket already past its SLA, an approver on leave. Each one is created through the rules, so it is a valid state.
Example worlds
A world is a set of shapes seeded with months of consistent records. Each seed produces a different history and different edge cases, so agents cannot memorize one answer. Illustrative examples:
Service desk + Payments + CRM
Refund requests tied to real charges, escalations across assignment groups, and accounts with duplicate records.
General ledger + Approvals + Payments
Pending intercompany entries, unapproved purchase orders, and a trial balance that does not tie out.
How it plugs in
The agent can only change the world through its rules. Your verifier reads the world's state and checks whatever your task needs.
Point the task at a world and a seed, write the instruction, and check the state your way. Names will change before anything ships.
my-task/
├── instruction.md
│ "INC-4411 breached its SLA. Find why and fix the routing."
├── task.toml
│ [[environment.mcp_servers]]
│ name = "service-desk"
│ url = "http://world:7000/mcp"
├── environment/docker-compose.yaml
│ world: counterworld/service-desk seed = 0042
└── tests/test.py # your verifier
inc = world.get("incidents/INC-4411")
assert inc.assignment_group == "Network Ops"
assert world.changes(exclude="INC-4411") == []
How this differs
| API mocks | Tools written per task | Real SaaS sandbox | Counterworld shapes | |
|---|---|---|---|---|
| Business rules enforced | Rarely | Whatever the author wrote | Yes | Yes, on every path |
| State to check after the run | Partial | Varies | Yes, through the vendor API | Yes, read-only state API |
| Months of consistent history | No | Hand-seeded | Empty account | Generated through the rules |
| Reset and fork | Reset | Varies | No | Both |
| Runs offline inside a task | Yes | Yes | No | Yes |
| Same behavior across tasks | Yes | No | Yes | Yes |
Not in scope: byte-for-byte clones of one vendor's API, browser UIs, and grading. Your verifier stays yours.
What would ship first
Records, operations, rules and invariants.
Months of operations run through the rules, with planted edge cases.
Read-only records and the full change log, including rejected attempts.
Side container in docker-compose, registered through [[environment.mcp_servers]].
Then: general ledger, approvals and payments, plus OpenEnv and NeMo Gym adapters. Everything above under a permissive open-source license.
What we want to know
Would environment builders use shared world shapes instead of writing tools for each task? What would stop them?
Which shapes should come first?
What would your verifier need from a world's state API?
Which rules do agents most often get away with breaking in today's environments?
Reply to the message this link came with. Short answers are fine, and "not useful" is a useful answer.