# webgym: four new task kinds (car, restaurant, signup, filter) Added 2026-09-26. All kinds follow the existing conventions: `(kind, seed)` -> deterministic spec (`spec.py`, registered in `KINDS`), rendered by `app.py` (port 8811), widgets in `widgets.js` keep committed values in `window.__gym.state`, the scripted expert in `expert.py` acts through jev's real observations (only controls jev offers; scrolls when a control's centre is outside the viewport; accepts the cookie banner when it covers something), and `eval_gym.py::success()` judges the real agent from page state. Held-out rule unchanged: `seed % 10 == 0` is never generated for training; eval seeds are `900000 + 10*i`. Randomised per seed: field labels, widget implementation, field order (35–30 % shuffled), hero pushing the form below the fold, cookie banner (25 %), four decoy nav links, wording of the goal. Run the expert: `../jev-ultrafast/.venv/bin/python finetune/webgym/expert.py out.jsonl 20 car|restaurant|signup|filter 0.12` Clean data: `python finetune/webgym/clean3.py` (gym_raw3/g_* -> gym/clean3_*, only episodes with a task_done case) Eval: `python finetune/webgym/eval_gym.py 10 car,restaurant,signup,filter 60` ## car — car rental search (`/t/car/` form -> `/results` -> `/results/c/`) Steps: pick-up location (autocomplete, inline or behind a trigger button; city or airport options), optional "return to a different location" **toggle** (native checkbox or `role=switch`) that reveals the drop-off autocomplete, pick-up date + time, return date + time, driver age, submit. Post-submit (about half the tasks): car-type checkboxes (Economy/Compact/SUV/Van/Luxury, multi-select), transmission radio (Any/Automatic/Manual), sort select, or the chain "filter SUV -> sort price low-high -> open the cheapest SUV" (car page, `meta.car`). Wrong/early submissions are recovered through "Modify search". Widget variants: dates picker (with/without Done/Apply) or typed MM/DD/YYYY; times native select or custom dropdown listbox (16 hourly options); age native select ("27 years") or stepper (targets within ±5 of the default 30); autocomplete typed as full name, prefix, alias (Zurich, MUC…) and committed by click or Enter. Sub-goals: pickup, [diff, dropoff], pdate, ptime, rdate, rtime, age, submit, [ctype | trans | csort | ctype+csort+open] (7–12). ## restaurant — table reservation (2 stages -> confirmation) Stage 1: party size (select / stepper / popup steppers), date (calendar picker or typed), **time-slot buttons** (10 slots 17:00–21:30, 2–4 per seed disabled = unavailable, never offered by jev), seating preference (radio / segmented / select). Stage 2: name, email, phone (text inputs), special requests (textarea, in half the tasks), "I agree…" (checkbox or switch), confirm -> confirmation page (`meta.done`, `meta.carried` = every committed value). Required-field validation (`role=alert` messages) blocks the stage submit; a Back button returns to the previous stage with values preserved. Text values come verbatim from the goal ("for Ada Lovelace, ada@example.com"). Sub-goals: party, date, slot, [seat], go1, name, email, phone, [req], agree, go2 (9–11). ## signup — 3-page account wizard (no password fields) Stage 1 account: full name, email, username. Stage 2 profile: country (native select with placeholder / custom dropdown / autocomplete), date of birth (typed MM/DD/YYYY or month+day+year selects), interests (checkbox group or toggle chips, 1–3 of 8). Stage 3 preferences: newsletter toggle (checkbox / switch), plan (radio / segmented / select: Free, Pro, Team). Next/Back on every stage, validation messages for empty required fields, final "Create account" -> welcome page. Sub-goals: name, email, username, go1, country, birth, interests, go2, [newsletter], [plan], go3 (9–11). ## filter — faceted product listing (`/t/filter/?f=1&…` -> `/p/`) 120 products with brand (8), colour (6), size (5), price, rating. Facets: price min/max text inputs + Apply (or Enter), rating radios (any / 3+ / 4+), brand and colour checkbox groups with **Show more** revealing the hidden options, size checkboxes, applied-filter chips with remove buttons (+ Clear all), sort (select or dropdown), pagination (numbers / Next / both). 35 % of tasks start with a pre-applied brand or colour filter; 60 % of those require REMOVING it (chip or unchecking), the rest keep it. Goals combine 2–4 constraints, 25 % end on page 2, 40 % end by opening the first result (the expected product id is computed from the same listing logic, `apply_listing`). Sub-goals: [rm_brand|rm_color], brand, color, size, rating, price, sort, [page], [open] (2–6; a facet with two values is two clicks). ## DART-style noise and recovery * 12 % per step: a harmless detour (click a decoy link or focus a field) that the expert then recovers from. * car (25 % of episodes): one premature submit -> wrong results -> "Modify search" recovery (`skill=recover`). * restaurant/signup (35 %): one premature Next while a required field is empty -> validation messages -> the expert fills the field and retries. The stage is only skipped early when validation is guaranteed to reject it, so carried values are never wrong. ## Expert success rates (20 episodes each, noise 0.1, seeds 1…) | kind | smoke test (20 ep, noise 0.1) | verification after fixes (noise 0.12) | |------------|-------------------------------|--------------------------------------------------| | car | 20/20 | 59/60 (seeds 700001+); the miss (submit covered by an open dropdown) is fixed, re-run 16/16 | | restaurant | 20/20 | 15/15 (seeds 710001+, mixed run) | | signup | 20/20 | 15/15 | | filter | 20/20 | 15/15 | | flight/hotel/shop (regression) | – | 45/45 (seeds 700001+); a "Modify search" link was added to the hotel results page so the early-submit recovery works there too (previously 2/24 hotel episodes died with "wrong results and no way back") | ## Training data (noise 0.12, 2 workers x 220 episodes per kind, seeds 300001… in steps of 20000) Raw: `finetune/out/gym_raw3/g__.jsonl`; clean (episodes with a task_done case): `finetune/out/gym/clean3__.jsonl`. | kind | seed ranges | clean episodes | cases | failures during generation | |------------|----------------------|----------------|--------|----------------------------| | car | 300001, 320001 | 431 / 440 | 20 501 | 9: covered date trigger / dropdown option (fixed afterwards, see above), 3 StalePage | | restaurant | 340001, 360001 | 440 / 440 | 16 517 | 0 | | signup | 380001, 400001 | 439 / 440 | 16 426 | 1 (covered dropdown option) | | filter | 420001, 440001 | 440 / 440 | 6 973 | 0 | | total | | 1 750 | 60 417 | | Cases per episode: car ~47, restaurant ~37, signup ~37, filter ~16 (each gold step yields a full-goal case and a sub-goal case, plus a DONE case per completed sub-goal and one task_done case). Add `$O/gym/clean3_*.jsonl` to the `cat $O/gym/*.jsonl` step of the training script (it already globs the whole directory). ## Files touched `spec.py` (car, restaurant, signup, filter_ generators + helpers `slug`, `person`, `staged`, `car_list`, `filter_items`, `apply_listing`), `widgets.js` (toggle, text, slots, multi, date "selects", initial values for Back navigation, placeholder selects, `error`/`empty`/`serialize`), `app.py` (form_page generalised with `show_if`; `car_results`, `stage_page`, `done_page`, `filter_page`, `filter_product_page`; hotel results "Modify search"), `expert.py` (kind-generic `same`, `satisfied`, `wrong_results`, `recover`, `early_submit`; `field_step` shared by forms and wizards; `filter_step`; public signatures unchanged), `eval_gym.py` (`success()` for the four kinds), `clean3.py` (new).