Cascade is the default because it is predictable: the cleaner who knows the property gets the first offer, and the host can see exactly who was asked, when, and what they said. Each offer is a row (job_assignment_attempts) with its token, expiry and response, so the history of a job is a list, not a memory. Assignment starts through a compare-and-swap in the database so two servers cannot both begin offering the same job.
The cascade respects each cleaner's availability rules (weekdays, date ranges, blocked windows) and their simultaneous-jobs capacity before making an offer, and skips a cleaner who has cancelled the same job. When the list is exhausted the job moves to waiting_for_cleaners and the host is alerted; adding a cleaner or restarting the cascade from the dashboard picks up where it stopped.