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_delaywhen 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_timeoutNone(default) and0keep polling off — the settle call degenerates to a single execution — unless the step declarestries: a count-bounded window stays alive with the time window disabledpolling_delay0re-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