Skip to content

Settle polling

The re-execution policy of step-code execution. For engineers tuning polling_timeout and reasoning about retry behavior.

One settle window per step execution: the step executor creates it from the polling settings and threads it into every execution of the step's code — cached code and generation candidates alike, replay-strict included.

The window

  • The window starts at the first execution of the step code — never at the first failure; the step's internal auto-waits count inside it, and the first execution may consume the whole window: no repetitions follow
  • The window gates repetitions, never kills a running attempt: the remaining-time check happens only before a repeat
  • A failure re-executes the same code after polling_delay when its kind is pollable and time remains — until success or window end; success continues the step normally
  • Everything else propagates as-is: locator ambiguity, Python-level errors of the step code, non-pollable and unknown kinds. On cached code such a failure goes straight to classification; on a generation candidate it feeds the next regeneration request with the fresh error
Setting Default Env Meaning
polling_timeout None PRETTYPLAY_POLLING_TIMEOUT settle window seconds per step execution; None/0 — polling off
polling_delay 0.5 PRETTYPLAY_POLLING_DELAY pause between re-executions, seconds

Size the window above the longest auto-wait it must absorb — 6.0 covers one exhausted 5 s expectation plus one re-execution.

from prettyplay import PrettyConfig, PrettyPlay

# a local generation session with a settle window
test = PrettyPlay("login-flow", config=PrettyConfig(polling_timeout=8.0))

Inside the engine contour the policy is a plain function pair:

from prettyplay.engine.polling import SettleWindow, settle

window = SettleWindow(timeout=config.polling_timeout, delay=config.polling_delay)
settle(execute=run_step_code, code=cached_step.code, page=page, window=window)

run_step_code comes from prettyplay.engine — the execution routine the caller threads in.

Count-bounded re-execution (tries)

A step declared with a retry count replaces the time bound for that step's loop with a count bound — t.step("open the cart", tries=3) builds a SettleWindow(timeout=..., delay=..., tries=3): the step's code executes at most 3 times in total, the first execution included (tries=1 — no re-execution). The pollable filter and the polling_delay pause keep applying between executions; retries appear as the same settle_retry records; count exhaustion propagates the failure to the ordinary path — the step never stays green on retries alone. Each settle call counts from zero: the cached code and every generated candidate each get their own full count, and no LLM budget is consumed. See Writing steps.

Which failures poll

The driver ships a fixed pollable map, exported as is_pollable_failure(exc) from prettyplay.driver: it returns True when the exception kind is transient page state and False when the failure is deterministic or unknown — see Driver facade. Pollable: timeouts, element-state races, navigation and context races, plain AssertionErrors of failed checks — a failed expect(...) chain or a plain Python assert on an immediate read (assert videos.count() > 1) alike. Not pollable: locator ambiguity (deterministic), Python-level errors of the step code itself (syntax, names, types), unrecognized failures. The map is fixed in code: it never reads settings and never asks an LLM.

Visibility

Each repetition writes a settle_retry record to the logger prettyplay at INFO — the attempt counter and the failure text. Re-executions emit no hook events: hook events fire per step or per LLM attempt, never per execution retry.

Budgets

Re-executions consume no generation or healing budgets and make no LLM requests — polling is cheaper than one regeneration attempt. The window applies to cached code in strict mode too: re-executing cached code is execution, not generation.

Rules

  • polling_timeout None (default) and 0 keep polling off — the settle call degenerates to a single execution — unless the step declares tries: a count-bounded window stays alive with the time window disabled
  • polling_delay 0 re-executes without a pause
  • The window never re-arms: one window per step execution, shared by every execution inside it — the steering dialog included
  • A non-pollable failure is never swallowed, translated or retried — the classification path decides it