When staff move, cancel or hand off a tour

What Clara does when a leasing agent tells her to move a prospect's booked tour, cancel it, or give it to someone else: who she asks, what the tour holds while she waits, and what the host hears back.

2026-11-01 · Proposed — pending Fede's review · local POC built, nothing merged or on · related: Situs tours: current and new state

On this page
  1. The short version
  2. What really happens today (60 days)
  3. How Situs does it today
  4. 1. Four cases, told apart from staff's words
  5. 2. What the prospect gets
  6. 3. The tour while we wait
  7. 4. Handing a tour to another agent
  8. 5. What the hosts hear
  9. 6. Guardrails
  10. 7. Contract for the POC
  11. 8. Rollout and PR cut
  12. POC results, Nov 1–2
  13. Decisions for Fede
  14. Sources and caveats

The short version

Today, when an agent moves a tour, Clara tells the prospect "your tour is confirmed for the new time" and never asks if it works. Proposal: when the agent moved it because they can't make it, Clara asks the prospect "does Tue 4 PM work? If not, reply with a time that does", keeps the tour at the new time while she waits, books any counter-offer against the host's real calendar (drive time included), and tells the agent how it landed. When the prospect asked for the move, Clara just confirms, as today. "Give Rosa's tour to Miriam" becomes a staff command too: same time, new host, prospect not bothered. Situs staff already do the ask by hand: one new time, by text, wait for the yes; prospects answer in a median 6 minutes. It is rare on Clara today: 1 staff move through Clara in 60 days (77 tours), so this is built because Situs is about to route tours through her, not because of a pile of failures.

The plan in one table

Staff saysClara doesProspect getsAgent hears
"I can't make Rosa's 2 PM, move her to Tue 4"Moves the tour, marks it waiting for RosaThe ask: does Tue 4 PM work, or reply with a time that does"Moved. I've asked Rosa to confirm." Then: confirmed / new time / declined
"Rosa called, she wants Tue 4 instead"Moves it, confirmedToday's confirmation, no ask"Moved and Rosa has the new time."
"Cancel Rosa's tour"Cancels (today)Today's cancellation, plus "want another time?""Cancelled and Rosa knows."
"Give Rosa's tour to Miriam" / "can someone else take it"Same time, new host: calendar, AppFolio showing and host field moveNothing (time unchanged)Both agents told; if nobody is free it becomes case 1

What really happens today (60 days)

Moves through Clara are rare, and the one real staff move told the prospect nothing.

Since Sep 2Tours bookedTours movedStaff moves via Clara
Western Slope494 (5 moves)1
Camellia2620
Situs200
Who moved the 7MovesProspect replied
Prospect asked, Clara moved it21 "Thank you!", 1 silent
Prospect running late, staff moved it on Clara's line1told nothing (no text permission, email-only lead); attended
Our team fixing a Clara mistake by hand31 clarified, 2 silent
Second visit saved as a "move"1silent
Clara's running-late path0it tried twice for the late prospect and failed
Changed in AppFolio and synced in0impossible today: the showing sync never reads changes back

Lead time: 3 of the 7 moves happened under 2 hours before the tour (one 1 minute before), so same-day handling is the common case, not the edge. The best hand-written move we sent (Oct 2) already had the shape this design proposes: "it's at 3:45 PM, not 4:00… If 3:45 doesn't work, just reply here."

Seen along the way, not fixed (suspicion, needs its own reproduction): the late prospect's own "running behind, 9:15" failed in Clara's reschedule with "no prospect found for this phone number" (email-only Zillow relay lead), and staff were told a different reason. History before Oct 5 doesn't record who moved a tour; attendance exists from Oct 3. Each case was read in the raw conversation.

How Situs does it today

Situs already does propose-and-confirm, by hand: one time, by text, and they wait for the yes. Prospects answer in minutes. The gap is the other half: about half of staff moves are never asked; staff change the time in AppFolio and its automatic "your showing has been updated" text is the only notice.

Staff-initiated moves (Jan–Oct 2026)Count
Asks found (texts, mail, a call)9
  offered exactly one new time7 of 9
  offered two or more times0 of 9
  waited for the yes before changing it (where order is visible)3 of 4
Moves never asked, prospect told only by AppFolio's text4 of 8 + 1 by email
Median prospect reply to a staff-proposed time6 min (11 pairs)
Median staff reply to a prospect's own move request~54 min (8 pairs)
Staff conflict, one option · prospect yes in 4 min · 2257 S. Franklin, Jun 29"Hi M., I apologize but something has come up and our leasing agents are not able to tour on Friday, would you have availability on Sunday?" → "No worries!! We could definitely do Sunday, is 1:30 still a good time?"
Keep it or move it · yes in 5 min · Carr Street Flats, Jun 3"Would you like to keep the tour as scheduled today, or would you prefer to reschedule it for tomorrow at 9:30 AM?" → "9:30 tomorrow please and thank you."
Staff's own conflict · no reply · Mckenzie, Mar 25"I had an unexpected doctor's appointment come up at 3:20 PM… Would you be available to reschedule for tomorrow at 4:15 PM?"
Told, not asked · Barcelona, Jul 21"Your tour has been rescheduled for tomorrow at 9:45 AM… If you have any questions or need to make any changes, please let us know."
On the phone · Barcelona, Oct 30 (raw call transcript)"2:15 … no longer available. We have one for today at 3:30 if you would like to see the unit… you will receive a text asking you to confirm or deny."

Also seen: when nobody confirms, Situs staff cancel and don't drive out ("makes sense to not go if they don't confirm", Jul 28 email). After a no, they counter with one new time, a virtual tour or a lockbox. Sources: 884 AppFolio guest cards in our robot dump (54 showing time changes, hand-classified), the leasing mailbox May–Oct (read-only), 44 recorded calls. Emails on guest cards show no body and most calls are unrecorded, so 19 time changes have no visible message.

1. Four cases, told apart from staff's words

CaseStaff's words that decide itDefault when unclear
A. Staff can't make it"I can't make it", "something came up", "I need to move", "I'm stuck", "can we push her"Treated as A: Clara asks the prospect. Asking someone who requested the time costs her one "yes"; presuming costs a no-show. Staff are never asked "did she ask for this?"
B. Prospect asked (relayed)"she called/texted/emailed and wants…", "per her request", "she asked to move"
C. Cancel"cancel", "she's not coming", "she rented elsewhere"; "don't tell her" keeps today's silent cancelNever inferred from "move"
D. Reassign"give it to Miriam", "Miriam will take it", "can someone else take it", "I can't make it, can anyone cover""I can't make it" with no new time and no name: try D first (same time, someone free), fall back to A

Clara asks staff only what she cannot get anywhere else: which tour (two match), or the new time when staff gave none and nobody else is free. That is today's resolver behaviour; nothing new.

2. What the prospect gets

OptionMessageForAgainst
One-way notice (today)"Your tour is confirmed for Tue 4 PM."Zero frictionPresumes. A prospect who can't make 4 PM either no-shows or drops silently; staff never learn why.
Propose and confirm (recommended)"The team needs to move your tour from Mon 2 PM to Tue 4 PM. Does that work? If not, reply with a time that does."One question, one-word answer; staff's chosen time is usually fine; counters use the same booking rules as any requestSome never answer (handled by the timers below)
Offer 2–3 alternatives up front"Can we move you? Tue 4, Wed 10 or Wed 5?"More choiceLonger message, more back-and-forth, and staff already chose a time; slots offered must be held or they go stale
Case A: staff can't make it Agent (Lee)ClaraProspect (Rosa) "Can't make Rosa's 2, move her to Tue 4" moves calendar + showing; moveAcceptance: awaiting M2 "Does Tue 4 PM work?" H1 "Moved, asked her to confirm" "Can't, Thursday 10?" checks Lee's calendar + drive time → free "Done, Thursday 10 AM" H3 "She's now Thu 10, on your calendar" silence instead? one nudge → day-before asks again → H5 at 2 h before; tour stays on

After a no, Clara offers 2 open times from the host's calendar using the trip-aware pick (drive time between homes applies, same engine as every booking). A counter-offer ("Thursday morning?") goes through the ordinary prospect reschedule path against the same rules.

3. The tour while we wait

The tour lives at the new time only. The old time is dead (the agent can't make it), so holding both just blocks a slot nobody will use. A moveAcceptance record on the tour says "waiting for the prospect".

Move acceptance states awaiting_prospecttour at new time accepted countering unanswered declined → cancelled accepted at counter time tour happens or no-show "yes""no" / other timeno reply by T-2h open time foundnothing workskept at new time A second staff move while waiting replaces the record (superseded); the prospect gets one new ask, not two.
ThingWhile waiting
Calendar eventAt the new time on the host's calendar; title unchanged, one line in the body: "Waiting for Rosa to confirm this time."
PMS showingMoved to the new time through the adapter, as today. Not moved again until the prospect lands somewhere else.
RemindersRe-armed for the new time (today's behaviour). The day-before reminder carries the question if still unanswered; the 1-hour reminder is the normal one.
Virtual tourA moved virtual tour gets a new video room today (the old one can't be moved); the new link rides in the ask. When the move resolves to another slot or to a cancel, the abandoned room is closed.
HouseholdAsk goes to everyone today's confirmation reaches. First answer from any of them settles it.
Answer on another channelSettles it: the record is on the tour, not the thread.
Situs (Supervised)The ask is staff-started, so it goes out as today's confirmation does (no review). Clara's follow-ups to a no go through the review queue like any reply.

4. Handing a tour to another agent

5. What the hosts hear

Through the coworker template: email at Situs, text at Western Slope (each agent's own setting). One line each, only when something changed.

MomentHost hears
Ask sent"Moved Rosa to Tue 4 PM and asked her to confirm. I'll tell you when she answers."
Yes"Rosa confirmed Tue 4 PM."
Counter booked"Rosa couldn't do Tue 4. She's now Wed 10 AM, on your calendar."
Declined, nothing worked"Rosa couldn't make any time I offered, so I cancelled her tour. I'll keep following up for a new day."
No answer by 2 h before"Rosa hasn't confirmed the 4 PM move. The tour is still on; she may not come."
ReassignedOld host: "Rosa's Tue 4 PM is now Miriam's; off your calendar." New host: "You have Rosa, Tue 4 PM at Ember Estates, taken over from Lee."

6. Guardrails

7. Contract for the POC

Precise enough to code against with a local harness. Names are proposals; the shapes and rules are the contract.

7.1 State on the tour row

FieldShapeRule
hostPersonIdstring | undefinedNEW. The person hosting. Written at booking from the pool pick, on every host change, and from the PMS "assigned to" at the sync boundary. tourHostPersonId() reads it first, then falls back to calendarNode (legacy rows).
moveAcceptanceobject, belowNEW. Present only after a staff move that needs the prospect's yes. One per tour; a newer staff move overwrites it (old one recorded as superseded in history).
prospectConfirmedexisting {at, answered, forDate, forStartTime}REUSED. A yes to the ask stamps it for the new time; "confirmed for this slot" = forDate/forStartTime equal the tour's.
history[]existing TourEventStaff move keeps writing rescheduled with actor:'staff' + movedFrom, plus NEW moveCase ('staff_cannot'|'prospect_asked'|'reassign_fallback'). Host change keeps host_changed, plus NEW fromHostPersonId, toHostPersonId, source ('staff_text'|'staff_email'|'manager_page'|'pms_sync').
moveAcceptance: {
  id: string                       // move id, also the dedup key for messages
  state: 'awaiting_prospect' | 'countering' | 'accepted' | 'declined' | 'unanswered' | 'superseded'
  case: 'staff_cannot' | 'reassign_fallback'      // 'prospect_asked' never creates one
  from: { date, startTime, hostPersonId? }
  to:   { date, startTime, hostPersonId? }
  staffPersonId: string; staffWords: string        // verbatim, for the audit
  askedAt: ISO; askedVia: ('sms'|'email')[]
  nudgedAt?: ISO                                   // at most one nudge
  hostToldUnansweredAt?: ISO                       // the T-2h line, once
  offered?: { date, startTime, hostPersonId }[]    // max 2, only after a no
  resolvedAt?: ISO; resolvedByPersonId?: string; resolvedChannel?: 'sms'|'email'|'voice'
}

7.2 Decision table: staff instruction

Where the case lands (new inputs). reschedule_showing_for and the desk's reschedule_tour each gain move_reason: 'staff_cannot' | 'prospect_asked' | 'unclear' with staff_words_for_reason (a verbatim quote, same shape as caller_words_for_time), and notify_prospect (today only cancel_tour has it). A new desk tool change_tour_host takes tour_id, new_host_name (optional; absent = "someone else") and staff_words. moveAcceptance is written inside handleRescheduleTourImpl when opts.staffBooking is set and the reason is not prospect_asked, in the same row write as the reschedule; notifyProspectOfStaffMoveOrCancel then sends M2 instead of the confirmation.

Staff wordsNew time given?ActionProspect message
Relayed request (B)yesreschedule, confirmed, no moveAcceptanceM1 confirm (today's)
Can't make it (A) or unclearyesreschedule + moveAcceptance awaitingM2 ask
Can't make it, no time, no namenotry reassign (D) at same time; if no giver free → nearest slot any giver is free → as A with case:'reassign_fallback'none (D) / M2 (fallback)
Give to named agent / "someone else" (D)n/ahost change; refusal host_busy/not_eligible → readback names why + nearest free giver; never moves the time without staff's "can't make it"none
Cancel (C)n/acancel (today)M5 cancel + rebook offer
any + "don't tell her" as above, no prospect message, no moveAcceptancenone
M2 cannot go out (no permission to text, no email, company's tour-confirmation path Off/Preview, send failed) move stands, NO moveAcceptance created; staff hear "not told" (today's prospect_notified:false); never an ask that wasn't sentnone

7.3 Decision table: prospect reply while awaiting_prospect or countering

Reply meansState →TourProspectHost
Yes / thanks / "see you then"acceptedstamp prospectConfirmed for new slotClara's normal short ackH2
Names another timecountering → acceptedordinary prospect reschedule against host calendar + drive rule; if not open, offer the 2 nearest open (offered)Clara's reply (booked / 2 options)H3 when it lands
No, without a timecounteringstays at new time2 open times, trip-aware, host first then any sharing givernone yet
No to the offered times too, or "never mind"declinedcancelledClara's reply; normal follow-up cadence resumesH4
Anything else (question)unchangedunchangedanswered normallynone

Any party on the tour (household) and any channel (text, email, call) can settle it. Classification runs in the existing tour reply decider; it gains one input: "this tour has a pending move to X".

7.4 Timers (property's own clock, quiet hours respected)

WhenConditionEffect
Ask + 4 h (daytime)still awaiting, new slot more than 24 h outone nudge M3, stamp nudgedAt
Day-before reminderstill awaitingreminder uses M4 wording (asks again); no separate nudge that day
New slot − 2 hstill awaitingstate unanswered, host gets H5 once; tour stays on
Move made inside 2 h of the old or new slot ask goes by text if allowed (else email), no nudge, host gets H5 immediately if still awaiting at slot − 30 min
Second staff moveany open stateold record superseded; one new M2 ("one more change"); timers restart

7.5 Messages

M-messages go to the prospect (fixed family, translated, same sender and channel rule as today's confirmation; email variants answer by reply). H-messages go to the host through ONE sender: the host notice path (host-confirm-notice.ts, plus the Nov 1 host-email route that sends tour-day updates to the host, #24230): email by default, text when the agent opted in.

M1 confirm (prospect asked)Today's confirmation copy, unchanged.
M2 askHi Rosa, the team needs to move your tour at {home} from {old day, time} to {new day, time}. Does that work? If not, reply with a time that does. {video link if virtual}
M3 nudgeRosa, just checking: does {new day, time} work for your tour at {home}?
M4 day-before (still awaiting)Reminder: your tour at {home} is tomorrow at {time}, the new time. Still good? If not, reply with a time that works.
M5 cancelToday's cancellation copy, plus: "Want to pick another time?"
H1 ask sentMoved {Rosa} to {new} and asked her to confirm. I'll let you know.
H2 yes{Rosa} confirmed {new}.
H3 counter booked{Rosa} couldn't do {new}. She's now {counter}, on your calendar.
H4 declined{Rosa} couldn't make any time I offered, so I cancelled her tour. I'll keep following up.
H5 unanswered{Rosa} hasn't confirmed the {new} move. The tour is still on; she may not come.
H6 reassigned (old / new host){Rosa}'s {slot} at {home} is now {Miriam}'s; it's off your calendar. / You have {Rosa}, {slot} at {home}, taken over from {Lee}.
H7 nobody freeNobody else is free at {slot}. I moved {Rosa} to {nearest}, {host}, and asked her to confirm.

7.6 Reassignment write order

  1. Pick host: named → must pass eligibleTourPersonCalendars + free for the window; "someone else" → same filter over all givers, nearest-first (trip-aware pick). Today's free check (readHostFreeForTourWindow) reads the busy set only, with no drive pads: the POC adds padsAround from the drive engine.
  2. Run the existing applyChangeHost (claim → create new invite → write → delete old). Stamp hostPersonId in the same write.
  3. PMS: queue "assigned user = new host" through the adapter (matched inside the PMS, as the showing assignment does since Oct 31).
  4. Inbound: not read today. The sync reads "assigned to" on the guest card only; the showing's assignee is written (Oct 31) but never read back. Needs a new adapter read of the showing assignee, normalised to a person id, then step 2 with source:'pms_sync', no prospect message, both hosts get H6. The POC stubs this input.
  5. Lift today's refusal of host change on PMS-synced leads.

7.7 Harness cases the POC must pass

#CaseExpect
1"I can't make Rosa's 2, move her to Tue 4"M2 sent, awaiting, H1
2"Rosa called, wants Tue 4"M1 only, no record
3"move Rosa to 4" (unclear)as 1
4prospect "yes" by email after a text askaccepted, H2
5prospect "can't, Thursday 10?" host freemoved to Thu 10, accepted, H3
6prospect "no" then "none of those"cancelled, H4
7silenceone nudge, M4, H5 at −2 h, tour kept
8second staff move before replysuperseded, one new M2
9"give Rosa's tour to Miriam", Miriam freehost changed, no prospect message, H6 ×2
10"can someone else take it", nobody freefallback move, M2, H7
11PMS "assigned to" changedhost changed silently, H6
12"move her, don't tell her"moved, no message, no record
13planted defect: ask sent with the OLD timeharness goes red

8. Rollout and PR cut

PRWhatKind
1. StatehostPersonId, moveAcceptance, history fields, readers; no behaviour change (byte-identical replay)storage
2. Ask and answerscase split in the staff tools, M/H messages, decider input, timers in the tour workflowimprovement to live behaviour: no switch
3. Reassignstaff tool on text/email/voice, "someone else" pick, adapter write, inbound "assigned to"new staff command; no prospect-facing effect, no switch

Ladder (testing playbook): read-only replay of every real staff move in the last 60 days (what would Clara have sent, byte-identical otherwise) → Willows live run of harness cases 1, 4, 5, 7, 9 with a real text, email and calendar → live everywhere on merge. Proof is the real thread ids, never unit tests.

POC results, Nov 1–2

2 of 23
cases right today
the two where telling her is already right
23 of 23
cases right with the prototype
same cases, same checks
10 of 10
planted bugs caught
each one turns the test red
54 of 54
messages pass the wording check
and it caught 2 of 2 planted bad lines
23 of 24
staff texts read into the right case
the miss is a truly ambiguous text

Wording check: Opus 5.5 grades every rendered message. Staff-text reading: the same model and tool definitions the staff desk uses in production (Sonnet 5.5). Both ran on the subscription.

Case by case

CaseTodayWith the prototype
Agent can't make it and moves the tourProspect told "confirmed", never askedAsked "does Tue 4 PM work?"; the host hears Clara is waiting on her
Prospect asked for the move (through staff)Plain confirmationSame already right
Agent moves it with no reasonTold "confirmed"Asked (unclear means ask)
Asked by text, she says yes by emailNever askedSettled; host told
She texts "yes"Never askedSettled; host told
She offers another time and the host is freeNever askedTour moves to her time; host told
She says no, then "none of those"Never askedClara offers two times, then cancels; host told
She never answersOne plain reminderOne nudge 4 h later, the day-before reminder asks again, host warned 2 h before; tour kept
Agent moves it a second timeTwo "confirmed" textsFirst ask replaced; one new ask that says "one more change"
"Give Rosa's 2 PM to Noor"No such commandHost changes, time stays, prospect not bothered, both agents told, the PMS showing follows
"Can someone else take it?" and nobody is free at 2No such commandNearest free agent and time (3:00, Noor) becomes a move she is asked about; both agents told
Showing reassigned directly in the PMSClara never noticesHost follows the PMS, both agents told, nothing echoed back
"Move her, don't tell her"She gets "confirmed"Moved, nothing sent, nothing waiting
Household: the partner answersBoth get "confirmed"Both asked; the partner's yes settles it
Virtual tourConfirmedThe ask carries the video room for the new time
Same-day move (the real Western Slope shape)ConfirmedAsked; no nudge (under 24 h out); host warned 2 h before
Move inside 2 hoursConfirmedAsked; host warned 30 min before
Supervised companyConfirmedAsk goes (a person started it); host notice and nudge wait for review
Spanish speakerConfirmed in SpanishAsked in Spanish
Email-only prospectConfirmed by emailAsked by email; an email yes settles it
"Give it to Leah" and Leah is busyNo such commandRefused, names the nearest free teammate; nothing changes
The ask cannot go out (no text permission, no email)Nothing sentSame; nothing waits on her; staff hear "not told" already right
Planted bug: ask quotes the OLD timeTold "confirmed"Asks with the new time; planting the old time turns the test red

What this does not prove yet

Rung (playbook)Status
Made-up cases, red today and green on the branch, planted bugs caughtdone
Replay of the real 60-day staff moves, byte-identical otherwisenot run the cases are shaped from real tour rows, but no real thread was replayed
Willows live run with a real text, email and calendarowed per PR blocked tonight by the no-send rule

Where the prototype differs from section 7

PointPrototypeProposal
Note to the old host when "someone else" lands on a new timeReworded: the contract line failed the wording checkUse the new line
New host in that same caseAlso hears "it's yours now"Add it to the contract
Names of the hand-off tool and its fieldsIts own namesPRs adopt the contract's names
Nudge to an email-only prospectNone (the nudge is text only)Decide: email nudge or none
"She asked to move, the agent picked the time"Not covered by the contract; the one missed staff text is this shapeAsk her
Host noticesGo through the existing team-notice senderOne sender, as the contract says

Not built yet: decision 3 (a bare "I can't make it" tries another agent first), drive pads in the free check, closing the old video room, the real PMS "assigned to" read (stubbed). Open bug in the prototype: when "someone else" lands on a new time but the ask could not go out, the old host is still told "asked her to confirm".

Measured PR cut

PRWhatLinesKind
1. Statetour fields and readers~340no behaviour change
2. Ask and answersthe ask, replies on any channel, host notices, "ask could not go out leaves no record"~700improvement to live behaviour, no switch
3. Timersnudge, day-before re-ask, host warning~150improvement, no switch
4. Reassignstaff command, PMS showing follows, PMS "assigned to" flows back~480new staff command, no switch

Four PRs, not section 8's three: the timers come out of PR 2 to keep it near the size limit. The test harness rides with PR 2. Build the PRs from the branch's final files, never by copying its commits one by one (three early commits carry real staff first names). This page's design text also uses real staff first names in examples (Miriam, Kat, Lee); the prototype uses made-up ones.

Evidence: branch fede/poc-staff-tour-move, folder scripts/coworker-harness/staff-tour-move/results/ (before and after files with the attestation, the planted-bug run, both model runs). Run it: npx vitest run --config scripts/coworker-harness/staff-tour-move/vitest.config.ts.

Decisions for Fede

  1. Unclear staff words → ask the prospect, never staff. Recommend yes.
  2. No answer by tour time → keep the tour, warn the host 2 h before. Recommend yes (cancelling drops prospects who just didn't reply).
  3. A bare "I can't make Rosa's 2 PM" first tries to hand the tour to another free agent at the same time, and only moves it if nobody is free. Recommend yes; the agent hears who took it.

Sources and caveats

PropFlow Docs