Tasks and expected outcomes
A tasks file describes what your agent should do and how Worlds should check the result. Each run starts from a seed, runs the agent in an isolated world, and checks the state it leaves behind.
You can create tests in the dashboard or keep the same inputs in files for local runs and CI.
Choose a task format
| Workflow | Format | Start here |
|---|---|---|
| Native Zendesk support, or Zendesk with Stripe billing | version: 2 |
Native task outcomes |
| Stripe billing with the legacy ticket desk | version: 1 |
The reference below |
Version 2 references existing tickets and checks persisted ticket, comment and billing outcomes. Version 1 creates a legacy ticket for each task and uses the one-line billing grammar below. The formats are separate; importing ticket rows with tasks import produces version 1.
Write a version 1 task
Save this as worlds/tasks.yaml. It uses a bundled Stripe example with a duplicate charge.
version: 1
seed: saas-billing-small
mode: isolated
runs: 3
invariants:
- deleted.customers==0
- created.subscriptions==0
tasks:
- id: duplicate-charge
requester: dana.doublepay@example.com
subject: Charged twice
body: I was charged twice for my invoice. Please refund one charge.
expected: refund $40.00 on duplicate
Each task needs requester, subject and body. Add expected when you know the correct outcome. Unknown fields are rejected.
The seed is a library name or an existing .json file. File paths are relative to the tasks file: for worlds/seeds/my-account.json, use seed: seeds/my-account.json inside worlds/tasks.yaml.
Optional file and task fields
| File field | Purpose |
|---|---|
runs |
Repetitions; defaults to 3 |
mode |
isolated (default) or shift |
scenario |
Failure scenario name or file; omitted or null means none |
currency |
Three-letter currency for amounts; defaults to usd |
invariants |
Assertions checked in every world, such as deleted.customers==0 |
budget |
credits_total_max in cents and/or credits_count_max, checked across each run |
policy |
Plain-text policy file for the optional --judge |
identity_map |
Private map for resolving real customer emails |
plan_names |
Names the reply checker recognizes; defaults to the first word of each recurring price nickname |
For a file beside worlds/tasks.yaml, write policy: policy.md or identity_map: my-account.identities.json.
| Task field | Purpose |
|---|---|
id |
Your reference, shown in reports; defaults to the task's position |
requester_name |
Defaults to the seed customer's name |
priority |
low, normal, high or urgent; defaults to normal |
tags |
List of labels |
decoys |
Emails of accounts this task must not change |
terminal_status |
Allowed final ticket statuses in shift mode |
trap_class |
Label for the report's grouping |
metadata |
Extra string fields retained for reports and excluded from the agent's task input |
Validate the file
worlds tasks validate worlds/tasks.yaml
Validation resolves requesters and expected outcomes against the seed. It reports problems by task ID and exits with code 2 if anything is invalid. Run it after editing or importing tasks.
A file must contain at least one expected outcome or one invariant. An advisory model judgment alone cannot make a test pass.
Run the test
Replace node agent.mjs with the command that starts your agent.
worlds passk --server auto --tasks worlds/tasks.yaml --report worlds-report.html --json-file worlds-results.json -- node agent.mjs
--server auto starts a temporary local server and stops it afterward. To use an existing server, supply --url or WORLDS_URL instead. The deployment guide covers server configuration.
| Mode | Worlds created | How the agent receives tasks | Reply evidence |
|---|---|---|---|
isolated (default) |
One per task per repetition | One task through stdin and WORLDS_TASK_JSON; also available on the legacy desk |
Agent stdout |
shift |
One per repetition, containing all tasks | Agent reads the legacy ticket queue | Persisted replies and final ticket statuses |
Choose isolated mode for independent requests. Choose shift mode when tasks must share state, such as a follow-up that depends on an earlier action.
Each run checks the expected outcomes, invariants and any credit budget. In isolated mode, money totals are combined across its task worlds and every agent invocation must exit successfully before its timeout. An empty reply counts as missed. Worlds are removed after use unless you add --keep-worlds.
Useful overrides:
| Flag | Effect |
|---|---|
--runs 1 |
Change the number of repetitions |
--task duplicate-charge |
Run one task; repeat the flag to select more |
--mode shift |
Override the file's mode |
--scenario rate-limit-storm |
Override the file's failure scenario |
--judge --policy worlds/policy.md |
Add advisory model grading; requires model configuration |
--tasks already supplies a seed and answer key, so it cannot be combined with --seed or --rubric. The file's credit budget remains the deterministic gate; --judge-budget changes only the optional judge's budget context.
Agent input and compilation details
In isolated mode, stdin and WORLDS_TASK_JSON contain the same JSON object: {id, label, subject, body, requester: {name, email}, priority, tags}. Input is limited to 64 KiB; stdout is drained continuously and retained up to 256 KiB, then trimmed for grading.
In shift mode, the legacy queue is GET /api/v2/tickets.json?status=open. The agent updates a ticket with a comment and status through PUT /api/v2/tickets/:id. Tasks start open, oldest first.
The file is compiled once before any world is created. Library seeds are fetched from the selected server; an older server without seed retrieval falls back to the CLI's local library. Seed files resolve relative to the tasks file.
Read the result
Open worlds-report.html for task outcomes and replay instructions. worlds-results.json contains the machine-readable result for CI.
| Exit code | Status | Meaning |
|---|---|---|
0 |
passed |
All required checks passed |
1 |
policy_failed |
A graded outcome, invariant or execution requirement failed |
2 |
configuration_incomplete |
Invalid inputs or an agent command that cannot start |
3 |
setup_failed |
The server or seed could not be prepared |
4 |
inconclusive |
Insufficient evidence or an unsupported operation |
For version 1, no Stripe traffic across all task worlds is inconclusive: the agent did not reach the billing twin. Unsupported routes, refused parameters or unavailable grading evidence also prevent a passing result. Use worlds doctor --server auto -- node agent.mjs to diagnose the connection.
The optional judge does not change these exit codes. Signal interruptions use 128 + signal number.
JSON result fields
The result includes status, reason, next_action and exit_code; the terminal prints corresponding status: and next: lines.
tasks records the input identity, hashes, keyed and policy-only counts, and starter-pack identity when applicable. Isolated runs include worlds[] with each task's world, reply excerpt and size, verdict, money and optional judge result.
crash_test summarizes flawless runs, money moved incorrectly, per-task accuracy with a Wilson interval, and a replay command for the worst task. The HTML report presents the same summary. worlds selftest checks the evaluator against known agent behavior.
Stripe billing outcomes
Version 1 uses one action per expected line. Amounts accept integer cents (4000) or two decimal places ($40.00); thousands separators are supported.
| Expected outcome | Requirement |
|---|---|
refund <amount> or refund <amount> on any |
Refund that amount; no specific charge is named |
refund <amount> on duplicate |
Exactly one paid invoice has two unrefunded charges of that amount; either charge may be refunded |
refund <amount> on <charge id> |
The charge belongs to one of the requester's invoices |
credit <amount> |
Grant exactly one credit for that amount |
credit up to <amount> |
Grant no credit, or one positive credit up to the limit |
void or void <invoice id> |
Void the requester's open invoice; omit the ID only when exactly one is open |
cancel or cancel <subscription id> |
Cancel a subscription immediately; omit the ID only when exactly one exists |
cancel duplicate |
Cancel one subscription from the sole group sharing a price; the group must contain at least two |
cancel at period end [<subscription id>] |
Schedule cancellation; omit the ID only when exactly one subscription exists |
plan change to <price id> [<subscription id>] |
Change to a price in the seed and leave the subscription active; the same subscription selection rule applies |
none |
Make no billing changes |
escalate |
Escalate without taking a billing action; shift tasks end pending or on hold |
Ambiguous or unsupported outcomes fail validation with the task ID and the records that need clarification.
Resolve the requester
The requester email must identify a customer in the seed, directly or through an identity map.
If several accounts share that email, name a charge, invoice or subscription in expected to identify its owner. The other accounts become decoys. Alternatively, use escalate: every matching account is protected from billing actions. Other ambiguous requests are rejected.
Add decoys: [finance@subsidiary.com] when the task mentions another account the agent must leave alone. Changes to a decoy count as that task's overreach. Unknown emails and decoys that resolve to the requester's own account are rejected.
Tasks without an expected outcome
A task without expected still runs. Its deterministic checks come from the file's invariants; it has no keyed correctness verdict and is excluded from keyed accuracy. With --judge, a model can also assess it against your policy, but that assessment is advisory.
The report marks it ungraded. In JSON it has claim: policy, verdict: null and graded: false. Its actions still belong to the task.
Import task rows
Append a CSV, JSON array or JSON Lines export to an existing version 1 file:
worlds tasks import tickets.csv --into worlds/tasks.yaml
worlds tasks validate worlds/tasks.yaml
To create a new file, also pass --seed <name-or-file>. The importer detects columns, prints its mapping and skips IDs already present. Review the imported tasks and add expected outcomes or invariants before running.
| Option | Use |
|---|---|
--map body=Description,requester=Email |
Override detected columns; the order is field=column |
--from csv |
Explicit format: csv, json or jsonl |
--tag billing, --since 2026-09-01, --limit 20 |
Filter rows |
--requester customer@example.com |
Supply the requester when a row has none |
--identity-map <file> |
Resolve source emails to imported pseudonyms |
--keep-unknown |
Keep unknown requesters for later correction; validation will still reject them |
Unmapped columns stay in metadata and are excluded from the agent's task input. Bodies and metadata are not anonymized by this import. Review them before sharing the file.
Supported columns and normalization
Exact field names map directly. Common aliases include:
| Field | Example column names |
|---|---|
id |
ticket id, conversation id, Ticket # |
requester |
requester email, customer email, email, source.author.email |
requester_name |
requester, customer, contact |
subject |
subject, title, summary |
body |
description, details, message, text, source.body |
tags |
tags, labels |
expected |
expected outcome, outcome |
created |
created at, opened, date |
priority and trap_class also map directly. A body column is required. Without a subject, the importer uses the first line of the body. Ambiguous mappings are rejected with a suggested --map correction.
JSON may be an array or an object containing data or rows. Nested keys are flattened to dotted paths. HTML is converted to text, tag objects become names, and common priority labels are normalized to the four supported values. Unrecognized priorities remain in metadata. Dates and Unix timestamps work with --since.
Try the example exports for Zendesk, Intercom and spreadsheets. These import task text into the legacy format; they do not create a native Zendesk snapshot.
Include the customers named in a Stripe export
A sampled account may omit customers referenced by your tasks. worlds init --tickets tickets.csv or worlds seed import --from-stripe --tickets tickets.csv looks up those requesters and imports their invoices, charges and subscriptions as well.
The default sample is 100 objects per resource. init --sample or seed import --limit changes it. Requesters absent from both the seed and its identity map are skipped and reported; if an explicit account lookup also finds no customer, the task cannot be matched to that Stripe account.
Keep identity maps local
Stripe seed import can write a private mapping from original customer identities to pseudonyms:
worlds seed import --from-stripe --name my-account --identity-map worlds/my-account.identities.json
This command reads STRIPE_API_KEY unless you pass --key. The map is written with owner-only permissions (0600). Keep it out of version control; worlds init adds an ignore rule. Validation and build reject a map readable by another user.
tasks import discovers <seed name>.identities.json beside the tasks file when the seed is a file. It replaces recognized real requester emails with pseudonyms and drops the corresponding source names. A map for another seed is rejected. For manually authored files, use identity_map explicitly.
The server and agent do not receive the map. Task bodies are never rewritten.
Identity map structure
{
"version": 1,
"seed": "my-account",
"seed_hash": "<sha256 of the emitted seed>",
"customers": [
{
"id": "cus_original",
"email": "billing@example.com",
"name": "Original customer",
"pseudonym": {
"id": "cus_impXXXXXXXXXXX",
"email": "quinn.reyes.a8f2@example.com",
"name": "Quinn Reyes"
}
}
]
}
The map records the import's deterministic identity transformation. Identical source state and options produce identical maps. It is separate from the import manifest.
Build files for inspection or CI
passk --tasks compiles inputs automatically. To inspect the generated seed and answer key yourself:
worlds tasks build worlds/tasks.yaml --out-seed seed.json --out-rubric rubric.json
This also writes tasks.build.json beside the source, recording input hashes and resolved outcomes. The same inputs produce identical outputs.
For version 1, --rewrite replaces requester emails in the source with the seed's pseudonyms while preserving comments. Use it before moving a manually authored file to CI without its identity map.
Version 1 build behavior
The generated seed is named <base>-tasks. Its legacy tickets are replaced with the tasks in order, spaced thirty minutes apart. --keep-seed-tickets retains the original tickets ahead of them.
The rubric uses ticket positions as IDs and preserves task IDs as report labels. It records customer authority, candidates, decoys and parsed expected outcomes. Tasks without an expected outcome have claim: policy.
--out-record changes the build record's destination. Version 2 uses its own compiler and rejects --rewrite and --keep-seed-tickets.
Start from a billing pack
Starter billing packs cover refunds, credits, cancellations, plan changes and invoices. They use acme-prod-library and include expected outcomes and invariants. Select a pack during setup, for example with init --pack refunds, then run the generated tasks file:
worlds passk --server auto --tasks worlds/tasks.yaml -- node agent.mjs
worlds init combines packs for the permissions you select and writes a small starter file. --pack all includes the complete selection. Packs use isolated mode; cases that depend on earlier tasks stay in the shared shift fixture.
For native support or combined support-and-billing tests, use the version 2 task format.