Clara joins mid-flow
Clara as a coworker who can step into a renter's journey at any point: a showing staff booked by hand, a lead a person answered first, a tour that already happened. Two full designs on one brief, Fable 5.1 and Opus 5.5 at max, so the pick is made once.
Proposed, pending Fede's review · Nov 2, 2026, Norma section, one-pager and one-page tour design added Nov 4 · read-only research, nothing sent, no PR · sub-designs on this page: staff-booked showings, confirm or cancel · related: staff tour moves · AppFolio switch list: Situs ramp-up changes
Tours: one design, whoever booked them (Nov 4, proposed)
Added Nov 4, 2026 · Shipped Nov 5: #25337 (every tour step reads the thread first) and #25330 (Clara runs the steps on showings staff book; active only once onboarding records the customer's PMS no longer texts renters about showings) are merged. Live proof at Fairhaven is blocked until the test-company model accounts reset (~Nov 10).
What changes
| Piece | Today | After | Switch |
|---|---|---|---|
| Staff-booked showings | invisible to Clara | one tour record, origin human | none (a fix) |
| Clara's own tour messages | sent blind; only the after-tour link checks for a copy | every step reads the thread first, skips what was already received | none (a fix) |
| Messages on staff-booked tours | none | same steps as her own tours, read-first | none (a fix). Runs once onboarding records that the customer's PMS no longer texts renters about showings; default: still texting, so nothing changes at any customer on merge |
Results of the local test (Nov 4)
Real threads replayed locally, no sends: 27 tours from Situs and Western Slope, 89 tour steps, each judged against the thread as it looked at send time. Labels were written by hand before the model ran.
| At send time | After a 5-minute wait | |
|---|---|---|
| Right call (agrees with the hand label) | 72 of 89 (81%) | 76 of 89 (85%) |
| Duplicate would have gone out | 14 | 10 |
| Needed message would have been skipped | 3 | 3 |
| Skip only visible after the wait | – | 27 (25 are booking confirmations; AppFolio's text lands 1–2 min after the office books) |
Staff guest-card notes: live and read by Clara (Nov 5)
Fede, Nov 5: 'we need to be able to use notes', 'no feature flags', 'battle test it'. The Sep 30 notes import (dark for five weeks) is now live everywhere (#25404). Notes re-import within a minute of a change (#25413). Clara's follow-up, first-reply and tour-step reads consult the notes through the model, classifying each as a person acting, a bookkeeping note, or Clara's own. The first cut (a presence check) was rejected by its own battle test.
| Check | Rule-based cut | Model read |
|---|---|---|
| Wrong holds on live-possible cases | 7 of 25 (28%) | 0 of 13 |
| Follow-ups held, Situs / Western Slope (30 days) | 1–15 / 10–30 | 5 of 45 / 19 of 119 |
| Planted bookkeeping note held / planted teammate call held | n/a | 0 of 24 / 24 of 24 |
| Note text leaking into any decision or renter-bound text | 0 | 0 of 653 |
| No card rows: decisions identical | 305/305, 344/344 | 164/164 |
- Planted-defect controls: remove AppFolio's booking text, the model sends (PASS); add a teammate's confirming text, the model skips (PASS).
- Most remaining misses are definitions, not reading errors: the 1-hour reminder also carries how to get in, so the model sends it even after AppFolio's confirm exchange gave the time (5 of the 13 misses); reworded to time-only, 39 of 40 agree.
- "A teammate owns this thread" must not gate tour reminders: it was true on 16 of 20 steps on Clara's own tours. A one-hour "teammate texted recently" rule would have silenced 13 needed messages.
- Limit: this only stops Clara's copy. At Western Slope showing 597, AppFolio's "Thanks for visiting" email came ~22 h after Clara's after-tour link, so the renter got both; only AppFolio's own switch removes that.
Honest number: the read is right 85% of the time, so Clara's steps on a staff-booked tour run only where the PMS's own showing texts are off; otherwise about 1 in 10 renters would get a duplicate.
Harness: branch fede/office-activity-harness, scripts/coworker-harness/office-activity/send-check/ (prompt, fixtures and labels in separate folders; 161 subscription calls). No PR.
Built in three small PRs, in that order. Proof: replay of real Situs and Western Slope threads with AppFolio's texts on, then a live run at Fairhaven. Decision (Fede, Nov 5): no Clara switch ("why is this a setting"). Status: PR 1 read-first gate (#25303), PR 2 steps on staff-booked showings (#25229); results and Willows proof added here when they land.
Nov 4 one-pager: what staff do by hand, what Clara sees, and the path forward
Added Nov 4, 2026 (evening) · Tour-row decision taken Nov 4 evening; three pending · harness part 1 results in · full pass by an Opus 5.5 and a Fable 5.1 reviewer, five live sessions asked, past sessions mined, every page re-read · read-only, no PR · supersedes the Nov 2 decision list and N1–N5: three decisions below · independently verified against main a3cdc6bbeb Nov 4 evening (corrections below)
The problem in one line. Clara decides "is a person already on this?" from the last text in the thread. At Situs the office works in AppFolio, not in texts: a person booked every showing this year, and staff answered first in 36 of 47 text threads. Office silence in the thread means "someone has it", and Clara reads it as "nobody does" (Norma Nov 4, Matthew Nov 2). The opposite failure exists too: one staff text silences her for good (43 of 73 active Situs threads).
1. What the office does by hand, and whether Clara sees it
Three questions per action: is it in our records, is it shown in her prompt, does any decision read it. "Yes to records, no to decisions" is the pattern; staff-made showings are the one row that is no on all three.
| Staff action in AppFolio | In our records | In her prompt | Read by a decision |
|---|---|---|---|
| Texts the renter from AppFolio | live (1–2 min) | yes | yes: stops wake, sets the hold (never released) |
| Emails the renter from AppFolio | live (up to 6 h) | yes | partly: the follow-up check, and it stops the wake when it is the newest office row; sets no hold |
| Books, moves or cancels a showing | no | only AppFolio's own notice text | no; availability also reads connected host calendars, so where the host's calendar is connected the showing is an unnamed busy block |
| Marks shown / no-show | no | no | no |
| Writes a note | live (the card's newest notes, within a minute, no author; the entry-by-entry import with authors is once per card, dark) | no for renters; a staff member can ask Clara by text or call and she reads the notes back | no; one look reads them, log-only; the classifier has no caller |
| Logs a call | live (in the card's newest notes, by day, no author; the import with authors is once per card, dark) | no for renters; staff can ask Clara and she reads them back | no; log-only |
| Changes the assignee | live | no | no; the assignee gate is built, never wired |
| Sends the application link | live (as a mirrored send) | yes | yes: Clara skips her own link |
| Marks applied / lost | live | stage | yes: first email and follow-ups stop |
| Creates a card by hand (walk-in) | live | yes | first email yes; "hand-made, wait" is log-only; wake never checks it |
Showing outcomes (shown / no-show) are out of scope for now by Fede's word (Nov 4): a later phase, not dropped.
The wake reads the whole thread, not texts only. An office call filed on the thread would have kept Clara quiet on Norma; it was filed about 40 minutes late.
Sources: main a717d5d4ea, production settings read Nov 4 (notes import on only at Sandbox). Ten doors decide whether Clara speaks (wake on an AppFolio text, Twilio text, email, first touch, staff email, staff text, reminders, after-tour link, follow-up cadences, voice); none reads a staff showing or note. Out of scope by Fede's word (Nov 4): staff mail in their personal mailboxes.
2. Results so far
| What was measured | Result | Meaning |
|---|---|---|
| Chime-in replay, 42 live Situs prospect texts, 19 threads, Oct 28 to Nov 4 (session 006, numbers) | today's sender list right 34 of 42, 1 talk-over · the follow-through read as written 31 of 42, 7 talk-overs · control holds | "Has anyone on our side answered?" is the wrong question. The read needs "does a person at the office own this thread right now", with any staff touch, human-made showing or office call as yes. The late office calls and notice-only showings were the two signals it could not see |
| Norma, Matthew, Deyci: both gates on the Nov 4 case | both wrong (the office call proving staff had it was filed 40 min after the wake); Oct 30 both quiet, right; Nov 2 today's gate never ran | Reading the record is not enough on its own: the wake rule fires before any read asks. Read and react are two steps, as Fede put it |
| Situs shape (Jan to Sep) | 507 showings, all by a person (383 staff, 124 the shared VA seat); 15 of 111 real staff tour changes handled right; 43 of 73 held threads never released | The before numbers for the path below |
| Office-activity harness, part 1 "read all the data": 48 Situs cards, Oct 21 to Nov 4, 228 staff actions, truth = each card's raw AppFolio Actions Log labelled by a separate model pass, 4 cards and the 5 named cases checked by hand (this lane, local, no PR, replay makes zero network calls) | what we already store finds 152 of 228 (67%) and 0 of 27 staff-booked showings · adding the per-card showings read: 181 of 228 (79%), 26 of 27 showings · control: drop the showing source and the Nov 4 case goes red again | The showing read is the single biggest gain. The 47 still missed: 17 mass texts we file as bulk on purpose, 13 messages for 4 people with no thread on our side, 6 cards with no creator or a different created time, 5 outcomes and moves the current-state read cannot see, 6 single rows. Part 2 is the next row |
| Part 2, "react correctly": the 36 of 42 replay turns that have a part-1 fixture, 3 runs each (session 006, round 2) | A today's sender list: 30 of 36 right, 1 talk-over, 3 left waiting · B the read with the office rows visible: 28 right, 7 talk-overs · C the read plus "does a person at the office own this thread right now?": 32 right, 0 talk-overs, 4 left waiting; stable over 3 runs; controls hold (inverted 4, shuffled 25.8) | C is the design. Under C: Norma (Nov 4) quiet, and the office called 1.7 min later; Matthew (Nov 2) quiet at the booking notice; Deyci (Oct 30) quiet; Stephany's thread quiet twice where B spoke and staff answered 3.6 and 17 min later. Every "owned: yes" rested on a staff message or a named office person, never on an unauthored row. Caveat: the office rows were visible at action time, the favourable case; production sees them later, so the live number sits between B and C until the showings read (step 1) is live |
| What the record cannot tell (same run) | 74 notes and logged calls have no author (some are Clara's own) · no showing names who booked it (the confirmation goes out under the card's assignee; 14 of 26 would be wrong by sender) · 30 "status changes" are mostly the Tenant Turner integration, not a person | Rows carry "author not recorded", never a named teammate. The read in step 2 treats an unauthored row as office activity, not as a person's word; decision C3 reads facts from notes, never attributes them |
| Staff action kind (Situs, Oct 21 to Nov 4) | Stored data only | + showings read |
|---|---|---|
| Showings booked | 0 / 27 | 26 / 27 |
| Showings moved | 0 / 3 | 1 / 3 |
| Showings cancelled | 0 / 2 | 2 / 2 |
| Showing outcome (no-show, recorded) | 0 / 3 | 0 / 3 |
| Notes | 19 / 21 | 19 / 21 |
| Calls logged | 11 / 11 | 11 / 11 |
| Application sent | 12 / 19 | 12 / 19 |
| Card made by hand | 17 / 23 | 17 / 23 |
| Staff texts | 88 / 114 | 88 / 114 |
| Staff emails to the shared inbox | 5 / 5 | 5 / 5 |
Built on the Nov 2 POC's contract (Situs Onboarding lane): neutral contract under src/lib/domain/pms/office-activity/, AppFolio adapter in the integrations layer, harness and 58 scrubbed fixtures under scripts/coworker-harness/office-activity/, branch fede/office-activity-harness pushed as a backup only, no PR, no CI. Robot: 59 card reads + 58 showings reads, read-only, no login. Atlas row added. Not done: drift test and hash lock, README, the Sep 1 dump replay.
3. Corrections from the independent pass
Bottom line. "A refactor first" is mostly false; what is true is narrower: give the existing door the office record as an input and have the five callers ask it.
| # | Claim | What the code says | Where |
|---|---|---|---|
| 1 | The one function "never says stay silent" | It does: the turn-start ask returns silent when a teammate holds the thread, and the before-draft door has seven silent rules; only the supervision ask never does. | should-speak.ts, should-speak-before-draft.ts |
| 2 | "Already the only door for every reply and send" | Overclaim: the build check only catches copies of the review-hold rule, and five other "is a person on this" deciders live outside it. | check-should-speak-duplicates.ts, wake-on-reply.ts, decide-follow-up.ts, first-touch-decision-log.ts, guest-card-reply-eligibility.ts |
| 3 | The wake "reads texts only" | False: it reads the whole thread, so an office call filed on Norma's thread before 17:20 would have kept Clara quiet; Norma is a late call record as much as a missing showing read. | wake-on-reply.ts, pbx-call-record.ts, ingest-pbx-recording.ts |
| 4 | Old step 4: "fill shouldSpeak with the model read" | That file must not load the model client (the text and email bundles import it), so the model read lives in the before-draft door and is handed to the wake and cadence callers. | should-speak.ts, should-speak-before-draft.ts |
| 5 | Old step 3: "silent tour row: no workflow start, no message" | A confirmed tour row is not silent today: the walker starts reminders and sends the application link, the team gets a confirmation email, and the showing write-back fires. | tour-cohort-walker.ts, send-pm-tour-confirmation.ts, showing-sync-stream-consumer.ts |
| 6 | Section 6: follow-through calls the one function | Not today: follow-through has its own mode (default off) and its own read, sends nothing yet, and would become a sixth door. | follow-through/mode.ts, activities/follow-through.ts |
| 6b | "Four gates start a turn without asking it" | Partly wrong: the wake's turn goes through the before-draft door (which said "reply" for Norma because nothing was on the thread), follow-ups ask it at send time, tour timers skip the teammate stop on purpose and voice answers every caller by design; the gap is the missing office input, not a missing ask. | pms-mirror-reply-wake.ts, conversation-manager.ts, tour.ts |
| 7 | "Intake parks a card assigned to a person" (Fable memo) | False: the assignee check has no caller, so "built, never wired" is right. | guest-card-reply-eligibility.ts, guest-card-reply-intake.ts |
| 8 | Follow-up check counts staff texts only (Fable memo) | False: it counts any staff message on any channel. | staff-touched-since.ts |
| 9 | Availability reads our tours only | It also reads connected host calendars, where AppFolio copies Situs showings (#24585), so those already show as unnamed busy blocks. | busy-source-union.ts, check-availability.ts |
| 10 | Oct 21 "always yes" contradicts a default-off import | No: Oct 21 removed the switch on Clara writing showings into AppFolio; importing with no switch rests on the Sep 30 rollout rule (a read that sends nothing). | #21699 |
| 11 | Table 1, staff email: "follow-up check only" | It also stops the wake when it is the newest office row. | wake-on-reply.ts |
| 12 | Table 1, logged call: "once per card, dark" | Notes and calls also arrive live in the card's newest notes, by day, with no author (11 of 11 calls in the harness). | guest-card-mirror.ts, harness assemble.ts |
| 13 | Missed by all three reviews | A neutral seam for outside tour changes already exists and names "showing on the PMS card" as its next source; the staff-showing import should be its second source, with the harness's showing shape merged into it. | external-change/types.ts, reconcile.ts |
| 14 | Missed by all three reviews | Text-pattern deciders are still live on this path ("I'll call you", "stop texting me", the bare-closer words), each against Fede's Nov 2 reasoning rule. | decide-first-touch.ts, decide-follow-up.ts, should-speak-before-draft.ts |
| 15 | "Teammate replied" hold | Confirmed: set only by a live teammate text at companies texting through AppFolio, never released; the comment saying the mailbox also sets it is out of date. | file-message.ts, conversation.ts, should-speak.ts |
Confirmed as stated: the Oct 14 one-decision gate was never on main; first touch is log-only; the notes import runs once per new card behind a default-off setting; the notes classifier has no caller; nothing imports staff-made showings. Harness re-run offline: same numbers (152 → 181 of 228; dropping the showings source returns 152; zero network calls). Limits: every note, call and showing it found carries no named person; truth labels are model-made, 4 cards checked by hand; the adapter's hard-coded Mountain time and notice-wording match are fine for a prototype, a defect in the product. Source: code origin/main a3cdc6bbeb, harness fede/office-activity-harness 4091695854.
4. Path forward, rebuilt: see first, decide second
| # | What ships | What customers notice | Proof | Switch | Needs | Fede? |
|---|---|---|---|---|---|---|
| 1 | Office record, read only. One read per card through the adapter (showings, notes, calls, assignee, status) when a renter's text wakes Clara, when a follow-up is due, and on a calendar tick per company. The notes import loses its setting and its once-per-card limit. Measure how late office calls are filed, then write the call's heading row when the recording lands, before transcription | Nothing | Live, read-only at Situs against the day's AppFolio calendar; rate-limit errors flat | None (sends nothing) | — | No |
| 2 | One ownership read in the existing door, adding quiet only. The model reads the thread, roles and office record: "does a person at the office own this now?" The turn-start check, the wake, the follow-up check and first touch ask it. It can only make Clara quiet where she speaks today; every existing stop stays until C1 is answered; if the record can't be read, today's answer stands | Clara stops talking over staff (the Norma and Matthew shape) | Replay of real Situs and Western Slope threads with a control, then one real end-to-end run: staff book in AppFolio, the renter texts, Clara stays quiet, record ids kept | None (a fix) | 1 | No (Nov 2/3 rulings) |
| 3 | A staff showing becomes a tour in a "human-booked" state, through the existing outside-change seam: booked, moved, cancelled, host changed. The reconciler, confirmation email, write-back and availability all respect the state | No times offered against a staff showing | Replay of the 507 Situs showings: zero sends, zero AppFolio writes; control: remove the state and the reconciler goes red. Then one live case | None (sends nothing) | 1 | No (decided Nov 4) |
| 4 | Tour steps read the thread and the current host (rules R and H) | No "how did the tour go?" after a cancel; the right host gets the note | Replay of the 79 moves, then live | None | 2, 3 | No |
| 5 | New capabilities: Clara speaks on human-booked tours (reminders, codes), and picks a thread back up when the renter is waiting on nobody. Retires the never-released hold and the wake's list | Clara helps on staff-booked leads | Sandbox company in Situs's shape, end to end | Pick-up uses follow-through's existing mode; tour messages get one company setting, default off | 2, 3 | C1, C2, then turn-on |
Step 3 is decided. Fede took the human-booked tour row (silent, no switch) on Nov 4 evening; it is being built now. A confirmed tour row is not silent today: the tour walker starts reminders for a future tour and sends the application link at once for a recent past one; the team confirmation email fires when a tour turns confirmed; the showing write-back fires on every tour row. Each of those must refuse the human-booked state, each proven with a planted-defect control.
Steps 1 and 3 run side by side. Live runs in steps 2 to 4 need a real AppFolio test card; reopening Willows is Gera's call (ask Smith on the bridge). Stop: wake-list patches, timers, a second decider, confirm-or-cancel and group texts until step 3. Follow-through must ask the step-2 read before it sends. Where: tour-cohort-walker.ts, send-pm-tour-confirmation.ts, showing-sync-stream-consumer.ts, external-change/reconcile.ts.
5. Decisions for Fede (three; (a) recommended on each)
- C1. Once the office has spoken on a thread, may Clara ever speak there again on her own? Fede ruled yield + next-day re-check, Nov 4 (session 006; quotes relayed): "we need to be smarter here", "a human touched it during the day great, but then we need to check next day and see if we need to follow up", "not stay silent forever", "if a reschedule didn't happen why not have Clara follow up the next day… try a few times to reschedule". Joint wording (this lane + 006): (1) the read answers "does a person at the office own this thread right now": yes while the newest office-side row is a staff message or note, a human-booked tour is upcoming or ended less than N hours ago, an office call is filed on the thread in the window, or a named assignee touched the card today; the never-released hold becomes this derived answer, no flag. (2) When a person owns it, Clara yields, never mutes: the yield opens a Clara-held follow-through item due the next business morning (sooner only if the ask is time-bound). At due the same read runs again: still owned, stay quiet and read again next morning; no longer owned and the renter is waiting, or a promised step (reschedule, link, answer) never happened, Clara follows up with her normal cadence (one reschedule try). The clock only wakes the read; the read decides. No thread is left without an open item after a yield. (3) No pushes while the office owns it. Known gap: office calls land on the thread 40 to 70 min after the call, so the read can be early by that much. Split: 006 owns the read and every caller of it; this lane feeds it human-booked tour rows and office-activity entries (with a filed-at time on calls) through the person picture only.
- C2. Should Clara ever message renters about tours staff booked? (a) build it dark, then turn it on per customer once their own AppFolio reminders are off. (b) never; she only stays out of the way. Recommend (a). Parked (Fede, Nov 4: no notifications for now; "what?" on the question): nothing changes tonight; re-asked after the silent tour row is live.
- C3. What Clara may use from staff notes (can wait until after step 3). (a) the facts that change what she says (time, host, code sent, link sent), never the note's wording. (b) only to decide whether to speak. (c) raw notes. Recommend (a).
The former five, and why only these three remain:
- #1 Staff-booked showing → tour row. Decided (a), Fede, Nov 4 evening: "we just need a concept of a human manual tour vs Clara booked tour somewhere"; "would we send notifications if we detect a manual staff tour? I don't think we should"; "let's start there". A showing a person books, moves or cancels becomes a tour row marked human-booked; it sends nothing (no text, no host note, no reminder, no calendar event); Clara stops offering times over it and stays out of the thread; her follow-ups pause while it is upcoming, and after it passes today's rules apply. No switch, because a read that sends nothing is a fix under the Sep 30 rollout rule; proving it sends nothing is our job. Building now as one PR.
- #2 Model read vs a list patch: decided by the Nov 2 "every message … reasoning" rule and the Nov 3 "systematic refactor" ruling.
- #3 No clock for picking up: decided Oct 16 and Oct 25 (both quoted in the code). Only "may she pick up at all" remains, and that is C1.
- #4 Walk-ins: not a decision. Decision record 0072 covers what Clara books for someone at the door, not importing a showing staff booked; noted here since decision records are retired.
- #5 What she reads from notes: real but deferrable; it is now C3.
6. How this relates to "Clara remembers unfinished business" (002)
Same record; not yet the same read. The follow-through brief keeps an open-item list per renter and asks one question at each due time: speak, acknowledge, or stay silent, while tracking the item. Today's replay showed that question is blind to office activity, which is why it talked over staff 7 times in 42. Office activity is the missing input to both: a staff showing or call opens or closes items the same way a staff text does. Follow-through does not call the one function today: it has its own company mode (default off) and its own read, and sends nothing yet. When it starts sending, it must ask the step-2 read first. Supervised follow-through at Western Slope and Situs does not wait on this; nothing sends unseen there.
Not verified by the independent pass: production settings (notes import on only at Sandbox; Situs's text setup); Norma's timing (the 40-minute call-filing lag, and whether her 17:20 prompt held the "showing scheduled" notice; the scrubbed fixture was used, not production rows); session 006's replay numbers (34 of 42; 32 of 36); the Situs data numbers (507, 36 of 47, 43 of 73, 15 of 111, 61 of 202); whether Willows is closed, the AppFolio request budget, and which Situs hosts have connected calendars. Fede's quotes were not re-read in raw transcripts, so every dated ruling is as cited by the page and the code comments.
Read first: where the two designs agree, and where they don't
They agree on the shape. One tour record whoever made it, with an origin, a host and a provider-prefixed showing id. One Temporal run per tour that can start with the state the tour already has; every step first asks "already past, or already done by someone else?" Showings booked in AppFolio become Clara tours and get her calendar event, reminders and after-tour steps. When a person answered the renter first, Clara stays quiet. The north star is all seven AppFolio automatic messages off, each switched off the day its Clara replacement is proven.
They agree on the data. At Situs a person booked every showing this year (Fable counts 390 since January in one source, Opus 495 in another; both say 100%). About six in ten were booked the minute the card was typed in. One booked showing in four was changed afterwards. Renters self-booking on a listing site this year: zero. Staff answered a prospect's text first in 36 of 47 threads.
| Where they differ | Fable | Opus | Recommendation |
|---|---|---|---|
| When Clara picks up a thread a person answered first | Stays quiet until the renter is "waiting on nobody" (no open promise from a person). | Waits 15 business minutes (5 after hours) with nothing promised, then picks up; company setting, default off. | Opus's timer: it is measurable and matches how the VA worked. Ship it as a setting, Situs on after a Willows run. |
| Asking the renter to confirm | Reminders ask for a "yes"; silence 30 minutes out tells the host once; no cancel (Fede's ruling today). | Clara asks only where AppFolio does not; otherwise she reads AppFolio's CONFIRM/DENY reply. | Fede's ruling as the behaviour; Opus's "don't double-ask while AppFolio still asks" as the guard until Situs switches AppFolio's confirm text off. |
| What sits behind a setting | Import itself ships as a fix; only the renter-facing messages on imported tours sit behind one company setting. | The whole import (staff, VA and listing-site bookings) behind one company setting, default off. | Opus: the import writes calendar events and texts people, so it is a new capability, default off, Situs on after Willows. |
| AppFolio's after-tour email on Clara's own tours | Reads it as a company-wide setting the robot cannot untick; proven double. | Adds the evidence: at Western Slope AppFolio sent "Thanks for visiting" under Clara's name for a showing we wrote (Oct 18). Situs card 9022 was read before the email was due. | Both right. The Willows proof run must check that email, not only the texts before the tour. Keep the second AppFolio ask in today's Situs email. |
| PR cut | 7 PRs plus the staff-move chain, ordered by the AppFolio message each retires. | 7 PRs in 3 groups: fixes with no switch, the import behind a setting, the replacements. | Opus's grouping with Fable's "which AppFolio message this retires" column on every PR. |
Decisions for Fede (Nov 2 list, superseded by the decisions in the Nov 4 one-pager)
TL;DR: 19 lines, one list for the whole initiative. Yes to all except 15 and 16 (hold).
- The tour run starts from any state and every step checks "already past or already done" first: ships as fixes, no switch. Recommend yes.
- Showings booked in AppFolio (by staff or the VA) become Clara tours within 15 minutes, behind one company setting, default off; Situs on after the Willows run. Recommend yes.
- On an imported tour Clara confirms only if AppFolio did not, puts it on the host's calendar unless an event already sits there, and tells the host once only when someone else booked it for them. Recommend yes.
- Clara picks up a staff-started thread after 15 business minutes (5 after hours) of the renter waiting, as a setting, default off. Recommend yes. (Hold means Fable's "waiting on nobody" rule instead.)
- Clara's after-tour follow-up replaces AppFolio's "Thanks for visiting" email, and renter replies to AppFolio's own thread are matched to the renter's conversation. Recommend yes.
- Listing-site bookings (TurboTenant, Tenant Turner) use the same import path, built last, since none happened this year. Recommend yes.
- Situs switches AppFolio's messages off in the order the tables below give, one note per switch, each after its proof. Recommend yes.
- Staff-booked showings: Turn-on day: Situs switches off AppFolio's renter showing messages (scheduled, updated, cancelled, reminder, after-showing email); the confirm request goes off when Clara's confirm ask (decision 11) is on. Recommend yes, or renters hear everything twice.
- Staff-booked showings: Renters already on the calendar get Clara's intro on turn-on day (6 open on the dump date). Recommend yes, never between 9 PM and 8 AM.
- Staff-booked showings: Ask Gera for one real-AppFolio run at the Willows (closed today). Recommend yes; Fairhaven proves only Clara's side.
- Confirm or cancel: Build it as a company setting, default off; turn on at Situs after a Willows run. Recommend yes.
- Confirm or cancel: Renter wording: "Please reply to confirm you're still coming, or we may need to cancel." (softer: "…so Tessa knows to expect you"). Recommend yes: Fede's wording; fair because nothing is cancelled. The sample below now carries this ask in the Situs team's own words (what the team's own texts show).
- Confirm or cancel: No reply by 30 min: one email to the host; the tour stays booked; nothing written to AppFolio. Recommend yes: Fede's Nov 2 ruling.
- Confirm or cancel: The fold: with the setting on, a confirmation sends the host nothing. Recommend yes.
- Confirm or cancel: Also drop today's "confirmed" text where the setting is off. Recommend hold: Western Slope hosts on texts get it today.
- Confirm or cancel: Auto-cancel at the deadline, AppFolio's way. Recommend hold until a month of data.
- Confirm or cancel: Setting name
tour.requireConfirmation, label "Require confirmation before a tour". It names the policy, not the message: a tour is only kept if the prospect confirms the day of; Clara asks, reads the reply, and acts (keep, move, or release the slot per the no-reply rule); the text is just how the policy is carried out. Recommend yes. - Confirm or cancel: Only Clara asks: before turn-on, check (and if needed untick) AppFolio's confirmation box on the showings Clara books, or renters get two asks and AppFolio cancels. Recommend yes, before turn-on.
- Confirm or cancel: Heads-up at 30 min, as built. 45 min gives a driving host more notice but flags more tours. Recommend yes; revisit with data.
Already decided today: no automatic cancel or release of an unconfirmed tour at launch; the host hears once, 30 minutes before, and the tour stays booked. A staff reply addressed to another staff member on a thread Clara is only copied on gets nothing from Clara (fix in flight).
Nov 4: Norma at Barcelona, and what the past rulings say
Added Nov 4, 2026 · Proposed, pending Fede's review · read-only, nothing built, no PR · gleaned from 2,392 docs pages, the repo, and every session folder (Fede's own words only; relayed quotes marked)
What happened. A walk-in at Barcelona (8121 W. 9th Ave, Situs). Deiby made her a 10:00 AM showing in AppFolio. At 10:19 she texted her email address, answering Deiby. At 10:20 Clara woke, asked whether she had made it to the 10:00 showing, and offered times between 11:30 and 4:45. At 10:23 to 10:33 Deiby sent the application link, the lockbox code for a self-guided visit that afternoon (in Spanish), and moved the showing to 6:30 PM. Nothing on main changed since Nov 1 (five live sessions polled). Thread cbbe25a6…; the card says Norma, Deiby writes to "Nataly".
The rulings that already exist (newest first)
| Date | Fede's words or ruling | Where | Bearing on this design |
|---|---|---|---|
| Nov 3 | Self-guided arrival texts wait until 8 AM property time; only emergencies go any hour | session 010 (relayed) | Codes on imported tours keep the same clock |
| Nov 2 | "we should always check roles"; every inbound message is reasoned by the model, never string-matched | follow-through brief, global rule | The wake gate must be a read of the card, not a list of sender kinds |
| Nov 2 | Rules R (read the renter's messages before any tour step) and H (the host is AppFolio's assignee) are "a new design", not decided | above | Still open; Norma needs both |
| Oct 21 | "why is there a switch for this? If they have AppFolio it should always be yes" (the Showings switch, deleted) | PMS conversations | Said about our tour → AppFolio. Decision 2 above puts the reverse direction behind a default-off setting; his words cut the other way |
| Oct 20 | "Just because we don't have a phone number doesn't mean we fail": the lockbox code goes out 1 h before on every channel | self-guided reach | Only runs for tours PropFlow holds; Norma's never existed |
| Oct 20 (#21315) | Clara wakes on a prospect reply unless a teammate spoke after her; AppFolio's automatic texts are notices, never a teammate (Oct 14, #19618) | messaging handoff | The live rule. Docs disagreed on whether template texts silence her; Norma shows they do not |
| Oct 16 | Clara claims the guest card only after her own message went out; "watch mode no, do not build new things" | lead-source gate | A staff-made card with a staff-made showing is not hers to claim |
| Oct 15 | "yes. if a human starts it we dont intervene" | renewals loop | Given on renewals. Applying it to showings is an extension, not a decided rule |
| Oct 14 | No fixed waiting time; one decision reads every signal on the card, showings included, before every send. "Either advance them to the stage or don't engage at all" | lead-source gate, Opus §6 | In code today the decision is logged, not acted on. A staff-made tour starts at "booked" |
| Oct 5 | A self-guided tour still books an appointment | session (relayed) | Deiby's lockbox visit is a tour, not a walk-in note |
| Sep 27 | Clara never stores a lockbox code as a property or knowledge fact | session 010 (relayed) | Codes are read from the card at send time |
| Sep 23 | Hand-booked showings put off "until a second hand booking"; Sep 22 on Clara backing off once Kat steps in: "something we haven't built yet" | Fable §6 | The second booking came Oct 16; still not built |
| Jun 30 | Decision record 0072: a walk-in is a lead source with no tour row, not a self-guided tour | repo docs/adr/0072 | Conflicts with importing a walk-in's showing as a tour; one must win |
What Norma changes in the decisions above
The design on this page already covers the import, the pick-up timer and the read-back. Norma sharpens five lines. Each is a yes / hold.
- N1. The import's switch. (a) Split by what talks. No switch: the tour row itself, visible to the availability tool and to the wake read, with no workflow start, no calendar write and no message to anyone (a tour Clara cannot see is a correctness gap). Behind the company setting: everything that speaks or writes a calendar on an imported tour (the intro, reminders, host notes, the event). (b) Decision 2 as written: the whole import behind the setting. Recommend (a): it matches the Oct 21 words and the rollout rule (a switch only for a new outbound).
- N2. The wake gate. The Oct 14 gate (one decision, every signal on the card) already names "a showing by a human actor" as the strongest sign that a person has the card, but it was designed and never built: no code on main carries it. (a) Build that gate and run it at wake time as well as at intake: the model reads the card (showings and who made them, staff touches, notices, whether the renter is answering a person) and decides speak / stay quiet / tell the host. (b) Extend today's sender-kind list with "a showing notice counts as the office". (c) As today. Recommend (a); (b) is one more hardcoded rule of the kind Fede ruled out on Nov 2.
- N3. The pick-up timer (decision 4) runs only on a thread where the renter is waiting on nobody, as read by the same model pass; a staff-made showing with a staff reply inside 15 minutes is never picked up. Recommend yes; this reconciles Fable's and Opus's versions.
- N4. Walk-ins. A walk-in whose staff booked a showing becomes a tour (Oct 5: a self-guided tour still books an appointment); decision record 0072 is updated to say so. Recommend yes.
- N5. Codes on imported tours. Clara sends the lockbox code 1 h before, from the card, unless the model read shows a person already sent it (Deiby did, in Spanish, at 10:32); never before 8 AM, never stored. Recommend yes.
Replay cases for the harness: Norma (Nov 4, cbbe25a6…, name mismatch and Spanish), Matthew (Nov 2), Deyci (Oct 30), Stephany (Nov 2). Mutation control: restoring today's list-based gate must turn Norma red. Nothing is built until Fede's yes; no PR, no branch.
Staff-booked showings become Clara tours
Today Clara never hears about a showing booked in AppFolio. Situs booked 507 there from January to early September, all by hand (383 by staff, 124 by the VA desk's shared seat). Proposal: every 10 minutes Clara reads the AppFolio showing calendar through the robot (read-only), makes each new showing her tour, introduces herself to the renter and tells the host. The same read catches AppFolio-side cancels and confirmations of Clara's own showings, missed today. POC, 16 real Situs showings plus 3 variants: main gets 3 of 19 right, the branch 19 of 19; 8 of 8 planted bugs caught; 0 sends.
| When | Clara does | Renter gets | Team gets |
|---|---|---|---|
| Staff book a showing in AppFolio | Reads it within 10 min and makes it her tour | A short intro: who she is, the time, the host | The host, one email, unless they booked it themselves. Nobody assigned: the booker is asked who shows it |
| Before the tour | The same timers as her own tours | Reminders the day before and 1 h before | The host hears "confirmed they're coming" 30 min before, when the renter said yes |
| After the tour | The same as her own tours | Application link 1 h after, then the normal follow-up | The host is asked for notes 30 min after |
| Staff move, reassign or cancel in AppFolio | Spots the change on the next read | Told by Clara, unless staff already told them | The host, once, unless they made the change |
| Renter replies to Clara | Handles it as her own tour | Normal Clara replies | AppFolio updated through today's write path |
Yes / hold: decisions 2, 3 and 8–10.
Full sub-design: where Clara reads showings, the record, matching, exact wording, AppFolio changes, edge cases, PR cut, POC results (click to open)
(a) Where Clara reads showings
Ranked by speed and trust; 1 and 2 together are the design.
| # | Source | Speed | Carries | Verdict |
|---|---|---|---|---|
| 1 | AppFolio's showing calendar: the read behind its Calendar page, one call per company, through a new read-only robot route | Every 10 min, window yesterday to 45 days out | Showing id, time, home, host, renter name, phone and card link, status (including "unconfirmed"), notes | use |
| 2 | The renter's guest card: today's card read, extended | One card per read | Adds email, who booked it and when, who cancelled. The only place that says "cancelled" | use to confirm |
| 3 | AppFolio's reporting API, showings report | Unknown | Not verified for our access | check later |
| 4 | The showings report export in AppFolio's screens | Hourly at best | Similar, brittle | fallback |
| 5 | AppFolio's "showing scheduled" email to the agent | Instant | Reaches only the assignee; Situs is turning notices off | no |
| 6 | AppFolio's own renter texts copied onto the card | Instant | Stop the day Situs turns them off | no |
| 7 | Listing sites' booked-showing emails (TurboTenant, Tenant Turner) | Instant | None in Situs's mail this year | later, same contract |
The calendar read is a POST, so the robot's write guard must list it as a read. About 100 light reads a day per company.
(b) The record and the contract
Listing-site bookings (TurboTenant, Tenant Turner) use the same record and the same entry point.
| Field | Example, as Clara stores it |
|---|---|
| Showing ref | appfolio:situsgroup:showing:3934, provider-prefixed, opaque to core code |
| Start, end | Sep 2, 4:45 to 5:00 PM Mountain, stored in UTC |
| Home | Property and unit refs, plus the label people see |
| Host | The name the PMS shows; matched to a person by roles in core |
| Renter | Card ref, name, phone, email |
| Status | Scheduled, cancelled or completed; renter confirmed yes or no |
| Booked by | Staff, a scheduler the renter used, or unknown; name and time |
| Recorded late; cancelled by | Yes when entered after it happened; staff or renter |
Two calls. List a window returns live showings only, so a missing row means "go look". Read one is the only thing that can cancel a tour. Only the AppFolio adapter knows AppFolio; the harness fails if the engine names a PMS.
Start from state: the existing tour workflow starts at "confirmed", with its time and host, so it gets the same timers as a Clara booking.
(c) Matching
| Question | Answer |
|---|---|
| Is it ours? | The showing ref is already on one of our tours: compare with what we last saw. Never a second tour. |
| Did Clara book it but lose the id? | Same renter card and within 30 minutes of an unlinked Clara tour: link it, no copy. The same 30-minute rule Clara uses before booking. |
| Known or new renter? | The guest card is the record. A known card is the existing person; a new card makes a person from the card's name, phone and email, never from a listing email. |
| Who is the host? | The PMS's name for the host, matched only against people at the company with a hosting role (admin, regional manager, PM, assistant PM, leasing assistant, leasing agent, maintenance). A vendor with the same name never matches; a POC case proves it. No host: the booker is asked. |
| Which home? | The PMS property id, through our PMS link. A home the company has not given Clara is recorded with no messages: the one place a building decides, because the action is about that building. |
(d) What Clara does
Exact POC text on real Situs showings (names are role labels):
| Booked by | The renter's middle sentence |
|---|---|
| A coworker | "Agent 2 set up your tour at Building 9 for Wednesday, September 2 at 4:45 PM with Agent 1." |
| The host, same day | "Agent 4 set up your tour at Building 32 for today at 11:00 AM and will meet you there." |
| The VA desk's seat (no person record) | "Your tour at Building 41 is set for Wednesday, September 2 at 9:00 AM with Agent 1." |
Email-only renters get the same words, ending "just reply to this email".
| When | What | Same as Clara's own tours? |
|---|---|---|
| On import | Intro by text, or by email when there is no phone | New wording |
| T−24 h, T−1 h | Reminders; any already past is skipped | Yes |
| T−30 min | Host hears "confirmed they're coming", only when the renter said yes, in AppFolio or to Clara | Yes |
| Confirm-or-cancel setting on | Whatever it does for Clara's tours | Yes |
| T+30 min, T+1 h | Host asked for notes; application link, then the follow-up cadence | Yes |
Host calendar, no double: if something is already at that time, Clara adds nothing; an empty slot gets the event she adds for her own tours. Staff notes go by email (Situs has no text recipients).
(e) Changes made in AppFolio
| In AppFolio | Clara | Renter | Host |
|---|---|---|---|
| Time moved | Moves the tour, re-plans reminders | "Your tour is confirmed for Tuesday, July 28 at 3:00 PM…", today's staff-move text, unless someone told them in the last 30 min | Told once, unless they made the change |
| Host changed | Updates the host | Nothing | New host told; old host gets one line; nobody told about their own change |
| Renter confirmed (AppFolio's confirm request, live today, also on Clara's bookings) | Stamps the yes | Nothing | "Confirmed they're coming" at T−30 |
| Cancelled by staff, or the renter answered DENY to AppFolio | Cancels only after the card read says so; stops reminders | Told, unless they cancelled or were already told | Told once, unless they made the change |
| Missing from one read | One card read; nothing changes unless it says cancelled | Nothing | Nothing |
Already told: a staff text or AppFolio notice on the card in the last 30 min that names the new time (or the cancel) keeps Clara quiet. Staff can also email Clara to move or cancel.
(f) When the renter replies
"Can we do 5:30?" or "I can't make it" is handled like any Clara tour, and AppFolio is updated through today's write path. The next read sees her own change and does nothing: no second write, no second message.
(g) Edge cases
| Case | What Clara does |
|---|---|
| Already over (recorded after the fact; 24 at Situs this year) | Ended under 24 h ago: a past tour, no intro, no reminders; notes ask and application link still run. Older: recorded only. |
| Starts within the hour (16 of 507) | Intro says "today at 11:00 AM"; passed reminders skipped; after-tour steps run |
| No phone | Intro by email; no email either: the booker gets one note |
| Supervised mode | Renter messages wait for approval like every Supervised message; staff notes still go |
| Quiet hours, 9 PM to 8 AM (4 of 507 booked then) | Text held to 8 AM; if the tour starts before then and there is an email, it goes by email |
(h) PRs, order and proof
| # | PR | Switch | Size |
|---|---|---|---|
| 1 | Robot: read-only showing-calendar route; the card read adds status, confirmed and cancelled-by. Sandbox PMS serves the same list for Fairhaven | None, read-only | ~250 lines |
| 2 | Clara's own showings: AppFolio-side cancel, confirm and move are caught. Neutral record, adapter, the 10-minute read, compare, echo guard | None: a fix, live everywhere | ~550 |
| 3 | Hand-booked showings become Clara tours: import, host by roles, card as the record, start from state, past, within-the-hour and quiet-hours rules | Company setting, default off, on the company page; Dark line | ~500 |
| 4 | Messages for imported tours: renter intro, host and booker notes, the calendar event | Rides PR 3's setting | ~400 |
This week, targets: Tue PRs 1 and 2 · Wed PR 3 · Thu PR 4 and the Fairhaven run · Fri the read-only replay, then turn-on at Situs on Fede's yes, AppFolio's renter showing messages off the same day.
| Rung | Proof |
|---|---|
| Replay | The 10-minute read run against Situs's and Western Slope's live calendars, read-only, decisions logged, nothing sent; checked against what staff actually did |
| Fairhaven | Situs's centralized shape on the sandbox PMS: book, move, reassign, cancel; test phone and test mailbox; record ids as evidence |
| Real AppFolio | One hand-booked showing at the Willows, moved then cancelled in AppFolio (needs Gera to reopen it) |
| Supervised first week | Situs, setting on, AppFolio's renter messages off |
| AppFolio sends today | Clara instead | Off at Situs? |
|---|---|---|
| "Your showing has been scheduled / updated / canceled" texts | Intro, move and cancel texts | Yes |
| "Please confirm your showing" (also on Clara's bookings) | The confirm ask (Confirm or cancel) | When the confirm ask is on |
| Showing reminder | Reminders at T−24 h and T−1 h | Yes |
| Automatic after-showing email (278 this year) | Application link and follow-up | Yes |
| Current-tenant showing notice | Nothing yet | Keep on; a gap for a later proposal |
(i) POC results, Nov 2
| Case (real Situs showing unless marked) | Main today | Branch |
|---|---|---|
| Coworker booked it for the host, 2 days out | nothing happens | intro, host note, 4 timers |
| Same, company in Supervised mode (variant) | nothing | intro held, note sent |
| The VA desk's shared seat booked it | nothing | intro names no booker |
| Moved twice, host changed, renter confirmed in AppFolio | nothing | quiet: AppFolio already told |
| Same, AppFolio's texts switched off (variant) | nothing | Clara tells each move |
| Host changed only | nothing | both hosts told |
| Staff cancelled and told the renter themselves | nothing | card read, cancel, quiet |
| Renter cancelled a Clara-booked showing in AppFolio | reminders keep going | cancelled, host told |
| Renter confirmed a Clara-booked showing in AppFolio | host never hears | yes stamped, T−30 note |
| Booked about 30 minutes before the start | nothing | "today at 11:00 AM" |
| Booked at 9:55 PM | nothing | intro held to 8 AM |
| Renter has email only | nothing | intro by email |
| Nobody assigned | nothing | booker asked |
| A home Clara doesn't run | right | right |
| Clara's own showing comes back in the read | right | right |
| Clara's write landed but lost the showing id | right | right |
| Recorded after it happened | nothing | no intro; notes ask, app link |
| Showing drops out of one read | nothing | one card read, no cancel |
| Renter moves the tour with Clara (variant) | nothing | one write back, no echo |
| Planted bug | Caught by |
|---|---|
| Clara re-imports her own showing | 9 cases |
| No same-card, same-slot link | 1 |
| A missing row treated as a cancel | 3 |
| No echo guard | 15 |
| Quiet hours ignored | 1 |
| Reminders sent for a showing already under way | 7 |
| Homes Clara doesn't run not skipped | 1 |
| Host matched by name only (picks the same-name vendor) | 9 |
Not in the POC: the robot route (dump rows stand in), the calendar event, real sends, Clara's free-text replies, and the workflow itself (timers modelled from its constants).
Branch fede/poc-staff-booked-showings (local, never a PR) at 575621f815: npx tsx scripts/coworker-harness/staff-booked-showings/run.ts; red run SBS_ARM=today SBS_STRICT=1 fails 16 of 19.
Sources and caveats
- Situs robot dump on disk (calendar feed and card activity, Jan 2 to Sep 4, 2026), no AppFolio login: 507 bookings, 79 moves, 66 staff and 53 renter cancels, 259 confirmations.
- "Assistant Lead" is one of three VA seats, a shared AppFolio login (Situs inbox study), not a bot.
- Fixtures scrubbed (role labels, last-4 phones, fake card ids) and hash-locked.
- The POC calls main's real slot rule, echo check, quiet-hours gate and staff-move text (
18411eb637).
Confirm or cancel
A new company policy for tours Clara runs, "Require confirmation before a tour": a tour is only kept if the prospect confirms the day of; Clara asks, reads the reply, and acts (keep, move, or release the slot per the no-reply rule; at launch the no-reply rule is keep the tour and tell the host, and releasing is decision 18, on hold). The text is just how the policy is carried out. The reminder an hour before a tour asks the renter to confirm. Any reply counts, text or email. If the renter has sent nothing by 30 minutes out, the host gets one email: not confirmed, still booked, a quick call might help. A confirmation sends the host nothing, so today's "confirmed" note folds in. Nothing is cancelled and nothing is written to AppFolio.
Full sub-design: timings, messages, edge cases, PR cut, harness results (click to open)
The plan in one table
| When | The renter | The host | The tour |
|---|---|---|---|
| 1 hour before | The reminder adds the ask (skipped if they already confirmed) | Nothing | Booked |
| They reply, any words, text or email | Clara answers as today | Nothing (the fold) | Booked |
| 30 min before, silent since the ask | Nothing extra | One email (a text too only if opted in): hasn't confirmed, still booked | Still booked; nothing to AppFolio |
| A texted yes after that | Clara answers as today | "Confirmed after all", once | Booked |
| Setting off | Today | Today | Today |
What the team's own texts show
Every day-of confirm text in the Situs mirror, Sep 29 to Nov 2 (356 conversations read; full table in the session notes). Most asks are AppFolio's standard text sent under an agent's name; a few the team typed. Showings staff book in AppFolio have no tour row on our side, so attendance comes only from the thread.
| What the records show | Count |
|---|---|
| Confirm asks: AppFolio's standard text ("Please confirm your showing: … Must reply with CONFIRM or DENY by …"), Sep 30 to Oct 30 · typed by the team | 62 · 12 |
| When the standard ask goes: before the tour, median (range) · its deadline | 2 h 4 min (49 min–2 h 9 min) · 1 h before, 61 of 62 |
| Standard asks answered before the tour · before the deadline | 44 of 62 · 34 |
| Reply speed, median (41 distinct replies) | 14 min |
| Replies: the word CONFIRM · another yes ("Ok", "Yes", "Y", "I will come on time") · cancel or DENY · reschedule or late · other | 22 · 8 · 5 · 3 · 3 |
| Plain-words yeses that got AppFolio's "Your showing has been confirmed" within seconds | 8 of 8 |
| Typed asks answered: first asks · nudges after silence | 1 of 2 · 3 of 10 |
| No reply before the tour (18): staff nudged · cancel notice after the deadline · moved by staff · agent went anyway · renter turned up unconfirmed · nothing further in records (one showing can sit in two groups) | 6 · 1 · 2 · 1 · 2 · 5 |
| Tour outcome rows for these showings | not in records |
Read it as: the standard ask gets the replies (44 of 62); the typed ones are too few to rank. The team's own words, when they type, carry the same two parts: the word CONFIRM and a "no later than" time. Silence rarely ends in a cancel in these records: one cancel notice in 18 unanswered asks, and one agent who went anyway. AppFolio's help describes an automatic cancel at the deadline; whether it is on at Situs is not in records. Agents also tell renters ahead: "an hour or two before the showing you will receive a text message from us to confirm."
Where AppFolio's ask comes from (read Nov 2, look-only)
AppFolio has no company switch for the CONFIRM/DENY text. It is a box on each showing ("Request confirmation by Prospect: Text"), ticked by default on the blank form, sent as the assigned agent about 2 hours before with a 1-hour deadline. Every showing a person booked Oct 12 to 30 had it on; the only two since (Patricia Oct 31, Stephany Nov 2) were booked by Clara, and our robot creates and moves every showing with that box off (unchanged since Sep 22). That is why the template stopped and the showing host typed the ask by hand. AppFolio does not cancel on silence: past showings still sit at "confirmation pending". The renter's reply lands in our thread through the mirror. The Showings Settings page itself is not readable by the Clara login.
| Tour booked by | AppFolio asks? | What Clara does today | Under this design |
|---|---|---|---|
| Clara (robot writes the showing) | No, box off | 1-hour reminder, no ask | Clara asks at 1 hour, reads the reply or silence, acts |
| A person, in AppFolio | Yes, 2 h before, 1 h deadline, no cancel | Nothing (no tour on our side) | Never a second ask: read AppFolio's ask and the reply or silence, act (needs the staff-booked-showings import and the external-updates step; a DENY is a cancel to reflect) |
Open for Fede: (a) keep the robot's box off and let Clara own the ask on her tours (recommended: one voice, one deadline, action on silence); (b) tick the box on Clara's showings and have Clara only read the reply (two senders on one thread, no action on silence).
What others do
| Who | When they ask | No reply | After a no-show | When staff text the prospect |
|---|---|---|---|---|
| EliseAI | Confirms at booking. Reminder 23 h before if booked more than 2 days out, 3 h before if booked 1 to 2 days out, none if same-day (vendor claim) | A reminder call; never cancels (vendor claim) | Text 2 h after a missed tour: "Were you able to chat with our leasing agent? If not, I'd be happy to get you rescheduled…" (vendor claim) | Elise keeps running; turning her off is a deliberate step with a reason and an end date (vendor claim) |
| Showdigs | Confirm about 4.5 h before, reminder about 2 h, final 45 min (vendor claim) | Auto-cancel (vendor claim) | Not stated | Not found |
| ShowMojo | 2 h reminder, up to 3 confirm attempts (vendor claim) | Auto-cancel if enabled (vendor claim) | Agent marks the no-show (vendor claim) | Not found |
| AppFolio standard ask at Situs | About 2 h before, deadline 1 h before | No auto-cancel seen in records | Not in records | Not in records |
| Clara (proposed) | Ask 1 h before, deadline 30 min before | Keep the tour, tell the host | Not part of this decision | Today a "human replied" hold silences her |
EliseAI keeps working when a staff member texts the prospect, while Clara's "a human replied" hold silenced her on Stephany's "switch to 3pm" text today (a staff member answered by hand). Sources: support.meetelise.com/hc/en-us/articles/32499730992653, showdigs.com/property-managers/showing-confirmations-reminders-showdigs, showmojo.helpjuice.com/the-details-behind-automated-showing-confirmation.
What the renter and the host see
What the renter sees (the one-hour reminder, from the harness):
Team's own wording, 12 real typed texts (plus 62 AppFolio standard asks sent under their names), Sep–Nov 2026: "Hello [renter]! Please confirm your scheduled showing by replying CONFIRM to this message no later than 1:25 PM so we can ensure someone is available to meet you and show you around the property. Thank you!" (an assistant property manager, Nov 2).
What we change on purpose: Clara reads any reply as the answer, in any words or language (a yes, a "can we do 3 PM", a cancel), not only the word CONFIRM; AppFolio's text missed a Spanish "Si" and stamped "Can I go at 3:20" as confirmed.
What the host sees if Rosa stays silent:
Rosa hasn't confirmed the 3:30 PM tour; no reply to my reminder an hour out. It's still booked. Might be worth a quick call before you head out.
How often (estimate): Situs renters answered AppFolio's ask within 30 minutes 26 times of 44 answered asks, within 90 minutes 39 times (62 asks, Sep 30 to Oct 30). At that pace, asking at one hour flags about 36 of 62 tours (58%); asking at two hours, about 23 (37%).
Folding in today's "confirmed" note
Today
Rosa confirms: the host gets "Rosa F. confirmed an hour out" by text only (Situs: never arrives). Rosa is silent: the host hears nothing.Setting on
Rosa confirms: the host hears nothing. Rosa is silent: one email. A late texted yes: "confirmed after all". The host hears only about tours that need her.With the setting off nothing changes, including today's text (decision 15).
The setting
Company switch tour.requireConfirmation (POC: tourConfirmBeforeDriving), default off, on the company settings page. The screen is Gera's lane; the PR ships the key, resolver and API. Dark line:
Dark: tour.requireConfirmation | feature: renters asked to confirm in the one-hour reminder; the host is emailed once 30 min out if they haven't | proof: pending | turn-on: Fede
Edge cases
| Case | What happens |
|---|---|
| Booked inside the hour | No reminder, so no ask and no heads-up |
| Already confirmed (e.g. the day before) | No ask in the reminder; the host hears nothing |
| Reply after the heads-up | A texted yes: "confirmed after all". A reply in other words or by email: the host is not told yet (gap, closed in PR 2) |
| Virtual and self-guided tours | No ask, no heads-up: nobody drives |
| Showings staff book in AppFolio | Never imported today, so Clara never reminds or asks; AppFolio's own text still applies there |
| Supervised mode | The reminder waits for approval, so nobody was asked and no heads-up fires. Finding: when a heads-up did fire in Supervised (one planted-defect run), the email was held for review, against Fede's Oct 16 rule that staff notices flow. Check on main. |
| Quiet hours | The ask rides the existing reminder, whose send path already applies quiet hours; the heads-up goes 30 minutes before a tour, inside working hours |
| Weekends | The weekend check-in replaces the one-hour reminder, so no ask goes. Its own heads-up is text-only, so Situs gets nothing: fixed in PR 3 |
| Renter with no phone | The emailed reminder carries the same ask; an email reply counts |
| Host with no phone (all of Situs) | Email is the default channel, not a fallback |
| The check runs twice | Still one email: its identity is the booked time |
PR cut and proof
| PR | What | Kind | Size |
|---|---|---|---|
| 1 | The switch (key, resolver, API, row on the company page) and the ask in the one-hour reminder, English and Spanish | new, dark | ~150 |
| 2 | The 30-minute heads-up (email first), reply-since-ask, the fold, "confirmed after all", any later reply passed to the host; plus row 6's honest "I'm here" words | new, dark; the "I'm here" words are a fix, live everywhere | ~300 |
| 3 | Weekend heads-up by email too; an emailed yes counts as a confirmation | fix, no switch | ~150 |
| later | Auto-cancel at the deadline, only on Fede's yes | new | ~100 |
Source lines, estimated from the POC; written against main, which the POC branch is behind.
Proof ladder (each rung with record ids):
- Read-only replay of Situs's last 60 days of showings: who would be asked and flagged, next to what staff did.
- Willows live run on Fede's go, a test phone and mailbox as the renter: silent, yes, late yes, email-only.
- Sandbox company in Situs's shape (email-only hosts): the same four runs.
- A supervised week at Situs after turn-on.
Harness results
23 cases through Clara's real reminder, heads-up and "I'm here" code, today and on the branch. 9 come from 6 real Situs cases; the rest are their edges.
| Group | Today's code | POC branch |
|---|---|---|
| "I'm here" from the curb | 0 of 3 | 3 of 3 |
| Confirm flow (silent, voicemail, late yes, yes in words, "on my way", email-only both ways, run twice) | 0 of 9 | 9 of 9 |
| Controls (setting off, confirmed with it on and off, day-before yes, self-guided, virtual, booked late, Supervised, two "I'm here") | 9 of 10 | 10 of 10 |
| Known gap: after a heads-up, an "I'm here" reaches the host only, not the office | 0 of 1 | 0 of 1 |
| Total | 9 of 23 | 22 of 23 |
| Planted defect | Cases that went red |
|---|---|
| Clara says "they'll be ready for you" again | 3 |
| "I'm here" words ignore the heads-up | 1 |
| No "confirmed after all" | 2 |
| Heads-up though nobody was asked | 1 |
| The reminder drops the ask | 11 |
| The fold is lost: a confirmation texts the host | 1 |
| The deadline cancels the tour (AppFolio's way) | 6 |
| Only a stamped yes counts as a reply | 3 |
| A retried check emails the host twice | 1 |
| Heads-up by text only, like today's weekend one | 6 |
| The heads-up promises to pass on any reply | 1 |
Not proven here: no live run; Clara's own replies and the yes/no reader were not run (each case sets whether a reply was understood as a yes); the workflow's timers are emulated; whether AppFolio ticks its confirmation box on showings Clara books is unverified.
Sources and caveats
- Situs: AppFolio's texting log, Sep–Oct 2026, read-only. Follow-ups: 35 "confirmed", 6 "canceled", none to the 17 silent renters. 56% and 39% are estimates.
- The hub's 66 staff / 53 prospect cancels (884 cards) are a separate count, not re-checked.
- No phone on file for 8 of 8 active Situs staff: another session's read, Nov 2.
- AppFolio help articles: Schedule Showing notifications; showing settings.
- Fede, Nov 2, on the deadline: "releasing the slot 30 minutes if nobody answers, I don't know if we should launch like that… Maybe the design is instead we email the person that's doing the tour."
- POC (local, no PR): branch
fede/poc-va-arrival-confirm, code8d2837a8cc, results0b94bb599c, red on base1eb03bef13, harnessscripts/coworker-harness/va-ways-of-working/__tests__/arrival-confirm.harness.ts.
When the PMS changes: one mechanism for outside updates
Related design, owned elsewhere: Clara re-engages when a human drops the ball (speak / stand down / re-engage when a teammate takes a thread and goes quiet, plus the team's daily list of items nobody is on; Proposed, nothing built). Split: this page owns the PMS inputs that read needs (current assignee, showing status, staff-booked tours created from the PMS, tour-row corrections); that page owns the rule and its proof; the Clara Refactor lane owns the shared context loader.
Proposed — pending review Nothing is built from this page. First consumer, in progress: a showing a teammate moved, read from their reply and checked against AppFolio; cancels wait for this design.
We write back to the PMS, but also if the PMS changes we should reflect that on our side. Two-way syncs are risky, so this is two one-way flows with one owner per field: Clara's changes go out through the outbox; PMS facts come back through one reconciler.
The rules
- One owner per field. The PMS owns its facts; Clara owns her messages, notes, reminders and the confirmations she collected. The owner wins; no approval button.
- Last-seen mirror. A change is "the read differs from the mirror", never "the newer clock".
- Once per fact. One keyed Temporal signal per change, also during a "human replied" hold.
What each change does
| Change in the PMS | Real case | Today on main (d95131e) | What the engine does |
|---|---|---|---|
| Showings | |||
| 1. Staff move one | Stephany, Nov 2: 2:00 to 3:00 PM, at her request | Showings are read only just before Clara's own create or move; nothing updates our tour (domain/pms/guest-card-outbox-drain.ts:3181-3191, 3362-3375). Fixed by hand | Move the tour, tourRescheduled; reminders follow |
| 2. Staff cancel one | Diego, Maria, Lynette, Oct 21 to 28 (no Clara tour) | Nothing; the stuck-slot sweep only alerts (app/api/cron/stuck-tour-slot-sweep/route.ts:2,12) | A card read confirms, then tourCancelled; host told once |
| 3. Staff rebook | Diego, Oct 28: 4:30 PM cancelled 12 min before; another home next morning, booked 22 min later | Nothing | A cancel and a new showing on the same card within about 25 min, in either order, are one move (tested) |
| 4. Staff mark shown or no-show; renter answers AppFolio's CONFIRM/DENY | Patricia: still "scheduled" after her 11 AM tour, CRM status "completed". Deyci: "prospect confirmed" | Our showing statuses are scheduled, canceled, no_show, other (domain/pms/guest-card-writer.ts:514) | The adapter maps every raw status to ours; a DENY is the renter cancelling; "completed" set by AppFolio's clock is not a visit |
| 5. Staff book one directly | Deyci, Oct 30 (POC below); 27 in 14 days | No tour row | Create the tour: the import in Staff-booked showings, behind its setting |
| Guest card | |||
| 6. Staff reply to the renter | 43 of 73 Situs threads active today were on "human replied" hold | Mirrored within about a minute (pms/pms-inbox-watch-job.ts); Clara stays quiet | The model reads it; if a fact changed, read the card. Runs on hold |
| 7. Staff edit contact | Jensen C.: three texts to a dead number | Card read every minute, but contact only fills empty fields (domain/pms/writers/guest-card.ts:371-372) | The PMS value is added; the channel the renter replies on stays first |
| 8. Staff close as lost | — | Status copied only onto an untouched new prospect (writers/guest-card.ts:76-77) | Stop follow-ups; an open tour follows its showing |
| 9. Staff merge two cards | — | Our own merges are followed (writers/guest-card.ts:2183); AppFolio's: unverified | Follow the surviving card |
| Unit | |||
| 10. Leased, held or off market; rent, special or available date changes | — | Read every 5 min (lambda/appfolio-sync/handler.ts:5954) | Stop offering it; correct a quote Clara gave in an open thread |
| Application and lease | |||
| 11. Application status; lease signed or move-in set; cosigner or roommate added | — | Read every 5 min (domain/pms/writers/rental-application.ts:269) | Same event shape into the existing flows |
| Every change | |||
| Owner of each field | The PMS for every fact it holds. Clara for her messages, notes, reminders and the confirmations she collected. | ||
| How fast we see it | About 1 min when a signal fires; 5 min for a tour due within 48 h; 10 min through the calendar read; 24 h at worst. | ||
| Clara said something now wrong | A new outbound behind a company setting, off by default, never at a Preview or Off building. With it on: one correction per fact, never twice, and none if a person told the renter from 30 min before to 3 min after the change (E2). | ||
How we notice: do we poll?
Yes, targeted, signals first. Cards, units and applications are already read every 1 to 5 minutes; showings are not.
| Path | AppFolio | Yardi (Yale 25) | Delay | Reads an hour, Situs (requests) |
|---|---|---|---|---|
| Signals wake a read (first) | AppFolio's notice email and text, and staff texts, reach our thread through the inbox mirror in 26 to 66 s (7 measured). Notices fire only on changes a person makes (our robot writes with them off) and stop if Situs switches renter notices off (decision 8) | None known: no webhooks for a browser login, no texting mirror | ~1 min | 1 read (3 requests) per change, ~2 a day |
| Targeted poll (the floor) | Calendar read every 10 min (route not built) plus one card read every 5 min per tour due within 48 h | CRM IQ calendar through the browser agent, planned every 15 min; later the API's guest-activity call by date window (no "changed since") | 5 to 10 min (Yardi 15) | 6 + 12 = 18 (42) today; ~54 (150) with staff showings imported |
| Nightly sweep | Calendar window plus every open card; counts drift | The same window read | up to 24 h | ~20 a night |
| no Flat poll | Every open card every 5 or 15 min | Same, heavier in a browser | 2.5 / 7.5 min average | 204 / 68 (612 / 204) |
Requests measured on Situs, Nov 2: 1 per showing read and 3 per card read (plain requests); a full card page in a browser takes 15 to 16. The earlier "37 a page" had no source. Method: Red team, Nov 2.
Four decisions for this design (E1 to E4)
- E1. Staff change a tour's time or status in the PMS: (a) PMS wins; (b) Clara wins; (c) latest wins. Recommend (a). Yes / hold.
- E2. Staff move or cancel a tour Clara confirmed. Telling the renter is a new outbound message, so it sits behind a company setting that is off by default and never sends at a Preview or Off building. With the setting on: (a) Clara sends one correction, unless a person told the renter between 30 min before and 3 min after the change. She waits about 3 min, then checks again. The model judges whether the renter was told. Who sent the message comes from the sender's identity, never a signature, and an unknown sender or a failed text never counts. (b) Nothing. (c) Staff approve first (a button we won't build). Recommend (a), with the setting off until you turn it on per customer. Yes / hold.
- E3. Freshness: (a) signals at once, calendar every 10 min, tours due within 48 h every 5 min, nightly sweep; (b) every open card every 5 min; (c) nightly only. Recommend (a). Yes / hold.
- E4. Both sides changed one field between reads: (a) owner wins, plus one correction if the renter saw the other value; (b) a person resolves it; (c) latest wins. Recommend (a). Yes / hold.
Two new rules, Proposed (found on Stephany, Nov 2)
Proposed — pending review Fede, Nov 2: "a new design". Nothing is built and no PR or branch is open for either. Both go through the battle test and Fede's review with the rest of this section.
R. Clara reads the renter's recent messages before any tour step. Before a reminder, the after-tour "how was it?" ask, the application link or a chase, Clara reads the renter's messages since the tour was booked or last moved (a thread on "human replied" hold included) and decides: go ahead, skip, or tell the host. Today none of these steps reads the thread, so Stephany's 2:32 PM "I'll have to reschedule" went unseen.
| Option | What it does | Cost |
|---|---|---|
| (a) Before every tour step | Catches a cancel or a move in her own words, wherever it lands | One model read per step, a few per tour |
| (b) Only before the after-tour steps (ask, link, chases) | Fixes Stephany's case; reminders stay blind | Fewer reads |
| (c) As today: trust the tour row and the hold | Nothing to build | Wrong asks, a link and chases for a tour that did not happen |
Recommend (a). Yes / hold.
H. The host follows the agent assigned in the PMS. When staff book or edit a showing, the person assigned in AppFolio becomes the tour's host for the calendar, notices and the after-tour ask. Stephany: a different host took the showing at 1:15 PM; our tour still names Deiby.
| Option | What it does | Cost |
|---|---|---|
| (a) Assignee is the host, on import and on every later edit | Notices reach whoever actually shows the home | Assignee mapped to a person by role, else the company rotation |
| (b) Assignee only when the showing is first imported | Simpler | Misses same-day handovers like Stephany's |
| (c) Clara's rotation decides; assignee ignored | Nothing to build | Wrong person asked, as with Stephany |
Recommend (a). Yes / hold.
What we will not do
- Write both systems at once, or let the latest change win.
- Treat an AppFolio email as the fact, or skip echoes by time window.
- Poll every card on a flat timer, or add a "confirm this change?" button.
Where the code goes
- Adapter: neutral change events (kind, source, provider-prefixed id, actor's roles); core never names a PMS. The first consumer already uses this shape.
showing-sync-stream-consumer.tsis not the seam: it only turns our tour changes into PMS writes; it should skip origin-externalones (no check today).
POC, Nov 2: Stephany, Deyci, Matthew, and every showing Situs cancelled (read-only; click to open)
Case 1: Stephany's move
Mountain time. 8:34 AM Clara booked 2:00 PM and wrote showing 4082. Noon: the pre-tour check took AppFolio's own calendar copy of this showing for a conflict and only noted it (fixed separately, #24585). 1:00 PM reminders. 1:08 PM Situs called; voicemail full. 1:11 PM she asked for 3:00; Clara stayed quiet (the thread was on "human replied" hold). 1:14 PM the staff member agreed by text. 1:16 PM AppFolio's "Showing Updated" email and text, in our database at 1:16:59. 1:29 PM a person moved our tour.
| At 1:17 PM | AppFolio (showing 4082) | Our tour | Proposed |
|---|---|---|---|
| Start | 3:00 PM (−07:00) | 2:00 PM | 3:00 PM, origin external, history "moved in the PMS by staff" |
| Status | scheduled | confirmed | No change |
| Signal | The hand fix sent tourRescheduled at 1:29:03 PM (workflow history) | tourRescheduled at about 1:17 PM, fact key = showing 4082 + start + mirror revision | |
| Renter | Already told twice, by the host and by AppFolio | Nothing | |
| Write-back | The hand fix pre-seeded a preview-only reschedule_showing outbox row (1:29:01 PM) so nothing went back | Not needed: origin external is skipped, and since a fix merged at 2:08 PM Nov 2 the outbox reads the card first and writes nothing when the showing already matches | |
Without the hand fix, the timers would have fired on the 2:00 PM clock: host note 1:30, staff check-in 2:30, "slot passed" and the application link at 3:00 PM, the minute she arrived (inferred from her original timers and Patricia's workflow history). At 2:32 PM she asked to move again; the thread was still on hold when read at 2:34 PM.
Found on the way (3:45 PM, Nov 2): the after-tour "how was it?" email was wrong twice. Clara emailed the original host only (no copy to hello@, as intended since the 12:47 PM change that day) "How was the 3:00 PM tour with Stephany M. at Lampliter?" at 3:45 PM, on time (slot end 3:15 + 30 min, re-based by the move). But (1) no tour happened: at 2:32 PM Stephany texted she had to reschedule, and at 2:49 PM staff offered tomorrow; AppFolio's "Showing Occurred" at 3:00 PM is its automatic start-time stamp. (2) the reassigned host, not the original one, held the showing: the 1:15 PM AppFolio edit also changed the assigned user from the original host to the reassigned one, and our tour still names the original host. Cause: the check-in reads only the tour row and the host's own staff thread; it never sees the renter's thread (on "human replied" hold, so Clara missed the 2:32 PM text) or the showing's assigned user. What would catch it: two new rules, R and H above, both Proposed and not built.
Case 2: Deyci, booked by staff directly
Oct 30, Mountain time: 8:51 AM Clara offered 1:30 or 1:45 PM. 12:54 PM a Situs agent called and booked 3:30. AppFolio's two notices reached our thread 26 and 31 s later. She confirmed on AppFolio's text at 1:30 PM and toured (AppFolio's thank-you for visiting, Oct 31). Background: Situs launch log, touring gaps.
| AppFolio (showing 4079) | Our side | Proposed | |
|---|---|---|---|
| Tour | Oct 30, 3:30 PM (−06:00), "prospect confirmed", CRM status "completed" | No tour row; prospect still at "inquiry"; thread on hold | Create the tour (origin external, booked by staff); prospect to "toured" |
Case 3: Matthew, booked by staff four minutes after Clara wrote
Nov 2, Mountain time: 3:13 PM a Zillow tour request; 3:14 PM Clara emailed and 3:17 PM texted, offering 4:15 to 4:45 today or tomorrow afternoon. 3:19 PM a leasing staff member booked 3:30 PM with the showing agent. AppFolio's "scheduled" email and text reached our thread 33 to 38 s later. No confirmation was asked: the box was off and AppFolio sent no CONFIRM/DENY text, so there was no reply to read.
How it was booked: not visible. Neither AppFolio nor we hold a renter reply, call, text or email before the booking; no Situs call or text conversation in our tables in that hour (phone-carrier logs not checked); the office call recordings we hold stop at about 1:13 PM. Likely a call to the office line or a walk-in; unconfirmed.
| AppFolio (showing 4084) | Our side | Proposed | |
|---|---|---|---|
| Tour | Nov 2, 3:30 PM (−07:00), booked 3:19 PM by staff; confirmation box off; status "completed" from 3:30 PM, set by AppFolio's clock | No tour row; prospect still at "inquiry"; thread not on hold, Clara's 4:15 offer still open | At about 3:20 PM, create the tour (origin external, booked by staff); no second ask; Clara's open offer replaced, not repeated |
Every showing cancelled in AppFolio, Oct 19 to Nov 2
Situs numbers used (2:34 PM Nov 2): 3 tour rows, 1 due within 48 h; 17 open guest cards with a conversation, 9 active today; 73 threads active today, 43 on "human replied" hold; about 2 staff-booked showings a day. Reads an hour: today 6 calendar + 12 for one hot tour = 18 (1%); with staff showings imported, about 4 hot tours = 54 (4%); a flat poll of all 17 cards = 204 every 5 min (14%) or 68 every 15 min (5%). Robot reads have no documented cap (logins do: 3 an hour), so the budget used is AppFolio's report limit, 1,440 an hour per database.
How it was read. The 2 tours linked to AppFolio (Patricia 4081, Stephany 4082) were read from AppFolio through the robot's warm session: GET only, one card page plus 3 card reads of 1.9 KB. Both "scheduled", times match ours. Situs has 3 tour rows; the third (Michael, Oct 29) was cancelled on our side and has no showing. The 27, 3 and 1 come from AppFolio's own notices stored on 34 of the 496 Situs threads active since Oct 19, counted once per home and time; the notices were sorted by text matching for this count only. In the design a notice only wakes a read.
Guards and records. Every script ran inside a guard that blocks all non-AWS network calls and all database writes (0 blocked). The card page tried 5 writes (mark as read, an AI text draft, a conversation, 2 telemetry); all were aborted. Temporal: describe, query and history only. Tours 64dfd201 (Stephany), ff5ef864 (Patricia); threads e379ef3e (Stephany), 9278d6a6 (Deyci); AppFolio cards 9028, 9022, 9014. Found on the way, not fixed: Clara's Oct 29 email to Deyci opened "Hi Pedro".
Battle test, Nov 2
Proposed — pending review We replayed every real Situs showing change since go-live (Sep 29) through this design, assuming E1 to E4 = (a). Everything was read-only: nothing was sent and no PR was opened. The model's reads are stubbed with what actually happened, so this tests the rules, not the model. Passing scenarios only show the rules hold on recorded data; the real proof is a live run after you decide.
| Scenarios, by what they check | Scenarios | Real cases | Pass |
|---|---|---|---|
| Who did it: AppFolio's automation, a person (and which one), the renter, Clara, or our own write coming back | 86 | 86 | 86 |
| Card edits: contact, status, closes | 51 | 51 | 51 |
| The renter's words and silences: confirms, cancels, no reply | 33 | 33 | 33 |
| Showings: booked by staff, moved, cancelled, reassigned | 21 | 21 | 21 |
| Red team (below) | 16 | 0 | 16 |
| Stephany, Deyci, Matthew, Patricia, and the 8 controls | 12 | 4 | 12 |
| Staff notes saying the visit happened, or did not | 8 | 8 | 8 |
| Applications on the card | 8 | 8 | 8 |
| Staff asking Clara or a colleague | 5 | 5 | 5 |
| Change in AppFolio since go-live | Real | Design right | Main today: right | wrong | missed |
|---|---|---|---|---|---|
| Staff booked a showing | 71 | 71 | 0 | 0 | 71 |
| Staff moved one | 10 | 10 | 1 | 0 | 9 |
| Staff reassigned the host | 10 | 10 | 0 | 1 | 9 |
| The renter declined (DENY) or the team cancelled | 10 | 10 | 10 | 0 | 0 |
| AppFolio's clock stamp "Showing Occurred" | 4 | 4 | 4 | 0 | 0 |
| The renter called it off: no tour | 2 | 2 | 0 | 2 | 0 |
| Toured, or the renter confirmed | 2 | 2 | 0 | 0 | 2 |
| Card edits in the four cases (contact, application) | 2 | 2 | not compared | ||
| All | 111 | 111 | 15 | 3 | 91 |
"Main today" means main with the teammate's-text fix merged (Nov 2). That fix sees exactly one kind of change: a move a teammate announced by text, on a confirmed Clara tour, at a live building. The 10 cancels had no Clara tour behind them, so there was nothing to undo.
Why 71 bookings and not 83. Staff booked 83 of the showings on the calendar since go-live (customer table below). 71 count as new bookings. 3 were rebookings that pair with a cancel on the same card into one move: Angelique 4011 to 4012, Maynor 3989 to 4026, Diego 4073 to 4076. The other 9 were booked before Sep 29, so only their later changes are replayed. Nothing is left over.
Three worst surprises
- Stephany (showing 4082) got the wrong host and an application link for a tour that never happened. The leasing agent who took over her showing never became the host. Her "I'll have to reschedule" sat unread on a held thread. AppFolio's 3:00 PM "Showing Occurred" is just its clock. So main sent the notes ask to the old host, the link at 5:15 PM, and queued three chase texts. Patricia (showing 4081) is the same: nobody joined, the clock stamp fired, the link went out. The design gets both right.
- AppFolio's automatic emails name an agent as the sender. Reminders, invitations, updates and cancels all carry the assigned agent in From. Production files them as that person. That is what paused Clara on Matthew's thread while her offer was still open. Reading who did something from evidence (the activity log, the roster, timing) instead of the From line resolves every record.
- Main misses almost everything staff do in AppFolio. It misses all 71 showings booked by hand: no person, no tour, no reminders, and Clara's open offer stays up. It also misses 9 of 10 moves and 9 of 10 host changes.
| Who made each change (records since go-live) | Records |
|---|---|
| AppFolio's automation (automatic messages, clock stamps, integrations) | 798 |
| A leasing agent, resolved to which one (3 agents) | 543 |
| The renter | 490 |
| The two shared VA seats (a person, never one name) | 201 |
| The property manager | 195 |
| Our own write coming back (an echo) | 29 |
| Clara | 16 |
| Unknown (fails the run) | 0 |
| All records since go-live | 2,272 |
Every record resolves from evidence, never from a signature. The evidence is, in order: our own ledger, the sender's user on the roster, the text's direction code, the activity author (texts and card events only), the clock, a few-second causal join to a showing change, and the full wording of AppFolio's automatic messages. A shared VA seat counts as a person, but never as one named person. "Unknown" fails the run, and there are none.
| Customer | Read tonight (GET only, warm session) | Showings since go-live | Replayed |
|---|---|---|---|
| Situs Group | Yes: showing list, 85 showings, 70 cards | 85: 2 booked by Clara, 83 by staff | Yes, every case |
| Western Slope | Yes: showing list once | 41: 32 by Clara, 9 by someone else | Not yet; the 9 are next |
| JP&Co | Yes: showing list once | 41: 39 by Clara on the Willows test property, 2 other | No (test traffic) |
Red team, Nov 2
22 of 22 pass. Each attack is a real case, a variant of one with one thing changed, or a made-up case where no real one exists (rows 12 and 13), plus a planted bug. A row passes only if its scenarios pass and the planted bug turns them red.
| # | Attack, or rule kept as is | Result |
|---|---|---|
| 1 (a) | At a Preview building a staff move still updates our tour. Telling the renter is a new outbound (company setting, off by default) and never sends at Preview or Off | pass |
| 2 (b) | A cancel and a rebook on the same card within about 25 min are one move, in either order; 26 min apart they are two (Diego, Maynor) | pass |
| 3 (c) | "Already told" looks from 30 min before to 3 min after the change, then checks again. A failed text or an unknown sender never counts | pass |
| 4 | A clock stamp never means toured. A shared seat is a person, never one named person | pass |
| 5 | The renter says yes to Clara, then DENY on AppFolio: her latest word wins | pass |
| 6 | Staff book on a second card with the same phone: same person, Clara's offer withdrawn, no second thread | pass |
| 7 | A contact edit in AppFolio adds a contact; the channel the renter replies on stays first | pass |
| 8 | Our own write coming back late is an echo; our stale write never overwrites the team's change | pass |
| 9 | A sign-in page, partial read or cut-off list never cancels and never changes what we last saw | pass |
| 10 | A slow read that finishes late is dropped; the same change seen twice is decided once | pass |
| 11 | After pairing, nothing is written on the cancelled showing | pass |
| 12 | Thirty reassignments in a minute wake one read, and every host follows | pass |
| 13 | A move or cancel three weeks out is found; a missing showing only triggers a read of that showing | pass |
| Keep | One owner per field: AppFolio owns the time | pass |
| Keep | Changes are found by comparing content, never clocks | pass |
| Keep | A notice only wakes a read; it is never the change itself | pass |
| Keep | Only a read of that one showing can cancel | pass |
| Keep | Each change is decided once, also while a thread is on hold | pass |
| Matthew | AppFolio's automatic reminder that names an agent never pauses Clara's thread | pass |
| Voicemail | A leasing agent's 3:44 PM voicemail from the office line is a person on a shared line, never Clara | pass |
| Stephany D1 | The host follows the agent assigned in AppFolio: old calendar event removed, new one created silently, no renter message, notes ask to the new host | pass |
| Stephany D2 | Her cancel by text on a held thread means the tour did not happen: no notes ask, nothing written. A clock stamp alone never means toured | pass |
Three changes from your review, now in the rules: (a) telling the renter is a new outbound behind a company setting, off by default, never at a Preview or Off building (E2 reworded above); (b) a cancel and a rebook on the same card within about 25 minutes are one move, in either order; (c) the "already told" check looks both ways, from 30 minutes before the change to 3 minutes after, then checks again. The model judges whether the renter was told. Who sent it comes from the sender's identity, and an unknown sender never counts. On the real cases, counting AppFolio's automatic notice as "told" changes no decision.
Still to align: the staff-booked showings rule (e) still counts an AppFolio notice as "told" and tells hosts about a reassignment. The battle-tested rules above count only a person, and the calendar moves silently (Stephany D1).
| AppFolio behaviour the design never defined | What the real records show | Rule now |
|---|---|---|
| What a renter's DENY does | Nayely, showing 4038: she texted "Deny". One second later AppFolio logged "Showing Canceled by Prospect"; six seconds later it texted her its own cancel notice | The renter cancelled: no correction. Her latest word wins over an earlier yes to Clara |
| How soon our own write shows up in AppFolio | Within seconds, often stamped a few seconds before our ledger time. A call line is dated at the call's start, up to 3 days before we wrote it | An echo is matched by content against our ledger, never by a time window |
| Does a missing showing mean cancelled? | A sign-in page or a cut-off list shows no showings at all | Only a read of that one showing can cancel; a bad read changes nothing |
| Does "Showing Occurred" mean they toured? | Stamped at exactly the start time, to the second, with the last editor's name (4 of 4 cases) | Clock only. A person's record or words decide whether it happened |
| Does "Showing Confirmed" mean they replied CONFIRM? | AppFolio also logs free-text replies this way | The model reads the renter's words |
Poll cost, in requests. The "37 requests per card page" figure has no source. We measured our own reads on Situs today:
- one showing: 1 request;
- one card, read with plain requests: 3;
- a full card page in a browser: 15 to 16.
| Path, Situs | Reads an hour | Requests an hour | Share of 1,440 |
|---|---|---|---|
| Targeted, today | 18 | 42 | 3% |
| Targeted, with staff showings imported | 54 | 150 (not ~2,000) | 10% |
| no Flat poll, every card every 5 min | 204 | 612 | 43% |
| Monday burst: 30 reassignments in a minute | 1 (one read per company per minute) | 1 to 2 | under 1% |
Method: counted from our own read logs on Nov 2 (85 single-showing reads; 70 cards at 3 requests each; 5 full page loads at 15 to 16 requests to the customer's AppFolio, plus 3 writes blocked each time). Requests an hour = calendar reads × 1 + card reads × 3. Reads have no cap, only logins do, and our own code disagrees on that cap: 3 or 5 an hour.
Gaps the replay found Proposed Each one is guarded by a planted bug:
- Words after the slot count. Sabrina, showing 4061: the agent texted "Are you at...?", and an hour later she said she needed to reschedule. Up to 48 h after the start, her words can show the tour did not happen.
- A plain "yes" to a teammate is a confirmation. The model reads it (Leslie, showing 4014).
- A teammate's note can close a visit, up to 48 h later. One note about an outing closes every showing on it (Sheyla's two homes, noted the next day).
- Each question to the model names its showing. Without that, Sheyla's "Confirm" to her 7:30 PM showing would also have confirmed her 8:00 PM one.
- Card closes count per home. Three homes closed in the same second are three closes (Diego).
- A cancel is dated at the message that called it off, not at the renter's last message (Rita, showing 4025).
- Tour mode can't come from AppFolio's type field. It said in person for video and lockbox tours (Rodney 4057, Ashley 4063). Open: no rule yet.
Yardi (Yale 25): the same engine, and what is AppFolio-only (click to open)
Same event shape, owner table and reconciler; only the adapter differs. Everything below is from our build notes and Yardi's published API spec. We have no Yardi login yet, so none of it is verified.
- Reading appointments: phase 1 is the browser agent on Voyager 8 and CRM IQ (ConAm's login), reading the CRM IQ calendar and the guest card's activity, planned "before each booking and every 15 min". Phase 2 is the SIPP API, where every call is a POST; the prospect module's guest-activity call takes a property and a date window. We deferred that module on Jun 29.
- Change feed: none. No webhooks for a browser user and no "changed since" in the prospect API (maintenance has one).
- Cost: unknown until we log in. A browser page is heavy (one AppFolio card page in a browser makes 15 to 16 requests to the customer's AppFolio; a plain read of the same card takes 3), so Yardi reads a calendar window per property, not card by card.
- A moved appointment shows as the same appointment at a new time in the window; a missing one gets a guest-card read before anything is cancelled.
AppFolio-only assumptions to keep out of core:
- Notices and the texting mirror as signals. Yardi has none known, so it runs on the poll.
- Reading card by card. The adapter needs both "read one card" and "read a window".
- The tour's AppFolio-named showing link with an integer id. It becomes a neutral, provider-prefixed reference.
- Status words like "prospect confirmed". Each adapter maps to ours: scheduled, renter confirmed, cancelled, no-show, shown.
- The report budget and AppFolio's calendar copy are AppFolio features. The renewals reconcile (ADR-0091: pure classifier, apply step, owner wins, rotation cursor) is the pattern to copy, not its AppFolio reads.
Research behind the rules, with its own caveats (click to open)
Two-way sync research, Nov 2, 39 sources. Its bottom line: the safe shape is not a symmetric sync but two one-way flows with one owner per field, a stored copy of the last remote state, change detection by content, and a sweep that always converges. Every published sync that works (HubSpot and Salesforce, Showdigs and AppFolio, Nylas, Dropbox) is built this way. It lists 14 failure modes (echo loops, last-write-wins loss, dual writes, duplicates, missed notifications, invisible deletes, a move read as cancel + new, double notification and others) with a fix for each; the rules, diagram and will-not list above follow those fixes, and E1 to E4 are its four questions.
- One owner per field: HubSpot sync rules; Showdigs and AppFolio (synced fields editable only in AppFolio).
- No dual writes: Kleppmann; transactional outbox.
- Echoes and change by content: Workato; Nylas; Jepsen on last-write-wins.
- Last-seen state as the merge base: Dropbox.
- Notifications wake, a sweep backstops: Graph webhooks; Graph delta; Merge; Google Calendar sync.
- Idempotency: Stripe; Temporal messages; Signal-With-Start.
- Correct once, don't block: Helland, Building on Quicksand; Hohpe.
Caveats the research flags. Not fetched (cited from memory or a search summary): Kleppmann's book chapter, Dynamo, RFC 5546, Workato's loop-prevention page, Cronofy, Merge's deleted-data and sync-frequency pages, Unified.to, Finch, and a Microsoft Q&A thread. Community, not vendor docs: that Microsoft Q&A thread and a Nylas forum post. Jepsen's ~91% loss figure comes from a search summary. "Tell the renter once per fact" and polling hot records faster are inferences, not sourced practice. Whether AppFolio offers partner webhooks is unconfirmed. The cadences on this page are our own design call.
Design A: Fable 5.1 (full design, click to open)
1. The answer in five lines
1. Clara joins a renter's journey at any point by reading what already happened and acting only on what is still ahead. She never redoes a step a person or a system already did: no second confirmation, no "I'm Clara" when a person already spoke, no re-sent link.
2. One tour record whoever made it (Clara, staff in the PMS, the renter on a listing site), with an origin, a host and a provider-prefixed showing id. One Temporal run per tour, started with the state it already has; its timers skip what has passed. At Situs a person booked every showing this year, six in ten the minute the card was typed in: the staff-booked door is the product.
3. Moves, cancels, reassignments and renter confirmations made outside Clara reach that run through the signals it already has; the PMS adapter turns PMS activity into neutral events.
4. On a thread, Clara looks before she speaks: a person's reply after the renter's last message means she stays quiet there until the renter is waiting on nobody.
5. North star: all seven AppFolio automatic messages off, Clara owning every renter touch. Four gaps are built now (staff-booked showings, confirm-or-release, listing-site bookings, the post-showing follow-up); Situs switches each AppFolio message off the day its replacement is proven.
The plan
| Phase | What | PRs | Proof rung before it is on | AppFolio message it retires |
|---|---|---|---|---|
| 0. Now | Ask Situs to switch off the automatic inquiry reply ("Thank You for Your Interest in <address>…", minutes after every inquiry, from the assigned agent's address). Clara's first reply is live. Lead-nurturing workflows are already drafts there: nothing to switch. | none | Supervised week at Situs (running since Oct 20) | #1 (#4a already off) |
| 1. Tour has an origin | Origin, host, external showing id, import stamps on the tour; the PMS write stamps its showing id back. No behavior change. | PR 1 | Byte-identical replay of every tour row at the three customers | none (foundation) |
| 2. Staff-booked showing becomes Clara's tour | PMS showing activity → neutral event → tour row (renter resolved from the card first) + workflow started as "already booked"; host told in one line; PMS cancel, move and reassignment flow back the same way. Also stops Clara's follow-up cadence from texting a renter staff already booked. | PR 2 (fix), PR 3 (renter messages, setting) | Replay of the 24 showings Situs staff booked since Oct 20 → Willows run with a hand-booked showing → sandbox company | #2 (booking text + email, confirm request), #6 |
| 3. Confirm-or-release | Reminders ask for a "yes"; silence 30 minutes out tells the host once; no tour is cancelled. (Sub-design: Confirm or cancel.) | PR 4, PR 5 (setting, default off) | Willows run → Situs on | #3 (auto-cancel) |
| 4. Listing-site bookings | TurboTenant / Tenant Turner "showing booked" emails become the same neutral event; Clara's reminders stay quiet while the site's are on. Last by frequency: 0 self-booked showings at Situs this year. | PR 6 | Replay of the 2026 listing-site booking emails → Willows | #7 (self-scheduling) |
| 5. Post-showing follow-up | One thank-you + "how did it go?" + apply path after the tour; renter replies to AppFolio's own email thread are matched to the renter's conversation. | PR 7 | Replay of attended tours → Willows | #4 (post-showing email, survey) |
| 6. Staff moves | Already designed and prototyped: propose-and-confirm, reassign (page: Staff Tour Moves and Reassignment). Plus one fix found today: a tour moved after its day-before reminder went out gets no reminder for the new time. | PR 2b (fix), PR 8–11 (that page) | That page's ladder; 2b: replay of October moves → Willows move after the reminder | #2 ("your showing has been updated") |
Yes / hold for Fede
All seven: yes. Two gate Situs this week: 2 and 7.
| # | Decision | Recommendation |
|---|---|---|
| 1 | A tour imported after it already happened: within 24 h of its end Clara runs the normal after-tour steps (host asked for the outcome, application link once attended is known and nobody sent it); older than 24 h she records it and sends the renter nothing. | yes |
| 2 | Renter-facing messages on imported tours (the "you're set" line and reminders) ship behind one company setting, default off, switched on at Situs the same day AppFolio's showing confirmations go off. The import itself, the calendar event and the host line ship with no switch. | yes |
| 3 | While AppFolio's auto-cancel is still on and it cancels a Clara tour: Clara says nothing to the renter (AppFolio already texted), tells the host once, and re-offers times through the normal follow-up. | yes |
| 4 | Imported listing-site tours: Clara's reminders stay quiet until that site's own reminders are off at the customer (one line per site on the company page). | yes |
| 5 | No intro and no second confirmation when a person already spoke to the renter; Clara continues the same thread, signed as herself, and takes it only when the renter is waiting on nobody for 15 minutes. | yes |
| 6 | AppFolio's own messages on Clara's showings: the one card Clara booked at Situs (Oct 31, tour Nov 2) shows none of them: no booking text, no "Showing Reminder" email, no confirm request by the time Clara's one-hour reminder had gone. The robot's notify-off holds on that evidence; Fede's note of Nov 2 says they fire. Keep the design for both and settle it with the phase-2 Willows run (one showing booked through Clara, bench phone watched). Auto-cancel: never seen at Situs (0 in the 2026 dump; a DENY becomes "Canceled by Prospect", staff cancel the rest by hand); its toggle is unreadable to us. | yes: design for both, prove at Willows |
| 7 | The Clara AI user cannot open AppFolio's Automatic Communication and Showings Settings pages, so every ON/OFF above is read from what AppFolio sent on real cards, not from the toggles. Ask the Situs org admin to grant that user the settings permission (one line), or to read the two pages for us. | yes: the permission, read-only use |
The seven AppFolio messages and what replaces each
| AppFolio message | Clara's replacement today | Build now | Proof rung | Off at Situs when |
|---|---|---|---|---|
| 1. Automatic reply to a new inquiry: "Thank You for Your Interest in <address>… to schedule a tour, please reply to this email or call … option #1" (minutes after every inquiry, repeats included, from the assigned agent's address; the questionnaire was never seen) | Live. First reply by text (AppFolio texting seat, Oct 29) and email (Oct 28); 12 first-touch emails and 18 texts at Situs so far | Nothing | Supervised week (running) | Now |
| 2a. At booking, Clara-booked: "Your showing has been scheduled: <address> <day date time> -- <owner LLC>" text + "Showing Reminder" email (same minute as the booking; five notify boxes ticked by default on the form) | Live. Clara's booking confirmation by text or email. AppFolio's did not fire on the one Situs card Clara booked (consistent with the robot unticking the boxes) | Nothing | Willows run in phase 2 (one card is not proof) | Per-showing boxes, no company switch: already off for Clara's |
| 2b. At booking, staff-booked: the same text + email | Missing. Clara never learns of the showing; the renter hears AppFolio under the agent's name | PR 2 + PR 3 | Replay → Willows → sandbox | PR 3 on at Situs; staff untick the two "send showing details" boxes (a habit; the default sits in a settings page we cannot read) |
| 2c. Day-of confirm request: "Please confirm your showing: <address> Today at 2:30pm… Must reply with CONFIRM or DENY by 1:30pm" about 2 h before, deadline 1 h before, under the assigned agent's name (22 of 23 staff bookings since Oct 20); "Your showing has been confirmed / canceled" replies instant. AppFolio sends no other reminder | Partial. On Clara's showings: day-before and one-hour reminders, per slot (more than AppFolio does). Neither asks for a "yes": that is confirm-or-release | PR 4 + PR 5 add the "reply yes"; PR 3 brings the reminders to staff-booked tours | Willows → Situs on | With 2d and 3 |
| 2d. The same request on staff- or site-booked showings | Missing | PR 3, PR 6 | Replay → Willows | PR 3 on; site reminders off |
| 3. Auto-cancel of unconfirmed showings | Unknown and never seen. 0 auto-cancels in Situs's 2026 dump: a DENY becomes "Canceled by Prospect", staff cancel the rest by hand, and unanswered requests still went on to "Showing Occurred". The toggle is on a page the Clara AI user cannot open | PR 4 + PR 5 (confirm-or-release: no cancel, host told once). The human version exists today: staff cancelled 28 never-confirmed showings by hand this year | Willows → Situs on | Setting on at Situs; a Situs admin confirms the toggle (yes/hold 7) |
| 4a. Lead-nurturing texts and emails | Live. Follow-up cadence to a quiet lead and re-engagement. At Situs the AppFolio side is already off: the three workflows are drafts, no leasing Realm-X flow runs | Nothing | Live at Western Slope | Already off |
| 4b. Post-showing email, about 23 h after the slot: "Thanks for visiting <address>", unit facts and the listing line, "For more information or to apply, please contact us by responding to this email or calling us at (303) 789-3030", signed by the owner LLC (5 of 6 occurred showings; also sent to a renter whose application was already in) | Partial. Application link after the host marks attended; no thank-you; a renter's reply lands in leasing@ under AppFolio's thread, unseen by Clara. This one is a company-wide setting, not a per-showing box, so the robot cannot untick it: Clara-created showing 597 drew it (Oct 19) | PR 7 | Replay → Willows | PR 7 live, then the setting off |
| 4c. Showing survey | Unused at Situs (the tab exists, zero answers ever). Clara asks the host "how did it go?"; the renter is not asked | Folded into PR 7's first after-tour touch | Same | PR 7 live |
| 5. Hand-sent templates: the VA's "Showing Invitation" email + text ("We received your inquiry for <address>… what date and time would work best…"), the agent's "We have received your application!" saved reply | Live. Clara's first reply and time offers; the card shows her claim once her message went out | Nothing in code; a team habit | n/a | Now |
| 6. Staff alert emails (the Clara AI user's own: showing created / updated / canceled ON, new guest card OFF; other users' settings unreadable to us) | Live. Host emailed on booked, moved, cancelled (Nov 1) and asked for notes after the tour | PR 2 adds the same host line for imported tours; AppFolio's showing-created email to the Clara AI user is a second, faster signal for the import (the card timeline stays the source) | Willows | Now (keep the inbox alert) |
| 7. Renters booking showings themselves from a listing | Partial. Clara books every tour she is asked for; a self-booked showing she never sees can become a second booking | PR 2 (PMS self-schedule) + PR 6 (listing sites) | Replay → Willows | PR 2 and PR 6 live |
2. The lifecycle, with every door Clara can walk in through
| Door | Clara does | Clara skips | Host hears | Never repeated |
|---|---|---|---|---|
| E1 Staff booked the showing in the PMS (today the only ways a person can book: the PMS, or texting Clara's desk; PropFlow's pages cannot create a tour) | Adopts it as her tour within the sync's minute: tour row with origin staff, host from the PMS assignee (proposed, rule H), calendar event if none; reminders only for moments still ahead; after-tour ask to the host; application link after attended. With the setting on: one "you're set with <host>, <day time> at <home>; text me here with questions" line | First reply, time offers, "please confirm" nudges, any reminder whose window passed | One line: "I have <renter>'s <day time> tour; I'll remind them and ask you for notes after" | The confirmation staff or AppFolio already sent; an intro |
| E2 Renter booked on a listing site | Same import from the site's email; claims the lead; one reply in the renter's thread with what happens next (an intro is useful here: nobody has spoken to them) | Proposing times (a second booking path); her own reminders while the site's are on | The normal booked notice, naming the site | The site's confirmation |
| E3 Staff replied first | Reads the thread; stays quiet while a person owns it (cadence parked); speaks only when the renter writes again and nobody answers in 15 minutes, continuing the thread; books if asked | Intro, first reply, questions already asked | Nothing; the person is in the thread | A question staff asked; a link a person sent |
| E4 Tour already happened | Within 24 h of the end: asks the host how it went, sends the link once attended is known and nobody sent it, starts the after-tour follow-up from the tour's end. Older: records it, sends nothing | Confirmation, reminders, host confirm | The after-tour ask only | An application link already on the card |
| E5 Application already in | Nothing outbound on the tour; the follow-up stops (it already stops on apply) | Link, chase | Nothing | The "apply" ask |
| E6 Added late to an email thread | Reads it all; answers only the open ask to her or the renter's unanswered one; adopts any tour the thread names (slot match); never proposes a second tour | Intro, recap, anything answered | Only if she books or moves | Anything already answered |
| E7 Staff move, cancel or reassign after Clara booked | Per the staff-move page: ask the renter when staff can't make it, plain confirm when the renter asked, reassign silently. From the PMS: the same path with actor pms; one voice to the renter: quiet while AppFolio's "updated" text is on. Host changes sync in neither direction today (a PropFlow host change makes no PMS action; a PMS reassignment is unread) | Rebooking from scratch; a second confirmation | That page's H-lines | A notice AppFolio already sent |
| E8 Staff take the thread over, then hand it back | Take-over = a person's reply or "I've got this": every cadence parks. Hand-back = "Clara, take it from here", or the renter waiting on nobody for 15 minutes: she resumes with a look at what changed | The step the person did | Nothing | Confirmation, link, answer a person gave |
3. Temporal: one workflow, started with the state it already has
Decision: catch-up, not a start-at-state input. The run already observes the tour instead of assuming it: workflow id = tour id, a second start is a no-op under USE_EXISTING, every timer hangs off the scheduled start, the day-before and one-hour reminders skip once their moment has passed, and every send re-reads the row and quietly skips already-sent. So a staff-booked tour starts the way the cohort walker already starts an auto-confirmed one: write the row as confirmed, then signalTourConfirmed(…, {startIfMissing}); the "please confirm" loop returns before its first nudge. What is missing sits in the adapter and the activities: no PMS code calls applyTourIntent or any signal today, so a showing created, moved or cancelled in the PMS changes nothing here. Two constraints: the run needs a personId (resolved from the guest card first), and the row's existing source: clara | manual | external is written only as clara.
| Door | How the run starts | What each leg does on its own | New |
|---|---|---|---|
| E1, E2, E7-in-PMS, upcoming | Row confirmed with source: manual | external, hostPersonId, external.showingId, the renter's personId from the card; signalWithStart(tourConfirmed) | Phase 1 resolves at once; T-24h and T-1h skip if past; host-confirm leg from T-30; link at start+60 after attended; after-tour follow-up from the end; the lead follow-up cadence stops at its next wake (it reads any confirmed tour) | Quiet skip external-reminders on both reminder activities when external.remindersBy ≠ clara |
| E4 already happened | Same start | Reminders skip; the link leg fires immediately, and the attendance gate already says "send" two hours after the end, so it would send; the walker's 7-day backstop would too | Quiet skip imported-after-end in sendPostTourApplicationLink (the domain function both the leg and the walker call) and in the after-tour chase when importedAt > end + 24 h |
| E7 move in the PMS (and any move) | Adapter → setTourSchedule(actor: 'pms') → tourRescheduled, as a staff move through Clara's desk already does | Legs still waiting move with the new time. Legs that already fired do not: today no schedule version is ever written (bumpVersion: false, the start never passes one), so a tour moved after its day-before reminder went out gets no day-before, one-hour or link for the new time | PR 2b (fix, no switch): every move bumps scheduleVersion; the two reminder legs re-arm after a reschedule, behind patched(); stamps compare per version (the reader already does) |
| E7 cancel in the PMS (staff or auto-cancel) | Adapter → cancel chokepoint with reason: 'pms_staff' | 'pms_auto_cancel' → tourCancelled | Run exits cancelled; the renter notice is the chokepoint's, not the workflow's: silent when the PMS already texted | Reason values only |
| E7 reassign in the PMS | Adapter → hostPersonId + calendar node on the row, history host_changed source: pms_sync | No signal needed: the check-in, host-confirm and after-tour legs read the host at fire time (PropFlow's own host change sends and signals nothing either) | Nothing |
| Renter confirmed through the PMS's own text (CONFIRM) | Adapter → recordProspectReconfirm, the path a texted "yes" takes: stamps prospectConfirmed, signals prospectReconfirmed (not tourConfirmed: that one means "accepted the proposed time") | Host-confirm leg sends its one "they're coming" line | answered: 'pms' on the existing stamp |
| Outcome marked in the PMS | Staff's "Showing Recorded" (attended / no-show) → the staff desk's stampTourAttendance → sendApplicationOnAttended, if no stamp yet. AppFolio's own "Showing Occurred" (339 of 394 at Situs have no actor) only means the slot passed: it maps to the existing slot-passed mark, never to attended | Link leg sends on attended, stays quiet on no-show; the after-tour chase starts | Status values in the adapter only |
| E3, E8 a person owns the thread | No change to the run | The after-tour chase already parks on a human-owned thread (14-day budget); the lead follow-up cadence gains the same park | Park in prospect-outreach-workflow (reads the thread owner at fire time) |
| Idempotency key | Value | Guards |
|---|---|---|
| Workflow id | tour.id, USE_EXISTING | Double start, walker + import racing |
| Import key | external.showingId = '<provider>:<id>', unique index → tour id; slot match (home, start, renter) as the fallback for rows written before PR 1 | Two tours for one showing; Clara's own showing coming back as "staff-booked" (the drain stamps the id on write) |
| Signal event id | '<provider>:<showingId>:<activityTs>:<kind>', stable like the walker's (today's intents build event ids from timestamps, so the run's 256-entry memory never spots a repeat) | At-least-once delivery, re-synced timelines |
| Message stamps | Per slot (scheduleVersion): the reader honours a version, nothing writes one today | A reminder for a slot that moved (PR 2b writes the version) |
| Import window | importedAt vs end + 24 h | A link or chase for last month's tour |
| File | Change | Size |
|---|---|---|
src/lib/temporal/workflows/tour-workflow.ts | Skip the cadence read when the confirm is already in; accept the two new quiet skips; PR 2b: the day-before and one-hour legs re-arm after a reschedule instead of returning (all behind patched()) | ~90 lines |
src/lib/temporal/workflows/tour-skip-reasons.ts | Document external-reminders and imported-after-end in the quiet list (never in the failure list, or the step re-runs) | comment |
src/lib/temporal/workflows/tour-timers.ts | No change: both anchors stay start-based | 0 |
src/lib/temporal/activities/tour.ts, tour-row-lookup.ts; src/lib/domain/leasing/send-application-link.ts, tour/send-application-on-attended.ts | Reminders and the weekend check-in (weekend-tour-checkin.ts, replacing its manual skip) read external.remindersBy; the 24-hour window lives in sendPostTourApplicationLink so the leg and the walker share it; the no_property early return in the attended path goes (company-scope tour instead) | ~120 |
src/lib/temporal/tour-client.ts | startAdoptedTour() = confirmed start helper (wraps signalTourConfirmed + startIfMissing) | ~40 |
src/lib/temporal/workflows/prospect-outreach-workflow.ts | Park a touch while a person owns the thread (same rule as the chase) | ~60 |
PMS adapter: activity-kind.ts, card read, showing-sync-stream-consumer.ts | Map the three phrases that are unknown today ("Showing Scheduled" 514 rows at Situs, "Showing Canceled by Prospect" 53, "Showing Occurred" 394) and split confirmed / modified / canceled instead of one showing kind; the card's showing read gains assignee, end and unit; each card refresh whose showing activity is newer than lastSeenAt emits ShowingObserved (one bounded read per changed card); route to the tour chokepoints | ~300 |
src/lib/domain/pms/guest-card-outbox-drain.ts | Stamp the created showing's id on the tour | ~40 |
src/lib/domain/leasing/tour/apply-tour-intent.ts, set-tour-schedule.ts, tour-event-provenance.ts | Trigger pms, classed as not Clara's action (today every trigger but ui counts as hers); source: manual | external on create instead of the hardcoded clara; bumpVersion: true on every move and the version passed to the start; silent-renter rule when the PMS already notified; provenance text "Booked by a person in the PMS" | ~100 |
agents/clara/lib/email/parse-lead-source.ts, process-inbound-message.ts | TurboTenant and Tenant Turner "showing booked / reminder" shapes → ShowingObserved; such a notice leaves the tour-request path (today ParsedLead has no showing time, id or booked flag) | ~180 |
4. Data model
| Field | Shape | Rule |
|---|---|---|
source (existing) | 'clara' | 'manual' | 'external' | The origin. clara: Clara booked it, including on a staff instruction through her desk (bookedByStaffPersonId stays). manual: a person booked it in the PMS. external: the renter booked it on a listing site or the PMS self-schedule page. Defined in types.ts since the field was added, written only as clara today. Its one reader, the weekend check-in's manual skip, becomes the remindersBy rule in PR 3 (or imported tours would never get the Friday check-in once their messages are on); the types.ts comment calling manual "made in the UI" is corrected in PR 1. No new origin field |
external.provider | opaque string, the prefix of showingId | Where it came from; data, never a branch in core |
hostPersonId | string | undefined | From the staff-move contract, reused. Today the host lives only in calendarNode { kind: person | property | org, id }; the import writes both when the PMS assignee maps to a person (else the property's calendar node). An imported tour is in_person with no video room unless Clara is its host |
external | { showingId: 'appfolio:123' | 'turbotenant:abc', guestCardId, assigneeRaw, confirmState: 'unknown' | 'confirmed' | 'denied', remindersBy: 'clara' | 'pms' | 'site', lastSeenAt } | Replaces today's Tour.af { pms: 'appfolio', showingId }, the one PMS name on the tour (read as a fallback until backfilled). Provider prefix is opaque to core; remindersBy is set from the company page per provider and flips to clara when the customer turns the PMS or site reminders off; confirmState is mirrored into the existing prospectConfirmed stamp |
importedAt, importedWhen | ISO; 'upcoming' | 'past' | Drives the 24-hour window in sendPostTourApplicationLink, shared by the workflow leg and the walker's backstop |
history[] | existing TourEvent (actor?: 'staff' today) + actor: 'pms', source: 'pms_sync' | Audit of every outside change |
Conversation threadJoin | { joinedAt, humanTurnsBefore, lastHumanReplyAt, ownerPersonId? } | On the conversation, not the tour; the speak / act / stay-quiet rule reads it |
Neutral shape (src/lib/domain/pms) | Fields | Direction |
|---|---|---|
ShowingObserved | provider, externalId, guestCardId, unitRef, startAt, endAt, assigneeRaw, status: scheduled | rescheduled | cancelled | confirmed | occurred | recorded_attended | recorded_no_show, actor: staff | renter | system | us, observedAt | Adapter → core, emitted from timeline activity kinds; routed to the tour chokepoints |
ShowingWriteResult | externalId | Adapter → core on create, so the tour carries its id from the first minute |
A showing on a home with no mapped property still becomes a tour at company scope, host line to the company inbox; the property is narrowed in when the unit maps.
5. Who goes first, and joining a thread late
Fede, Nov 2: no race rule at Situs now ("expected, we just launched"); what follows is behavior, not a switch. The pieces exist but do not see each other: the first touch skips when a teammate replied and waits ten minutes on a hand-made card; the follow-up gate stops on "teammate answered, no renter reply since" and "staff promise pending"; a staff text through the PMS texting door sets a hold, a staff email none; "coworker acted outside" is classed and then ignored; and "tour booked" reads PropFlow tours only. The thread-owner record and the import make one rule of these.
| Situation | Speak | Act | Stay quiet |
|---|---|---|---|
| New lead, nobody replied | First reply; claim the card after it went out | Book when asked | |
| A person replied after the renter's last message | Record the thread owner; park every cadence touch | Yes: the person owns it | |
| Renter writes again, nobody answers for 15 minutes | Continue the thread, no intro, no recap | Book, move, answer | |
| Added to an email thread late (CC, forward) | Only the open ask to her or the renter's unanswered ask | Adopt a tour the thread names; do what a teammate tells her, one line back | Teammates talking among themselves |
| A teammate already answered the ask Clara was drafting | Pull the draft | Yes | |
| Tour already booked by a person | No second confirmation | Adopt it (section 3) | Until the next moment she owns (a reminder, the after-tour ask) |
| Application already in | Stop the chase | Yes, until the renter writes | |
| Live case, Oct 29 (Situs): Clara texted and emailed a new lead; the renter asked her for a 2:15 slot; the agent asked the team, then called the renter and booked 3:30 in the PMS | Nothing: a person booked it | Adopt the 3:30 showing (E1), park the cadence; today Clara neither knows of the showing nor stops | Yes, until a moment she owns |
| Hand-back ("Clara, take it from here") | Resume with a look at what changed since her last touch | Next step only |
6. Western Slope said this already
Emails, card records and session records read as written. Lines marked raw come from the per-speaker #transcripts text of eleven Western Slope meetings (Sep 4 to Oct 28), never the recap; Kat had no mic of her own, so a † line is labelled Jason and may be hers, and Oct 21 and 28 names were removed at the source. The hand-off is their first ask at every meeting since Sep 16.
| Date | Who | What they said or did | Door | Answer in this design |
|---|---|---|---|---|
| Sep 9 | Jay, kickoff | Alert the leasing agent the moment Clara books a tour; "if it's in Guest Card, it's good to go" | E1 | Shipped Nov 1 (host email); PR 2 sends the same line for tours staff booked |
| Sep 16 | Jason, pre-go-live (raw) | "She's getting stood up on a lot of showings recently… AppFolio will be sending them a confirm or deny… that's already happening" | confirm | Confirm-or-release (PR 4–5); AppFolio's request stays on until it is live |
| Sep 16 | Jason† (raw) | "even after a showing, they'll send them another reminder. It's weird… the auto responses will turn off" | E4 | AppFolio item 4 off the day PR 7 is live; it is a company-wide setting the robot cannot untick |
| Sep 16 | Jason (raw) | "I'm out today, and this person's covering me, route this to this person's schedule… let's just book it to Jonathan right now" | E7 | Host from the PMS assignee (hostPersonId; proposed, rule H); reassign on the staff-move page |
| Sep 16–30 | Jason, meetings and emails | "Maybe we can build some protocol when a live agent should intervene?"; the hand-off is "most friction", "doubling the workload" | E3, E8 | Section 5: a person's reply parks Clara; the renter waiting on nobody for 15 minutes brings her back |
| Sep 21 | Email ledger | Bev double-booked a tour Clara had already booked | E1 | One tour per showing, keyed on the PMS showing id (PR 1–2) |
| Sep 22 | Jason†, working session (raw) | "the two that I just answered to before Clara got to it… I clicked schedule showing… but it did not show up on my calendar" | E3, E1 | PR 2: a showing booked in the PMS becomes her tour, calendar event included |
| Sep 22 | Jason (raw) | "if Kat intervenes at any point, Clara will just be like, okay, Kat's got it" (Fede: "something we haven't built yet"); "once I jump in… she doesn't jump back in" | E8 | Section 5: a person's reply parks her; "take it from here" or a renter waiting on nobody resumes her |
| Sep 23–24 | Kat (session record) | Could not take Clara's morning slot, booked the afternoon herself in AppFolio; PropFlow had no tour, so Clara's reminders would contradict her. Fix deferred "until a second hand booking" | E1 | The second one came (Oct 16). PR 2 adopts the hand booking within the sync's minute |
| Sep 24 | Bev; Kat, emails | "leads are starting to pile up, so I'm not sure whether I should step in"; "If someone calls me directly am I able to schedule my own showing" | E3, E1 | Yes to both: Clara takes new leads, keeps quiet where a person answered; a showing Kat books becomes Clara's to remind and follow up |
| Sep 25 | Jason, email | "find better ways where Clara is booking a showing even if Kat books it directly" | E1 | PR 2, exactly this |
| Sep 28, Oct 17 | Kat, email; card 3012 | "I accidentally sent an application… Does Clara send one as well?"; Clara sent the link, Kat sent it again 43 minutes later | E4, E8 | Never repeated: the link checks the card's "application sent" activity first (PR 7); a person's send counts as sent |
| Sep 29 | Kat, email | "How long should Bev or myself wait to respond if Clara hasn't?" | E3 | Section 5's 15-minute rule, written down for them |
| Sep 30–Oct 19 | Shared-inbox study | Staff restarted 6 of 80 of Clara's leads from scratch; "Also ignore Clara" to a resident | E8 | Thread owner recorded; Clara's claim visible on the card; parked cadence never restarts a lead a person is working |
| Oct 13 | Sean, our draft to them (raw) | "showings Kat and Bev book directly in AppFolio or Rently don't come back to us" | E1, E2 | PR 2 (PMS); PR 6 adds Rently's notice shape if it reaches the inbox |
| Oct 14 | Kat, weekly sync (raw) | "I already scheduled it verbally with the person"; wants it "entered as a showing that Clara has booked, but doesn't want to trigger Clara going all the way from the beginning" | E1 | The brief, in the customer's words: PR 2 starts the run as booked, nothing to its left fires |
| Oct 14 | Jay (raw) | "not to send her… hey, it's Clara, I'd like to reach out and schedule a showing… 20 minutes after Kat just recorded that she had a phone call with her" | E3 | A logged call on the card is "coworker acted outside": the first touch skips, the cadence parks |
| Oct 14 | Kat (raw) | "she said, okay, I'll put it in your notes. And then, a half hour later, she texted me and asked if I had any notes for N." | E4 | The after-tour notes ask checks the notes already given on that thread first (PR 7) |
| Oct 14 | Jay, weekly meeting | "Don't reach out to him like he's a brand new lead"; "how do I let Clara know not to reach out" | E3, E8 | Look before speaking; "Clara, I've got this" parks her, "take it from here" resumes her |
| Oct 15–21 | Fede; working session | "Clara can't see communications that happen through AppFolio from you or Bev"; "if it doesn't make it into AppFolio, Clara has no way of knowing" | E1, E7 | PR 2 reads the card's showing activity (scheduled, modified, canceled, confirmed, occurred); the adapter turns it into events Clara acts on |
| Oct 15–18 | Card 3012 | The AppFolio write failed, Kat booked herself, a re-send added a second showing, two "Thanks for visiting" emails | E1 | Showing id stamped at write; import keyed on it; AppFolio item 4 off once PR 7 is live |
| Oct 16 | Kat, email | "another no show and if I could've called them I could have found out if they were still planning on coming" | confirm | Confirm-or-release (PR 4–5): the reminder asks for a yes; silence 30 minutes out reaches the host |
| Oct 20 | Kat, email | "couldn't confirm the reschedule time because it was in 60 min same day notice window" (fixed Oct 20) | E7 | Staff-move page; same-day moves are the common case there (3 of 7 under two hours) |
| Oct 21 | customer contact, scoping call (raw) | "hey, Clara, I already scheduled this showing, can you just put it on the calendar with this person? And Clara reached out to schedule the showing" | E1, E6 | Adopt the tour the thread names; never propose a second one |
| Oct 21 | customer contact (raw) | "I see that Clara is reaching out, but I'm gonna go ahead and call them… then Clara is like, hey, I haven't heard from you, should I turn off your guest card?"; "we've asked our staff to put their hands down" | E3, E8 | The cadence parks on a staff call or note; stepping in becomes safe, so hands need not stay down |
| Oct 21 | Working session | "be very deliberate on the handoff"; they want "where we step in, where we don't, and what we need to do when we step in to let Clara know"; Clara chased a showing their lead had already set | E8, E1 | Section 5 is that protocol; E1 stops the chase of a tour a person already set |
| Oct 28 | customer contact (raw) | "as soon as someone steps into the conversation, Clara just takes the backseat… what's the trigger point if she wants Clara to then resume?" | E8 | Hand-back: "Clara, take it from here", or the renter waiting on nobody for 15 minutes |
| Oct 28 | customer contact (raw) | "she's only gonna know you left a voicemail if you mark record communication or you put a note… to avoid you guys both reaching out on the same thing" | E3 | Card notes and logged calls are read before every touch; nothing else can see a call |
| Nov 1 | Session records | 49 tours since Sep 2, 5 moves, one staff move through Clara (renter told nothing); moves made in AppFolio never reach Clara; no way to hand a tour to another agent | E7 | PMS moves and reassignments flow into the same chokepoint (PR 2); the staff-move page covers the ask and the hand-off |
7. How often each door is used (Situs, real data)
Counted from Situs's own records: the AppFolio guest-card dump (Jan 1 to Sep 1, 2026; apartment buildings, 658 cards; all property types in parentheses), the message mirror (Sep 29 to Nov 2) and the live window since Clara went on (Oct 29 to Nov 2, ten Clara threads). Renters as initials; doors in order of use.
| Door | Count | Denominator (window) | How counted | Confidence |
|---|---|---|---|---|
| E1 Staff booked the showing by hand | 390 of 390 booking events (507), about 11 a week; none by the system or Clara. 205 of 337 cards with a showing (61%) were born booked: the same person typed the card in and booked within five minutes, no earlier logged contact. Live: staff 4, Clara 1 | Booking events; cards with a showing (dump). Bookings (live) | "Showing Scheduled" events with their actor; mirror "Your showing has been scheduled" texts, deduped by phone and slot | Hard |
| E3 Staff talked to the renter first | 429 of 658 cards (65%; 538 of 883) were typed in by staff, so the talk began before any feed could show the lead. Live: on 20 new lead threads the first message out was staff 6, AppFolio's auto-reply 6, Clara 5; staff wrote first on 1 of the 10 Clara threads | Cards (dump); threads (live) | First 2026 create event and its actor; mirror threads joined on person id and phone | Hard (dump); tiny sample (live) |
| AppFolio's own automatic messages | 1,496 on apartment cards, about 43 a week: three to four per booked showing plus the inquiry auto-reply; 319 in the mirror | Messages (dump and mirror) | Template text match on card notes and message bodies | Hard |
| E7 Staff changed a showing after booking | 105 of 377 booked showings (28%; 135 of 489), about 3 a week: moved 39, reassigned 31, cancelled by staff 46 (28 of them never confirmed after AppFolio's request), confirmed by hand 26. Mirror: 11 moved notices, 15 cancelled | Booked showings, by card and unit (dump) | Showing Modified / Canceled / Confirmed with a staff actor | Hard |
| E8 Take-over, then hand-back | Live: staff took over 2 of 10 Clara threads after her first message; Clara never wrote again after staff (0). Proxy: 80 of 577 cards (14%) bounced between a VA seat and an agent two or more times | Threads (live); cards with human messages (dump) | Message order; sequence of who sent each human outbound message | Hard (the two); estimate (proxy) |
| E4 / E5 Clara joins late: tour done or application in | 45 of 658 cards (7%; 103 of 883) were created by an application; 18 came back with a new inquiry after touring or applying. Had Clara been switched on Sep 1, 61 of 87 open active cards (70%) had already toured or applied. Live: 0 of 10 | Cards (dump); the Sep 1 snapshot | "Created automatically via new rental application" notes, first 2026 create event only | Hard |
| E2 Renter booked on a listing site before anyone spoke | 0 showings without a staff actor. Tour requests before contact: Zillow "I would like to schedule a tour" on 31 cards, Zumper 3, Tenant Turner availability 6; 10 Tenant Turner cards booked by the agent with no logged outreach. Mirror: no renter text mentions self-booking | Showings and cards (dump); renter texts (mirror) | Actor on Showing Scheduled; text match on activity and inquiry text | Hard zero; the requests are estimates |
What the counts change: the import (PR 2) is the product at Situs, and a switch-on day is a mid-flow day (seven in ten open cards already past the tour), so phase 2 ships before anything renter-facing; staff moves are one booked showing in four, so PR 2b and the staff-move chain come next; listing-site import goes last. Caveats: AppFolio's confirm requests carry the agent's name and read as staff work; talk outside AppFolio is invisible, so "staff first" is a floor; the live sample is ten threads.
8. Risks and the double-message hazards
| Hazard | How it bites | Answer in this design |
|---|---|---|
| AppFolio's own messages on Clara's showings | Fede, Nov 2: AppFolio does ask the renter to confirm. The evidence says otherwise so far: the one Situs card Clara booked (Oct 31, tour Nov 2) carries no "scheduled" text, no "Showing Reminder" email and no confirm request by the time Clara's one-hour reminder had gone; its thread holds only Clara's texts. Staff bookings get the request 22 times in 23, 2.0–2.2 h before, under the assigned agent's name: the team's confirm habit is AppFolio's template | Designed for both. Phase 2's Willows run books through Clara and watches the bench phone; until then AppFolio item 2 stays on at Situs and Clara's reminders stay as they are |
| AppFolio auto-cancel an hour out | A Clara-booked tour the renter never "confirmed" to AppFolio would be cancelled under the team's name. Unknown: the toggle sits on a page the Clara AI user cannot open. Never seen: 0 auto-cancels in the 2026 dump, 90 unanswered requests still went on to "Showing Occurred", a DENY becomes "Canceled by Prospect", staff cancel the rest by hand | Go-live page item 3 goes off only when confirm-or-release is on; meanwhile a PMS cancel flows in as pms_auto_cancel: silent to the renter, host told once, re-offer through the cadence (yes/hold 3) |
| Clara's follow-up cadence after a staff booking | The cadence's "tour booked" stop reads PropFlow tours only. Staff book in the PMS, Clara keeps texting the renter; when the cadence ends with no reply it marks the guest card inactive. Since Oct 29, 2 of the 4 staff bookings at Situs were on threads Clara had already texted. Suspicion, not reproduced | PR 2's import trips the existing stop. Its replay checks every VA-booked Situs card for a Clara touch after the booking |
| A moved tour loses its reminders | Found in code today, not yet reproduced (a suspicion until the replay says so): no schedule version is ever written, and a reminder leg that already fired returns for good, so a tour moved after its day-before reminder gets nothing for the new time (every move: Clara's desk, staff, PMS) | PR 2b: the replay reproduces it first, then bump the version on every move and the legs re-arm |
| A renter confirms, then cancels, to AppFolio | On one Situs card the renter texted Confirm, then Cancel an hour later: AppFolio sent nothing, logged the showing as Occurred and emailed "Thanks for visiting" the next day; the host went for nobody | Clara reads a cancel in any words (the existing classifier) and tells the host; the baseline to beat, not the target |
| Echo loop | Clara's own showing comes back through the sync as "staff-booked" and mints a second tour. Today her rows are told apart by actor name only, and a real Situs record stored her showing as actor kind staff, name "Clara AI" | The drain stamps the showing id at write; import keys on it; slot match covers old rows |
| Imported tour that already happened | Link leg fires at once; the attendance gate says "send" two hours after the end; the walker re-sends off !applicationLinkSentAt for 7 days | 24-hour window in the one domain function both call (yes/hold 1) |
| Listing-site reminders | TurboTenant and Tenant Turner remind on their own; Clara would make it two | remindersBy: 'site' quiet skip until the customer turns the site's off (yes/hold 4) |
| Tenant Turner bookings land in a mailbox we do not read | Blind to most of them; Clara may still offer times. Size at Situs: 0 self-booked showings this year, 6 Tenant Turner availability requests, 10 Tenant Turner cards the agent booked with no logged outreach | Ask the Situs org admin where they land (go-live page); PR 6 parses what reaches leasing@; the PMS self-schedule path (PR 2) covers Zillow/AppFolio bookings |
| A listing-site "showing booked" notice read as a tour request | Today the notice is classed tour_request; when its body names a day and time the tour pipeline can create an auto-confirmed tour and send Clara's confirmation on top of the site's. Reachable in code, no fixture, untested | PR 6 turns the notice into ShowingObserved before the tour pipeline sees it |
| Supervised mode | Imported-tour renter messages held for review; a hold that outlives the tour | Same hold as any tour text: expires at the tour's start |
| AppFolio's post-showing email on Clara's showings | Proven double: Clara-created showing 597 drew AppFolio's "Thanks for visiting" email (planning note of Oct 19). It is a company-wide setting ("send automatic emails to guest cards after showings"), not one of the five per-showing boxes, so the robot cannot untick it; at Situs it also went to a renter whose application was already in | PR 7 is Clara's version; AppFolio item 4 goes off the day it is live. Until then a renter who replies to AppFolio's email is matched by address (PR 7) |
| Staff keep using AppFolio templates on Clara's cards | Two voices | A habit, not code; the visible claim on the card after Clara's first message (Oct 16 rule) |
| Missing property on the PMS showing | An early return drops the import or the next step: send-application-on-attended.ts returns no_property; the team-update and staff-alert senders and the "which tour did you mean" resolver bail the same way | Company-scope tour, host line to the company inbox (Nov 2 rule); the guards on this path go as touched, one PR each |
9. PR cut and proof ladder
| PR | What | Kind | Lines | Proof before merge → before on |
|---|---|---|---|---|
| 1 | Tour fields (external, importedAt, hostPersonId if the staff-move PR 1 has not landed; source already exists), readers, the drain stamping the showing id | Storage, no behavior change | ~300 | Byte-identical replay of every tour row at the three customers |
| 2 | Adapter: showing activity → ShowingObserved; renter resolved from the card; import/upsert into a tour with source: manual | external; confirmed start; PMS move / cancel / assignee (proposed, rule H) / CONFIRM / recorded outcome into the existing chokepoints; trigger pms; silent-renter rule; host line by email; the no_property exit removed | Fix (staff-facing; closes "Clara never learns of it" and the cadence texting a booked renter) | ~580 | Replay of the 24 Situs staff-booked showings and every "showing canceled" line since Oct 20, plus the VA-booked cards checked for a Clara touch after the booking → Willows: a hand-booked showing becomes her tour, host emailed, calendar written, bench phone watched for AppFolio's text → sandbox company in Situs's shape |
| 2b | A moved tour gets reminders for its new time: bumpVersion: true, version passed to the start, the two reminder legs re-arm after a reschedule | Fix, no switch | ~90 | Reproduce first: replay of October moves at Western Slope and Situs where the day-before reminder had gone out → Willows: move a tour after its reminder, the new slot gets one |
| 3 | Renter messages on adopted tours: the "you're set" line, reminders, the two quiet skips, the 24-hour window | New capability: company setting, default off | ~300 | Willows run with the setting on → on at Situs the day AppFolio item 2 goes off |
| 4–5 | Confirm-or-release (sub-design): the "reply yes" reminder, the host line at 30 minutes, no cancel | New capability: setting tourConfirmBeforeDriving, default off (Fede, Nov 2 row 7) | ~220 | Willows → on at Situs, then AppFolio item 3 off |
| 6 | Listing-site booked-showing parsers → ShowingObserved; the notice no longer enters the tour-request pipeline; remindersBy: 'site' | Fix (a booking Clara ignores, or doubles, today) | ~300 | Replay of the 2026 TurboTenant / Tenant Turner booking emails → Willows → AppFolio item 7 and the sites' own reminders off |
| 7 | Post-showing follow-up: thank-you + "how did it go?" in the first after-tour touch; replies to AppFolio's thread matched by sender address to the renter's conversation | Improvement to live behavior, no switch | ~250 | Replay of attended tours → Willows → AppFolio item 4 off |
| 8–11 | Staff moves and reassignment | As cut on that page | ~1,670 | That page's ladder |
Order for Situs: item 1 off now (4a is already off); PR 1–2 and the Willows run settle the AppFolio-confirmation question, PR 2b alongside; PR 3 with PR 4–5 retire items 2 and 3 together (a renter never hears two confirmations, nor none); PR 7 retires item 4; PR 6 last, for item 7.
Parked ideas (Nov 2 2026, Fede: no further work)
- Income vs budget slot in the call summary. The after-call summary has one money slot ("budget"), so an unverified income from a TurboTenant form was saved and emailed to the team as the renter's budget (Patricia, Barcelona, Oct 31; issue #24548). A fix adding a "stated income" slot was built and closed unmerged on Fede's "no thanks" (PR #24610, branch kept). Her record was left as is.
- Office-call notes reach the card ~30 min after the call (found Nov 2 on Matthew: the agent's 3:44 PM voicemail was logged on the card at 4:15 PM; the drain waits 15 quiet minutes so a transferred second leg joins the same entry). Option, not built: write the entry at once and let a later leg amend it. Fede's call.
Found Nov 2, lines only (Fede: no unasked PRs)
- Stephany's tour still says "confirmed": a known stale record. She texted at 2:32 PM that she could not make 3:00 and did not come; our tour (Lampliter, Nov 2, 3:00 PM, its original host) still reads confirmed and that host's calendar keeps the event. Fede, Nov 2: no hand fix; the row and the calendar stay as they are. What would have handled it: proposed rule R (read the renter's recent messages, held thread included, before any tour step) and the AppFolio showing sync above. Kept as a harness fixture (tour
64dfd201, threade379ef3e). What the stale "confirmed" set off: after the 3:45 PM "how was it?" email to the original host, the application link went to her at 5:15 PM (AppFolio's text and email, plus a card note); three "did you get a chance to apply?" texts are scheduled for about 4 PM on Nov 3, Nov 5 and Nov 9, and only the "human replied" hold on her thread stops them (if the hold is cleared first, they go). Nothing writes a no-show to AppFolio, and no report or call list goes out at Situs. - A renter who comes back on a closed card gets no reply from anyone. Avery, Lampliter unit 109, Nov 2 4:12 PM MT: her new Zillow inquiry landed on her July card that staff had marked "No Longer Interested". AppFolio sent no thank-you, logged nothing, told nobody; our sync mirrored the card as "not interested", so Clara's first email and follow-ups never fired. Rule, Fede yes (Nov 2 evening): new interest on an inactive card is a new lead (stage back to inquiry, lead event raised, first email on the normal path, the pre-go-live skip keys on the new interest's date). Fix PR building, no switch.
- The Slack arrival card showed July's message as today's. The card quotes the inquiry's first message; a re-inquiry carries none. Show today's text or "no message this time". Fede yes (Nov 2 evening); fix PR building, no switch.
- Signature block prints the property's main address, not the home's building. Lampliter is three buildings (375, 385, 395 S. Depew); Clara's email signature always says 375, so every email about a 385 or 395 home carries the wrong street number under her name (seen on Avery's unit 109 email, Nov 2; the body said 385 correctly). Fix: signature uses the unit's street address when one is known, else the property's. Fede yes/hold.
- Office-call notes: decided. Fede, Nov 2 evening: the call entry is written "as soon as we have the recording"; a second leg becomes a short follow-up entry (the card writer is add-only). Fix PR in flight, no switch. The 15-minute quiet wait for Clara's own conversation lines stays (origin PR #22676, Oct 27; no Fede decision on the number).
- The nightly leasing-quality eval is red, but Clara is not worse. Red since the Nov 2 nightly (132 pass, 108 fail, floor 78%). Cause: Oct 28 (#22827) Clara's prompt moved to Fede's "name one home, never a price range" rule; Oct 31 (#22766, Gera) the eval started grading the real prompt instead of an old hand-typed copy. About 97 of the 108 failures are cases that still expect the old "quote a range" answer; one grader demands the literal word "several". Production emails Oct 29 to Nov 2 (Situs, Western Slope) name one home as intended. The eval is not a merge gate. Handed to Gera's side on the bridge Nov 3 00:50Z: rewrite the range cases to the one-home rule and re-measure the floor; no revert.
- Every PR went red at midnight UTC Nov 3 because a date in the Temporal patch ledger test expired on purpose (its own design: re-answer the cohort question). Fix PRs #24674 and #24676 are open; our three PRs (#24663, #24668, #24672) re-run once one merges. Not ours to fix.
Design B: Opus 5.5 (full design, click to open)
Proposed, pending review · Nov 2, 2026 · Opus designer · read-only research: nothing sent, no PR, no setting changed · AppFolio behavior read from real Situs cards on Nov 2
1. One-pager
1. Today Clara runs only the tours she booked herself. At Situs in 2026 that is 0 of 495 showings: staff or the VA booked every one in AppFolio.
2. Fix: the tour workflow can start at any point. Every step checks "already passed, or already done by someone else" before it fires.
3. New: showings booked in AppFolio or on a listing site become Clara tours within 15 minutes. She puts each on the host's calendar, reminds, and follows up after. She confirms only if AppFolio did not, and never introduces herself again.
4. Threads: when a person answered first, Clara stays quiet until the renter has waited 15 business minutes with nothing promised, then picks up where the thread is.
5. North star: all seven AppFolio automatic messages off. Lead nurturing is already off at Situs; three more settings and one team habit can go today; each other one goes the day its replacement passes a Situs replay and a Willows run.
| Phase | What changes | PRs | Proof rung |
|---|---|---|---|
| 1. Fixes, no switch | A tour Clara learns about late never gets an early step. A call or note staff log on the card counts as a teammate stepping in; AppFolio's automatic texts never do (if the suspicion in section 8 reproduces). | 2 | Replay of every open tour run: identical except the listed late-started runs; replay of 440 Situs confirm texts; Willows late-start run and a logged call |
| 2. Showings booked elsewhere (company setting, off) | Staff, VA and renter-booked AppFolio showings and TurboTenant bookings become Clara tours with the right host. Moves, cancels and reassigns follow. Each goes on the host's calendar and blocks the slot in her availability. | 3 | Read-only replay of 495 Situs showings, zero duplicate tours; Willows: book, move, reassign, cancel in AppFolio; sandbox company in Situs's shape |
| 3. Replacements | After-tour text for every tour. Replies to AppFolio's "Thanks for visiting" email reach Clara. Clara picks up staff-started threads when the renter waits (setting, off). | 2 | Replay of 266 post-showing emails and 47 Situs text threads; Willows |
| Sibling pages | Confirm-or-cancel; staff tour move acceptance; group email threads | ~10 | On their pages |
Decide (yes / hold):
- Ship phase 1 as fixes, no switch? Recommend yes.
- Build phase 2 behind a company setting, default off, and turn it on at Situs after the Willows run? Recommend yes.
- On a tour staff booked, Clara sends her own confirmation only when AppFolio's did not go, puts it on the host's calendar unless an event already sits there, and emails the host only when someone else booked it for them (a card note either way)? Recommend yes.
- Clara asks a renter to confirm only where AppFolio does not: her own tours once a Willows run confirms what card 9022 shows (AppFolio sent nothing before the tour), and staff tours only when the confirm box was unticked. Otherwise she reads the CONFIRM or DENY reply? Recommend yes.
- Clara picks up a staff-started thread after 15 minutes of renter waiting in business hours (5 after hours), as a setting, default off? Recommend yes.
- Situs switches AppFolio's messages off in the order of the table below? Recommend yes.
North star: AppFolio's seven automatic messages, each replaced
Wording is AppFolio's own, from real Situs cards (October). Coverage counts Situs renters, not code paths: "live in code" is not "live at Situs". On the one Clara-booked card checked (9022), none of AppFolio's messages before the tour fired; its thanks email was not yet due. At Western Slope AppFolio did send that email, under Clara's name, for a showing we wrote (Oct 18).
| AppFolio message at Situs (verbatim; 2026 count) | Clara today | Situs coverage at 2026 volume, today → after | Build now | Proof rung | Off at Situs when |
|---|---|---|---|---|---|
| 1. Inquiry auto-response, within minutes of every inquiry, from the assigned agent, replies to that agent: "Thank You for Your Interest in 375 S. Depew St. … please reply to this email or call 303-789-3030 and press option #1 … We will also follow up with a call." 248 sent. Questionnaire: never seen on a card. | live for new cards first reply within a minute, with times. But in renter text threads since Oct 20 she was first in 1 of 47 (staff median 9 min); the reason is unmeasured | New guest cards: all (go-live page) → same, and renter replies reach her instead of one agent's inbox. Text threads: 1 of 47 first → the section 5 rule decides | Nothing | Running at Situs (go-live page, Oct 30) | Today |
| 2. Showing confirmation, the same minute staff book (form boxes ticked by default). Text: "Your showing has been scheduled: 8654 E. Richthofen Pl. Denver, CO 80220 Fri 10/16/2026 2:30pm MDT -- IH Holdings Six LLC". Email titled "Showing Reminder": "A showing has been scheduled for you on … If you have any conflicts or concerns, please reply to this email or call our office." 525 texts. No later reminder exists. | partial her own tours get a confirmation, a day-before and a 1-hour reminder (card 9022); staff-booked showings get nothing from her | 0 of 495 staff-booked showings → 495, plus two reminders AppFolio never sent | Phase 2. Her confirmation goes only when AppFolio's did not | Replay of 495; Willows | Phase 2 proven |
| 3. Confirm ask, about 2 h before (7 of 7 recent staff-booked showings), logged under the agent's name: "Please confirm your showing: 8654 E. Richthofen Pl., Denver Today at 2:30pm MDT Must reply with CONFIRM or DENY by 1:30pm." Instant replies: "Your showing has been confirmed: …" and "Your showing has been canceled: …". 440 asks. Auto-cancel of unconfirmed: unknown (never seen on a card; Clara's AppFolio user cannot open that setting). | missing no ask, no release; her 1-hour reminder is the only day-of touch | 0 of 495 asked by Clara → each tour asked once, by one sender | Confirm-or-cancel (sibling page). Launch: ask; if silent 30 min before, email the host once; no automatic cancel. Read CONFIRM and DENY replies | Replay of 440; Willows | Confirm-or-cancel proven. Both auto-cancel states designed (section 8) |
| 4a. Lead nurturing: 3 workflows, all drafts, none published; no leasing Realm-X flows | already off Gap on our side: her follow-ups stop once a person has replied | At most 11 of 47 text threads, the ones no person answered first (Oct 20 to Nov 1) → 47 of 47 | Phase 3 staff-thread pickup | Replay of the 47 | Nothing to switch |
| 4b. Post-showing email, about 23 h after (5 of 6 showings), even after the renter applied, from the agent: "Thanks for visiting 8654 E. Richthofen Pl. … 3 bd, 2.0 ba, 1,100 Sq. Ft. | $2,500 /mo … For more information or to apply, please contact us by responding to this email or calling us at (303) 789-3030." 266 sent. | partial after her own tours: "How was your tour of <home>? We hope you loved it! If you're ready to take the next step, you can apply here: <link>". Never after an application. AppFolio's thanks email also follows her own tours (Western Slope, Oct 18), so those renters get both today. A renter's reply to AppFolio's email lands on AppFolio's thread (Fede: in leasing@), not hers; the reply join covers only replies reaching a mailbox she reads | 0 of 266 → every attended tour; replies answered on the thread they arrive on | Phase 3: after-tour text for every tour origin; reply join. AppFolio still sends the application itself | Replay of 266; Willows | Phases 2 and 3 proven |
| 4c. Showing survey: the tab exists, zero responses ever | live in effect: the after-tour text asks how it went; the host gets the notes ask | Nothing to lose | Nothing | None needed | Today |
| 5. Hand-sent templates: "thanks for your interest" texts (103 typed by staff) and the VA's "Send Showing Invitation": "We received your inquiry for … and just wanted to check what date and time …" | live her first reply offers times | Team habit, not a setting | Nothing | None needed | Today (ask the team) |
| 6. Per-agent alerts in each agent's My Settings (only an admin can read them) | not needed the leasing inbox alert stays; tour notices email the host (Fede, Nov 1; being built) | Same | Nothing | None needed | Today |
| 7. Renter self-scheduling: AppFolio's prospect-scheduled showings (setting not readable), Tenant Turner on every property, TurboTenant | missing she cannot see these bookings | AppFolio self-booked: 0 → all (they are AppFolio showings). TurboTenant: 1 booking since May 20 → read. Tenant Turner: blocked until we know which mailbox gets its bookings | Phase 2 (TurboTenant in its third PR) | Replay; Willows with a forwarded sample | Phase 2 proven; Tenant Turner after the mailbox is found |
Stays on: the showing notice to current residents (ticked by default on the form) and AppFolio's application email.
2. The journey, with every way in
Clara can enter at any state. Numbers on the picture match the table.
| Way in | Clara does | Skips | Tells the host | Never repeats |
|---|---|---|---|---|
| 1. Staff or VA booked the showing in AppFolio, or told Clara it is booked | Makes it a tour with AppFolio's host (from a staff message, writes the showing first); puts it on the host's calendar unless an event already sits there; blocks the slot; reminds the day before and an hour before; after the tour asks how it went and sends the application link | Her confirmation when AppFolio sent one (the default); offering times; intro | One email when someone else booked it for them (the VA booked 288 this way); a card note either way | AppFolio's "scheduled" text; AppFolio's confirm ask (she reads the reply) |
| 2. Renter booked on a listing site | Same as 1, and writes the showing into AppFolio | Confirmation (the site sent one), offering times | One email: nobody on the team has seen it | The site's reminder, where the site still sends one |
| 3. Staff answered first | Waits; takes over when the renter waits 15 business minutes with nothing promised | First-reply text, intro, questions already answered | Nothing | Anything a teammate already said |
| 4. Tour already happened | Application link if attended and not sent; notes ask if the card has no note | Every step before the tour | Notes ask, once | The application, when AppFolio already sent it |
| 5. Application already in | Answers questions | Tour offers, follow-ups, application link | Nothing | Anything about applying |
| 6. Copied late on an email | Reads the whole thread; answers what she is asked; does what she is told | Team talk; intro | One line back to whoever asked | A teammate's words |
| 7. Staff moved, cancelled or reassigned a tour she booked | Re-times every step; host steps go to the new host | Her "moved" text when AppFolio already sent one | Old and new host: one email each (Fede, Nov 1) | A second confirmation |
| 8. Staff took the thread, then handed it back | Pauses conversation; resumes from where the thread is | Re-greeting | One line on hand-back | Questions already answered |
3. Temporal: one tour workflow from any state
Two parts. A start state in the workflow input, used only by the import (phase 2, behind its setting). And a catch-up check in every step, which is the fix: a late start, retry or replay skips a moment that has passed, and nothing new fires. So the workflow PR merges alone; its replay lists every open run that differs.
| Step (code) | Started late today | After |
|---|---|---|
Confirm nudges (Phase 1, tour-awaiting-confirm-nudges) | Runs a cadence read even for a confirmed tour | Skipped when the start state is confirmed or later |
| Booking confirmation (sent at booking, outside the workflow) | Not reached by a late start | Staff and listing-site tours: sent by the import only when the card shows no outside confirmation after one sync |
Day-before and 1-hour reminders (runReminderTimer, runReminderT1hTimer) | Already skip a passed moment | Same; also skipped when the outside system already reminded |
| Confirm ask (on this page) | Not built | Clara-booked tours: on once a Willows run confirms card 9022 (AppFolio sent nothing before the tour). Staff-booked: off when AppFolio's box was ticked, the default; she reads the reply. Its "silent 30 min before" host email belongs to the sibling page |
Host "they're coming" text at −30 (runHostConfirmNoticeTimer) | Sends on the renter's yes; a late yes sends at once | Same; a CONFIRM reply to AppFolio counts as the yes |
Staff check-in +30 and notes ask (runStaffCheckinTimer) | Fires at once | Skipped if more than 2 h past at entry |
Application link +60 (runAppLinkTimer) | Fires at once, after attendance | Once, if attended and AppFolio has not sent the application; skipped if the tour ended over 7 days before entry |
Calendar write retry (runCalendarWriteRetry) | Runs | Outside tours: writes the host's event unless one already overlaps that slot (staff add showings by hand). Never moves or deletes an event she did not make. Host with no connected calendar: the slot is blocked in availability |
| Post-tour follow-ups (Phase 3) | Park on a person-owned thread | Same |
| Concern | Design |
|---|---|
| Signals for outside changes | Reuse tourRescheduled and tourCancelled; add tourHostChanged {eventId, hostPersonId, at}. Host steps move to the new host; renter steps stay. |
| Idempotency: import | Conditional put on the outside id (appfolio:situsgroup:612), so two sync passes cannot make two tours |
| Idempotency: Clara's own showing read back | The drain writes the outside id on her tour; fallback match: same person, same home, within 30 minutes |
| Idempotency: outside change | eventId = ext:<id>:<hash of day, time, status, host>; the workflow's existing event dedup drops repeats |
| Idempotency: each send | Existing row stamps (day-before, 1-hour, application claim, host notice) and call-time activity keys; unchanged |
| Versioning | One patched('tour-start-at-state') read once at the top; old runs replay unchanged |
| Start | Workflow id stays the tour id; the import starts with signal-with-start, so a running workflow gets signals, never a second run |
| Delay | AppFolio guest cards are read every 15 minutes today (adapter comments), so an outside showing reaches Clara within about 15 minutes. One showings report per account per tick keeps within AppFolio's request budget |
| File | Change | ~Lines (est.) |
|---|---|---|
src/lib/temporal/workflows/tour-workflow.ts | Optional startAt {state, origin, outsideNotices} input; marker; Phase 1 skip; catch-up call per step; tourHostChanged | 220 |
src/lib/temporal/workflows/tour-timers.ts | legDueState(fireAtMs, nowMs, graceMs) → future / due / passed, and one grace per step | 40 |
src/lib/temporal/workflows/tour-skip-reasons.ts | Quiet skips late-entry-passed, done-elsewhere | 6 |
src/lib/temporal/tour-client.ts | startTourAtState(), signalTourHostChanged() | 60 |
src/lib/temporal/activities/tour.ts | Host steps and the calendar write read the host from the tour; the write first checks for an event already in that slot | 70 |
src/lib/data/types.ts | Tour fields in section 4 | 30 |
src/lib/domain/leasing/tour/outside-showing-import.ts (new) | Upsert by outside id, echo guard, signals, card note | 300 |
src/lib/domain/person-picture/staff-touched-since.ts | A call or note logged on the card counts as staff engaged; AppFolio templates never do | 60 |
src/lib/domain/calendar/check-availability.ts | Outside showings count as busy for that host and home | 60 |
src/lib/domain/pms/guest-card-writer.ts + AppFolio adapter | Neutral outside-showing read (section 4) | 190 |
agents/clara/lib/email/parse-lead-source.ts | TurboTenant booking and reminder emails | 150 |
4. Data model
Core code reads only these neutral fields. AppFolio names stay in the adapter. Today's Tour.af field, an AppFolio name in core, becomes outside.
| Field | Values | Written by | Read by |
|---|---|---|---|
origin | clara · staff · listing_site | Clara's booking; the import | Start state, never-repeat rules, notices |
hostPersonId | A person with a leasing role at the company | Clara's booking; the import maps AppFolio's assignee to a person by role (proposed, rule H); else the company rotation | Host steps, availability |
outside.id | Provider-prefixed: appfolio:situsgroup:612, turbotenant:<id> | Adapter, parser, the drain after Clara's own write | Echo guard, change detection |
outside.seenHash | Hash of day, time, status, host | Import | Change → signal |
outsideNotices | confirmation · confirmAsk · reminder · afterShowing: what the outside system already told the renter | Adapter turns AppFolio's automatic texts into neutral notices | Never-repeat rules |
| Boundary record (adapter output) | OutsideShowing {id, personRef, homeRef, startsAt (with zone), minutes, status: scheduled | cancelled | no_show | completed | other, hostRef, createdByRef, updatedAt, confirmAsked: yes | no | unknown} | AppFolio adapter; listing-site parsers. Today's card showing has no host, creator or update time; the adapter adds them | The import only |
| Thread link on the conversation | {threadRef, kind: email | pms_text, joinedBy: copied | reply_to_pms_email | forward, members} | Inbound email; mirror | Section 5 rules |
A showing whose home does not map still imports at the company, in the company's time zone, and the host is asked which home. A missing property never drops it.
5. Who goes first, and joining late
At Situs, staff or the VA answered first in 36 of 47 text threads since Oct 20 (median 9 minutes). Clara keeps going first on new leads. Once a person has the thread, these rules apply. They extend today's follow-up check and first-touch decision; no second engine.
| Situation | Clara | Her first line | Today |
|---|---|---|---|
| New lead, nobody has answered | speak within a minute | First reply, as today | Live |
| A person answered first and the renter has not written since | quiet | None | Live for replies (she stands down after a staff text) and follow-ups (the cadence ends for good). First reply: decided, only logged |
| Renter's newest message waits 15 min in business hours (5 after hours), no promise pending | speak (setting, off) | Picks up the thread's state. No intro, unless she writes from a number or address the renter has not seen: then one clause, "This is Clara with the leasing team" | New (PR 7): the ended cadence becomes a wait that wakes on the renter's next message |
| Staff logged a call or note on the card, or texted Clara about it (Oct 14, 21, 28) | quiet; reads it first | Picks up from the note; never re-offers what it says is done | Not a signal: the follow-up check reads only messages (fix: PR 1) |
| Staff promised something ("I'll send times tomorrow") | quiet up to 24 h, then nudges that teammate, not the renter | None to the renter | The 24 h wait is live; the nudge is new |
| Staff said "I've got this one" | quiet in conversation; reminders keep going unless staff say stop | None | New: the staff text line books, moves, cancels and takes notes, but has no stand-down |
| Staff hand back ("Clara, take it from here") | act, one line back to staff | Continues from the thread's state | New |
| A card a teammate made by hand (Oct 14: "likely is not at the beginning") | Decided on the lead-source page: Fede asked on Oct 14 for one signal-based check, with who made the card as a signal | None until decided | First-touch decision only logged |
| Copied late on an email | Speak, act or stay quiet by the group-thread rule (VA POCs page) | Answers only what she was asked | Sibling page |
| Renter replies to AppFolio's automatic email (lands in leasing@ on AppFolio's thread) | speak on that thread if no person replied in 15 min | Answers the question; application link if asked | New (PR 6) |
| AppFolio template sent under a teammate's name (CONFIRM/DENY, "scheduled") | Not a person: never silences her | None | Suspected to silence her today (section 8; PR 1) |
Proof: replay the 47 Situs threads. Count where Clara would have spoken; the bar is zero times within 5 minutes after a staff reply.
6. What Western Slope told us
Since September their staff act first, by hand: they book in AppFolio or by phone, call leads Clara is working, and own any thread they step into. Clara does not see it, so she starts over or repeats. Quotes are raw transcript lines; a speaker is named only where the transcript names one.
| Date, who | What they said or did | How this design answers | Source |
|---|---|---|---|
| Sept 9, Jason | The VA's habit: "hey, Kat, I just booked you a showing in an hour, let me know if you need to cancel it." | Way in 1 tells the host the same way: one email when someone else booked it | Sep 9 |
| Sept 16, Jason's mic (likely Kat) | AppFolio's schedule-showing button "used to automatically put it onto my calendar. It's not." | Way in 1 puts every adopted showing on the host's calendar | Sep 16 |
| Sept 16, Jason's mic | AppFolio will send "a confirm or deny ... that's already happening"; it reminds "even after a showing ... It's weird"; "I set that up, I can turn that off" | One confirm ask and one after-tour message per tour, from one sender (north-star rows 3 and 4b) | Sep 16, later |
| Sept 16, Jason | Kat out: "let's just book it to Jonathan right now." | Way in 7: a host change moves the host's steps and calendar event; the renter's steps stay. Finding the cover: VA POCs page | Sep 16 |
| Sept 21, Fede (our debrief) | "a human went in and ... scheduled another tour for the same person that already had a confirmed tour." | Same renter and home: the newer showing moves the open tour; the host hears once (section 8) | Sep 21 |
| Sept 22, said to Kat (transcript label: Jason) | "you don't respond to the email if you see it at leasing, you wait to see Claire taking it." Then: "you're quicker than Clara." | Clara answers new leads within a minute; when a person still answers first, section 5 | Sep 22 |
| Sept 22, Jason; Jason's mic (likely Kat) | Staff must "see what Claire has done ... pick up where Claire left off", "some of the most friction". "once I jump in, does she? She doesn't jump back in." | Section 5: quiet while a person has it; back on hand-back or when the renter waits | Sep 22, later |
| Sept 22, Jason's mic (likely Kat) | "the two that I just answered to before Clara got to it ... I clicked schedule showing ... but it did not show up on my calendar." | Way in 1: a Clara tour within 15 minutes, on the host's calendar | Sep 22 |
| Sept 23 to 24 | Kat booked a renter's afternoon showing herself in AppFolio. Fede deferred detecting this until a second hand booking in AppFolio came within two weeks; the next on record is Oct 16 (below) | Phase 2 is that deferred item | Go-live tracker |
| Oct 14, Jay's mic (Jay and Kat); Fede | "I already scheduled it verbally with the person"; Kat "doesn't want to trigger Clara going all the way from the beginning"; "I already did the showing." Fede: "Either advance them to the stage or don't engage at all." | The start state (section 3): the tour starts at booked or done, with no first steps | Oct 14, later |
| Oct 14, Jay (shared mic) | "Don't reach out to him like he's a brand new lead." On a hand-made card: it "likely is not at the beginning." | No intro on a late join. Hand-made cards: the lead-source page decides (section 5) | Oct 14, later |
| Oct 14 and 21, Jay's mic; Western Slope (speaker redacted) | Clara texted to schedule "20 minutes after Kat just recorded that she had a phone call with her." Oct 21: Kat should call good leads herself, and Clara should not then ask "should I turn off your guest cart?" | A logged call or note, or a staff text to Clara, counts as a person stepping in (section 5, PR 1) | Oct 14, Oct 21 |
| Oct 15 to 18, Kat and us | Kat asked Clara by text to book Tiffani; Clara's write failed; Kat made the showing herself; our by-hand re-send then made showing 597 beside hers. The double booking came from us. AppFolio then sent "Thanks for visiting" for both, one under Clara's name | Phase 2 reads showings back; every write, re-sends included, checks the slot and the outside id first; message 4b goes off with phases 2 and 3 | Oct 19 inspection page (production timestamps) |
| Oct 19, Kat, by email | "We always do a showing in person or virtual before we send them an application. We don't want people spending the money if they haven't seen it first." | Application link only after the tour, for every tour origin | Kat's email, quoted on the Oct 19 inspection page |
| Oct 20, Kat | Moved Spencer's tour to 9:15 by staff text; Clara's next notice still said 9:00 | Way in 7: every step reads the tour's current time | Western Slope inspection, Oct 19 to 20 |
| Oct 21, Western Slope (speaker redacted) | The hand-off is "my big thing". "But if it doesn't make it into AppFolio, Clara has no way of knowing, period." | Phase 2 and section 5: what staff do in AppFolio reaches Clara within 15 minutes | Oct 21, later |
| Oct 21, Western Slope (speaker redacted) | "hey, Clara, I already scheduled this showing, can you just put it on the calendar with this person? And Clara reached out to schedule the showing." | Way in 1 and the start state: she records it, puts it on the calendar, never offers times | Oct 21 |
| Oct 7 to 28, notes | Oct 7: "Now Clara's doing it." Oct 21 (speaker redacted): "Clara texting her and asking her for showing notes is a fantastic example of something that is working well." Oct 28 (likely Kat): "She texts me after every showing to ask me what notes to put in." Oct 14: asked again half an hour after Kat gave notes; Fede: "it shouldn't ask you again" | Kept and extended to tours staff booked; skipped when the host already gave notes for that renter | Oct 7, Oct 14, Oct 21, Oct 28 |
| Oct 28, Western Slope (speakers redacted) | "as soon as someone steps into the conversation, Claire just takes the backseat"; "what's the trigger point if [Kat] wants Clara to then resume with that guest card". Two of them disagreed on whether whoever steps in owns it "maybe up until a certain point" | Section 5's triggers: staff hand back, or the renter waits 15 business minutes with nothing promised | Oct 28, later |
| Nov 1 | AppFolio refused Clara's showing because the host had no AppFolio access to that home; it was written unassigned | The host field maps AppFolio users to people; the import reads the host back | Production alert, Nov 1 |
7. How often each way in happens (Situs)
| Way in | Count | Window and source |
|---|---|---|
| Showing booked by staff or VA in AppFolio | 495 of 495 | Jan 1 to Sep 4, 2026; 884 guest cards, robot dump |
| AppFolio confirm ask sent (all logged under a teammate's name) | 440 | Same |
| Of all asks: renter confirmed · no answer, tour happened · DENY | 45% · 29% · 10% | Same dump, 452 asks 2022 to 2026 |
| Showing moved · cancelled by staff · cancelled by renter | 78 · 66 · 53 | Jan 1 to Sep 4, 2026, dump |
| Showing handed to another agent | 39 (35 cards) | 2026 to Nov 1; Fable inbox study |
| VA booked onto an agent's calendar | 288 | 2026 to Nov 1; Fable inbox study |
| Staff or VA answered a text thread first | 36 of 47 | Oct 20 to Nov 1; Fable inbox study |
| Clara wrote in a staff-touched renter thread | 10 of 150 | Oct 20 to Nov 1; Opus inbox study |
| Teammate email to a renter, leasing@ copied, skipped by Clara | 72 | Oct 25 to Nov 1; Clara's log (234 such emails in 2026) |
| Showings staff booked in AppFolio since go-live | 42 | Oct 20 to Nov 1; Opus inbox study, unverified |
| TurboTenant "New lead!" emails · booked showings | 119 · 1 | May 20 to Nov 2; go-live page |
| Tenant Turner booking that reached leasing@ | 1 | 2026; go-live page |
| Showing Clara wrote that got an AppFolio message: before the tour · after it | 0 of 1 · 1 of 1 | Situs card 9022, read Nov 2 (its tour was that morning, so the thanks email was not yet due) · Western Slope showing 597, "Thanks for visiting" under Clara's name, Oct 18 (inspection page) |
Order by frequency: staff-booked showings first, then staff-first threads, then moves and cancels, then late email joins.
8. Risks and double messages
| Hazard | What the renter would get | Guard | Status |
|---|---|---|---|
| AppFolio confirms or asks on a Clara-booked showing (Fede's concern; the robot's "notify off" comment was unproven) | Two confirmations, two confirm asks, or two after-tour messages | Before the tour: on Clara-booked card 9022 AppFolio sent nothing. After it: AppFolio's thanks email did go, under Clara's name, for a showing we wrote at Western Slope (Oct 18). A Willows run checks both before her confirm ask goes live; if AppFolio asks, her ask stays off and she reads CONFIRM and DENY. The thanks email goes off at Situs with message 4b | before: holds on 1 card · after: AppFolio sends |
| Auto-cancel of unconfirmed showings (state unknown) | A tour released at the deadline, an hour before | If on: arrives as an outside cancel; no 1-hour reminder; host told once; renter offered the next time once. If off: nothing changes | both cases designed |
| Confirm asks logged under the host's name (440 of 440 in 2026) | If the inbox mirror files them as staff, Clara goes quiet on tour day | Reproduce on Willows first; if it reproduces, AppFolio templates become notices (phase 1) | suspicion |
| A renter texts "Cancel" after "Confirm" | AppFolio ignores it, logs the showing as occurred and sends the thanks email (card 9002) | Clara reads the text as a cancel and tells the host; AppFolio's "occurred" is never attendance | Designed |
| Replies to AppFolio's emails go to one agent's inbox (the auto-response replies to the agent; the thanks email comes from the agent with no reply-to; Fede: thanks-email replies land in leasing@) | An answer only that agent sees | The reply join covers only replies that reach a mailbox Clara reads. Switching each email off is the full fix: message 1 today, 4b after phases 2 and 3 | verify where replies land |
| Import sees a showing before AppFolio's "scheduled" text is read | Two confirmations | Decide the confirmation one full sync after the showing is first seen, or read showings and texts in the same pass | verify which read carries the texts |
| Listing-site reminder (TurboTenant "Showing Reminder") | Two reminders | Clara skips her day-before reminder on that tour until the site's is off | Designed |
| Clara's own showing read back | A duplicate tour | Outside id; fallback: same person, home, 30 minutes | Designed |
| Tenant Turner marks cards Inactive or Cold (851 times in 2026) | Clara might stop on a live lead | A listing site's status change is not the renter's decision; check which statuses stop her | verify |
| No attendance on a staff-booked tour | No application link | Use the host's notes reply; else today's fallback (end + 2 h) | verify |
| Staff book a second showing for a renter with an open tour (Sept 21: the VA did this at Western Slope) | Two times and two sets of reminders | Same renter and home: the newer showing moves the open tour and the host is told once; Clara cancels the older showing only if she made it. Different home: a second tour | Designed |
| Staff already put the showing on the host's calendar by hand (Sept 16: Bev "just looks at your calendar and she puts it on there") | Nothing; the host would see two events | No write when an event already overlaps the slot; she never moves or deletes an event she did not make | Designed |
| Rently at Western Slope (Oct 13, our team: showings booked "in AppFolio or rentally" don't come back; transcriber's spelling) | A tour Clara never sees | Find where its bookings land; then the same import as TurboTenant | verify |
| AppFolio's showing notice to current residents | None from Clara | Stays on in AppFolio; Clara never sends one | By design |
9. PR cut and proof ladder
This page cuts seven PRs in three provable units. About ten more live on the sibling pages and are not re-cut here.
| # | PR | Kind | ~Lines | Proof rung | Unlocks at Situs |
|---|---|---|---|---|---|
| 1 | Who counts as a teammate: a call or note logged on the card does; AppFolio templates never do (that half only if section 8's suspicion reproduces) | Fix, no switch | 140 | Replay of 440 confirm texts; Willows, with a logged call | Nothing to switch; protects tour day; no follow-up right after a staff call |
| 2 | Tour workflow: start state, catch-up check per step, host-change signal | Improvement, no switch; dark until PR 4 uses it | 380 | History replay of open runs: identical except listed late starts; Willows late start | None alone |
| 3 | Neutral outside-showing read (host, creator, update time, status, confirm box) and tour fields | New capability, storage, dark | 420 | Read-only replay of 495 Situs showings | None alone |
| 4 | Import: new showing → tour; changes → signals; host's calendar (no write if an event is there) and slot; card note | New capability, setting leasing.adoptOutsideShowings, default off | 600 | Willows: book, move, reassign, cancel in AppFolio; sandbox in Situs's shape | Message 2; AppFolio self-booking (7) |
| 5 | TurboTenant booking and reminder emails → outside showing (Tenant Turner once its mailbox is known) | Same setting | 250 | Replay of the May 20 booking; Willows with a forwarded sample | Message 7 for TurboTenant |
| 6 | After-tour text for every tour origin; replies to AppFolio's emails reach her thread | Fix (reply join), no switch; staff tours ride PR 4's setting | 260 | Replay of 266 post-showing emails; Willows | Message 4b |
| 7 | Pick up staff-started threads after the wait: the follow-up check's "teammate answered" stop becomes a wait that wakes on the renter's next message | New capability, setting leasing.pickUpStaffThreads, default off | 300 | Replay of 47 threads, zero collisions | Message 4a |
Referenced, not cut here: confirm-or-cancel (on this page; unlocks message 3), staff tour move acceptance (4 PRs, Proposed Nov 1), group email threads (3 PRs, VA POCs page). The staff-booked showings page, when it lands, owns PRs 3 to 5; whichever cut it settles wins.
Ladder for every unit: replay of real Situs records → Willows live run on AppFolio's test property → sandbox company in the centralized shape (new capabilities only) → Situs switches off the matching AppFolio message.
| Dark-features register lines |
|---|
Dark: leasing.adoptOutsideShowings | feature: showings booked elsewhere become Clara tours | proof: pending | turn-on: Fede |
Dark: leasing.pickUpStaffThreads | feature: Clara picks up staff-started threads when the renter waits | proof: pending | turn-on: Fede |