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.

On this page

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.