1. Architecture at a glance — core vs. the centralized layer
The kitchen never changes. A host stand only gets added for a restaurant big enough that someone has to decide which server takes which table.
- Core loop — calendar → offer → book → confirm → remind — unmodified for every company.
- Centralized layer sits between "open slots" and "book," deciding who goes.
- Off by default; on per company, Western Slope first.
- A future presence signal (Orca) is a dashed, unbuilt input.
Proves: everything below is either the unchanged core or one box in this diagram.
2. The invariant, and the five questions it hides
Clara is a vending machine: she asks for times and takes what drops out. She never opens the machine to see how it's stocked.
- Whose free time? Merged free time of people who show homes, not one calendar.
- Who shows up? Decided by a company rule at booking, not left blank.
- How long? Grows with homes shown, not fixed.
- How soon? A per-company minimum heads-up, not "now."
- How many homes? Up to three in one visit, not always one.
Proves: today's code answers all five by accident. File map in A4.
3. Today, end to end — and where it is single-by-construction
A single-lane bridge: plenty of stages, but two of them only fit one car through at a time.
- Seven stages, from Clara's tool call to the PMS guest card.
- Two chokepoints assume exactly one calendar and one notification address.
- Ten places in the code can only hold one answer — full table in A4.
- PR #7615 tried the person-level read once — closed same day; see A5.
Proves: sync-tour.ts, provider/resolve.ts, notification-recipient.ts — line-by-line map in A4.
4. The model: people own calendars, code owns choice
A taxi dispatcher doesn't own the cars — the drivers do. Dispatch only decides who takes the fare.
- A calendar belongs to a person — Gera's model already says "a person is a hook too."
- A company's shared calendar, if it has one, becomes one more pool member.
- The walk changes from stop-at-first-hit to collect, ending in "nothing" if no rung has one.
- Western Slope is a pool of one; Situs becomes a pool of three — same code path.
Proves: matches Gera's portfolio design; needs his confirm on placement (D1, A1).
5. Western Slope, the first case
The company opens the front door once; each agent still carries her own house key.
- Fede's ruling, 2026-09-14: admins approve the company mailbox and calendar (leasing@) once — both permissions held in reserve, the calendar may go unused for now.
- Leasing agents connect their own calendars; Clara books tours off people's calendars, not the building's. One agent today, Kat; several when there are several.
- Admins never connect a personal calendar or mailbox — their account page shows company status only.
- Verified against production code, 2026-09-14: booking still reads the building's calendar, then the company hook, never a person's, and no tour carries a host — so Kat's connected calendar sits stored but unused until ladder steps 1–2 ship.
- Origin: on 2026-09-11 the admin hit Microsoft's admin-approval wall on the company step, connected his own personal calendar instead — which nothing reads — and Microsoft later revoked it while the card kept saying Connected.
Proves: go-live tracker row te-people-calendars turning to tested, with a real tour landing on Kat's calendar.
6. The schema, in Gera's shapes
Like adding new labels to file folders that already exist — no new cabinets, no new filing system.
- No new tables — every row is a hook on a company, building, or person.
- Calendar, assignment, schedule-rule, and per-company tour settings are the four core rows.
- Holds are keyed by calendar, not building, so two buildings can't double-claim one calendar.
- Four optional fields land on the tour; a tour written before this exists reads unchanged.
Proves: row shapes follow Gera's solution section; full row-by-row table in A4.
7. The resolver — one pure function, a fixed fold order
A recipe followed the same way every time: same ingredients in, same dish out, no guessing.
- One pure function: reads rows, writes nothing, calls no model, uses no randomness.
- Walks the calendar chain, reads policy, then collects the pool.
- Sizes the visit and merges free time before applying the heads-up cutoff.
- Ranks last — rule order, area, soonest, priority, id — where pooling choices differ.
Proves: unit-testable to the minute; two runs on the same data give the same answer.
8. The heads-up rule — one hour, per company
A restaurant reservation minimum: you can't book a table five minutes before you walk in.
- One setting per company, overridable per building; default 60 minutes.
- Never told to the model — the times Clara is given already exclude it.
- If the heads-up eats every slot, she offers the next real one, never silence.
Proves: Western Slope call transcript, 2026-09-09 — quotes in A2; no competitor ships a sensible default (A6).
9. The duration rule — the visit grows with what there is to see
Packing a suitcase: one shirt takes a minute to fold, five shirts take five.
- Visit length = minutes-per-home × homes, plus travel if addresses differ.
- Computed before free time is merged, so 30 minutes needs 30 contiguous free minutes.
- Travel ships as a flat 30-minute buffer first; real distances come later if that's wrong.
- No mainstream scheduler sizes a visit by its contents — this looks like ours alone.
Proves: Jason's own bug report on the client call — quote in A2; competitor survey in A6.
10. Two homes, one visit
Like one doctor's appointment that covers two check-ups back to back, not two separate visits.
- One visit, one calendar event, up to three homes on it.
- Canceling the visit cancels all of it — it's one tour, not several.
- The primary-home field stays; extra homes ride as a new list beside it.
- Rently's own data: 2.6 units per self-guided tour — the best real number available.
Proves: Jason's request on the client call (A2); only Rently batches units today (A6).
11. The pool: merge, share, or rotate
Three doctors sharing one waiting room beats three doctors each locked to their own — merging free time beats either extreme.
- Three options: merge everyone's free time, one shared calendar, or rotate by rule.
- Recommended: merge — two part-timers add up to full coverage, unlike the other two.
- A pool of one isn't a special case of merge — it's the general case.
- Proximity ranking, via an OSRM drive-time matrix, breaks ties within the merged pool.
Proves: Fede's original question; full comparison table in A4; competitor survey in A6.
12. Cover, reassignment, and the confirmation
A relay race baton pass: the runner who's out hands off automatically, nobody stops to ask who's next.
- The invite now goes to the actual assignee, not one address per building.
- No buttons: the engine re-hosts on its own when someone's out, declines, or leaves.
- A booked visit keeps the rule it was booked under — a rota edit doesn't rewrite the week.
- Staff can text Clara to trade a tour; she can rebook the prospect herself if nobody covers it.
Proves: the Situs incident, 2026-09-01 transcript (A2) — the exact gap this closes.
13. Lockbox / self-guided showing
A hotel keeps its front-desk process but hands over a keypad code instead of a bellhop.
- Per-property setting, not company-wide — not every home has a lockbox.
- Recommended: scheduled but unassigned — reuses the booking loop, skips only the assigner.
- Identity must be verified before a code is released — phone match at minimum.
- Start with Tier 2 (existing lockbox); Tier 1 (smart lock) is the later upgrade.
Proves: Situs's leasing director's own request; six-product comparison in A3.
14. The double-book race, and what we already have
Two people reaching for the last parking spot at once — one has to lose cleanly, and be told why.
- A short-lived hold keyed by the calendar is the missing piece.
- One authoring function for every tour write — one place to add the guard.
- Write-then-read-back against the provider catches a collision the calendar itself sees.
- Microsoft Bookings is the only competitor that documents this: first writer wins.
Proves: existing durable-workflow-per-tour and single-author pattern; competitor behavior in A6.
15. Writing the visit back to the PMS
Updating your own calendar but not the shared family one — miss it and someone shows up confused.
- We already write guest cards; recording the showing itself is what's missing.
- The assignee has to survive the write — needs a mapping to the PMS's own user.
- A three-home visit likely writes as three linked showings, one per unit.
- Confirm that multi-home shape against AppFolio's API before the step, not during it.
Proves: every competitor treats PMS sync as table stakes — sources in A6.
16. What Camellia keeps, unchanged
A renovation that never touches the load-bearing wall a tenant already lives behind.
- Single-building customers never enter this engine — the walk stops at step 1.
- The building's own calendar is the pool; "whoever owns it" is the rule.
- Heads-up and length fall back to today's constants where no row exists.
- Gate: a replay of real Camellia tours must be byte-identical, or the step doesn't ship.
Proves: replay test against production tour history.
17. Presence and Orca
A rideshare app assigning the nearest driver instead of a fixed queue.
- Orca is a real staff-location app, used today only for maintenance/mileage tracking.
- Extending it to leasing/touring is Fede's idea — neither client has asked for it.
- The resolver already reserves one input slot for a future presence signal.
Proves: Situs transcript — Orca mentioned only for maintenance (A2); full detail on the Orca Staff Location page.
18. The change ladder
Climbing a ladder where every rung is tested for weight before the next one is trusted.
- Ten steps, each ships dark, each with a single exit test before the next opens.
- Steps 1–2 are all Western Slope needs; 3–8 are what Situs needs.
- Fede's one-week plan: roles → pool → proximity assigner → staff-text reassignment → harness.
- Situs onboards in two weeks; Gera's program ladder prices this for November — a scheduling call.
- Status, 2026-09-14: steps 1 and 2 are in flight, dark, PR pending for Western Slope; step 3 (merge across several agents) is next, supported by the design, but not needed while Western Slope has one agent.
Proves: full ten-step table with exit tests and owners in A7.
19. Decisions for Fede
A menu with the chef's recommendation starred — pick a number, or trust the star.
- D1 calendar placement: recommend collecting person + company-shared calendars — needs Gera's sign-off. Decided by Fede, 2026-09-14: people's calendars; the company (leasing@) calendar stays as reserve/fallback.
- D2–D3 availability & reschedule: merge everyone's free time; keep the same person on reschedule.
- D4–D6 timing & caps: 60-min heads-up, flat 30-min travel buffer, cap at three homes.
- D7–D8 notice & Situs launch: invite goes to the assignee; give Situs PropFlow calendars day one.
- D9–D11 lockbox & settings: scheduled-unassigned, static-code tier first, plus two new settings rows.
Proves: full options and reasoning for all eleven decisions in A1.
20. Risks
Checking the weather before the climb, not after the ladder's already up.
- Reopens a same-night ruling — needs Gera's sign-off before step 2 starts.
- Read/write split already failed once (PR #7615) — step 2's exit test requires both together.
- Latency: a pool of three must not triple call-setup time.
- A calendar we can't read must never look "free" — HubSpot's fail-open is the mistake to avoid.
- Situs arrives in two weeks; the program ladder prices this for November — decide sequencing now.
Proves: full seven-risk table with mitigations in A5.
A1 · Appendix — decisions in full
Every option considered for all eleven decisions, with the recommendation and why.
| Decision | Options | Recommendation |
|---|---|---|
| D1 · Where a calendar lives | (A) On people only; the company keeps the mailbox for sending and has no calendar. (B) On the company only — what shipped tonight, and what the dated ruling says. (C) Both: the walk collects a pool of the company's leasing people plus the company's shared calendar if it has one. | C. A throws away a row that already works and that some companies genuinely want — a shared leasing desk is a real way to run an office. B cannot express "her calendar is the availability", which is exactly Western Slope. C is one word in the walk (collect, not stop), needs no migration, and makes the pool-of-one and the pool-of-three the same code path. Needs Gera: this refines his dock row from "a calendar attached to the org" to "attached to the org, a building, or a person", and reopens the 2026-09-10 ruling that said per-org, not per-officer. DECIDED by Fede, 2026-09-14: C — people's calendars; the company (leasing@) calendar stays as reserve/fallback. |
| D2 · What "available" means with several people | (A) Merge everyone's free time, pick the host at booking. (B) One shared calendar, nobody assigned. (C) Rotate by rule, offer only whoever is up. | A. It is the only option where two part-timers add up to full coverage, and it is the only one where a pool of one is not a special case. B stays available as a company setting. C is a fairness mechanism wearing an availability costume — it makes us book fewer tours to solve an internal problem. |
| D3 · Reschedule keeps the person, or re-picks | (A) Keep the original person if she is free at the new time. (B) Re-pick from the rule every time. | A. The prospect has already spoken to someone; handing the visit to a stranger because the time moved by an hour is worse for the lease. Calendly ships exactly this as a toggle, defaulting the other way — we should default to keeping the person. |
| D4 · The heads-up default | (A) 60 minutes, set on the company, overridable per building. (B) One number for everyone. (C) Per tour type — zero for a self-guided tour, longer for an agent-led one. | A now, C when self-guided tours exist. Jason asked for "at least an hour notice" and Fede promised configurability in the same breath; A is literally what was said on the call. |
| D5 · How travel time is counted | (A) A flat buffer: 30 minutes between addresses, zero at the same address. (B) Real distances from a table of minutes between properties. | A first. B needs a filled-in distance table nobody has, and Western Slope's spread — Grand Junction to Ridgway is not a 30-minute hop — will tell us fast whether the flat number is wrong. Ship the number, watch it fail, then buy the table. |
| D6 · The multi-home cap | (A) Three. (B) Two. (C) No cap. | A. The only real-world number available says 2.6 units per batched tour. Three covers it; no cap turns one no-show into a wasted afternoon. |
| D7 · Who is told | (A) The invite to the assignee; the company mailbox keeps sending the prospect's confirmation. (B) Everyone in the pool. (C) The company address only, as today. | A. B is how a pod learns to ignore notifications. C is the current bug. |
| D8 · Situs's calendars at launch | (A) Give all three a PropFlow-hosted calendar on day one; swap in their real ones whenever they connect. (B) Wait until they connect Outlook or Google. | A. They have no calendars today, and B makes their launch wait on an IT task at the client. The row is the same row either way. |
| D9 · Lockbox self-showing default | (A) Open access, no schedule. (B) Scheduled, unassigned — slot booked on the pool calendar, no host. (C) Hybrid — self by default, agent-led on request or for lease signing. | B. Reuses the existing slot/booking loop and skips only the assigner step. C matches Situs staff's own wish to be present for signings, worth revisiting once B ships. |
| D10 · Lockbox access tier to build first | (A) Tier 1, smart lock — one-time codes, auto-expiry, vendor audit trail. (B) Tier 2, static code — existing lockbox, controls live on PropFlow's side. | B. Western Slope's and Situs's homes already have lockboxes; no hardware purchase or vendor deal needed. Tier 1 is the later upgrade. |
| D11 · Two new per-company settings | Tour clustering gap (Situs's hand rule: 15 minutes) and the pre-showing confirm-text window (Camellia: 60 minutes today; Situs wants 120). | Ship both as company-level settings, overridable per building, following the settings cleanup pattern — not hardcoded constants. |
A2 · Appendix — Situs and Western Slope evidence, raw transcripts
Quotes are verbatim from the underlying per-speaker transcripts, not the meeting recap.
Western Slope call, 2026-09-09 — the heads-up rule
Sean: "Yeah, you can say, like, for this location, we need a minimum 1 hour notice, or, you know, like…"
Fede: "Yeah, I can't be like, hey, I need a showing right now. It's like, no, we need… so you guys can tell me if, you know, you want 30 minutes, or an hour, or whatever, we can configure it that way."
Jason (Western Slope): "if we told Clara, yeah, you can go ahead and book a showing during these hours with at least an hour notice."
#transcripts, thread on the 2026-09-09 client-call recap. Settled as a rule to configure per company, not as one universal number.
Same call — why a fixed hour is wrong (duration)
Jason: "I scheduled two showings … one at 10 and one at 11, and I assumed it's assuming drive time, but those are… those are next door to each other, you know what I mean? So there's obviously some logic that we can put in there, where, like, yeah, I need, like, 15-20 minutes in one, and then we'll walk…"
Same call — two buildings, one visit
Jason: "even like, if you have a giant apartment building, and across the street is another giant apartment building that you own. You could — same logic would apply — like you can show both of those properties in the same showing with a little bit of incremental time as opposed to like a whole hour block."
Situs Group, raw transcript, 2026-09-01
- 550 units, 3 leasing staff doing showings (plus virtual assistants on scheduling/intake), ~100 guest cards a week portfolio-wide from one lead channel.
- Showings are clustered by hand: VAs stack back-to-back visits with roughly a 15-minute gap, "no rhyme or reason" beyond judgment — no system does this today.
- A showing traded staff-to-staff went unreassigned in the system; the new host arrived late. Eileen, reading the actual text thread live: "It was assigned to Davey, but somehow they trade midday and they don't reassign it. And then because it wasn't assigned to her, she doesn't even know to leave... that's another opportunity for us to lose the person."
- Lockbox self-showing is Eileen's own proposed redesign for standard turns — her leasing staff push back because "they want to be there for the lease signing."
- No-show handling was never discussed. The one thing Fede and Eileen actually agreed to build first is after-hours/weekend phone coverage, not touring — touring surfaced only as an observed workflow.
- Orca, Situs's real-time location app, is used only for maintenance/mileage tracking in this transcript — "I can watch them driving around on Orca" is said about maintenance staff, never leasing. Extending it to touring is Fede's own idea, not a Situs request.
A3 · Appendix — self-showing / lockbox product comparison
Full research: self-showing-competitors-2026-09-13.md.
The three shapes considered (§12)
| Option | Shape | Note |
|---|---|---|
| (A) Open access | No schedule at all — the prospect gets a code any time inside the property's showing window. | Fastest for the prospect, but no record of who is on-site when, and nothing stops two prospects colliding. |
| (B) Scheduled, unassigned | The slot is booked on the pool calendar exactly as an agent-led tour would be — so nobody double-books it — but no host is assigned. Clara handles the code and the follow-up. | Recommended Keeps the existing slot/booking loop unchanged; only the assigner step is skipped. |
| (C) Hybrid | Self-showing by default; agent-led on request, or always for the lease signing. | Matches Situs's own staff pushback but is two modes to build and test, not one. |
Required sub-decision: identity verification before the code is released. Self-showing has no person present to catch a mismatch, so the code cannot go out until the prospect's identity is checked against who booked the slot — a phone-number match at minimum, ID capture as a stronger option. This gates the release step in both access tiers, not just option B.
Six-product comparison, 2026-09-13
| Product | Setting | Host assigned? | ID verification | Code delivery | Coexists w/ agent tours? |
|---|---|---|---|---|---|
| EliseAI | Per-property | No — AI guides via SMS | ID scan + CC hold ($0.99) + likely liveness selfie | SMS, time-scoped, smart-lock partners | Unverified in public sources |
| Rently | Per-unit (subscription) | No; staff may pre/post-check | ID (front+back) + liveness selfie + CC hold — strictest | SMS, one-time, serial-number gated | Yes — 64% of showings self-guided, rest agent-led |
| Tenant Turner | Per-property | No | Government ID | SMS, time-scoped | Yes |
| ShowMojo | Per-property/account | No (self) / Yes (agent) | ID + phone (unverified detail) | SMS, varies by lock (MojoBox, CodeBox, Vault eLock) | Yes |
| Knock (RealPage) | Per-property | No — AI-routed | ID + selfie (CC unverified) | Unverified | Yes |
| Funnel | Per-property | No | Unverified | Unverified | Yes |
| AppFolio | Per-property, via integrations | Via integrations | Via integrations (ShowMojo, Tenant Turner) | Via integrations | Yes (integrations) |
Industry convergence: time-scoped one-time codes on smart locks are standard; ID + liveness selfie + a small credit-card hold before code release is near-universal (Rently strictest); AppFolio has no native self-showing — it delegates to ShowMojo, Tenant Turner, RemoteLock or CodeBox; scattered-site single-family operators (Western Slope, Situs's shape) are the heaviest self-showing users because there is no on-site staff. Several cells above are marked unverified in the source research — not confirmed in public documentation, not a design assumption.
A4 · Appendix — file map and schema
Every place in the code that can only hold one answer today, and every new row the model needs.
The ten places that can only hold one answer
| Where | What is hard-coded | Why it matters once there are two people |
|---|---|---|
| The calendar chokepoint sync-tour.ts:159-200 | Exactly two rungs: the building's own connected calendar, then the company's. There is no rung for a person. | This is the whole refactor in one function. Every other row below is downstream of it. |
| The provider resolver provider/resolve.ts:56-188 | Returns one calendar, or nothing. No list, no merge. | A pool needs a list. The return type is the first thing that changes. |
| The provider interface provider/types.ts:198-233 | One adapter, one timezone, per calendar. No type exists for "merge these sources". | Merging free time is a new, small type — not a rewrite of the adapter. |
| Availability check-availability.ts:758, :833 | One calendar read per day, one event list filtered. | Becomes one read per person per day. Latency is the real cost; see A5. |
| The dated ruling org-calendar.ts:1-56 | In the file header, as a decision: "one calendar per operator org … not one per leasing officer and not one per building." | This is the one thing on this page that contradicts Fede's refinement. Decision D1. |
| The three voice tools src/lib/tools/leasing.ts:513-826 | Book, reschedule and cancel carry no host, agent or staff parameter — only which building and who the prospect is. | Good news. Nothing in Clara's contract has to change. The signatures are already right. |
| The booking handler agents/clara/.../tools-leasing.ts | Zero references to any assignee concept when booking. | Assignment is new code, not a change to old code. |
| The tour record src/lib/data/types.ts:11004-11124 | No field for who is showing it. The only "who" on a tour is the prospect. | Three optional fields added; an old tour without them reads exactly as it does today. |
| The notification notification-recipient.ts:34-50 | Resolves to exactly one address: the building's email, else the calendar's own mailbox. | The person who is actually going is not told. Smallest user-visible bug in the list. |
| The slot injection leasing-context-injection.ts:976-1054, :1418 | One property, one window, formatted as a plain-text block of times and handed to the prompt. Never cached. | The shape is already right — a list of times, no calendar ids. It just needs a better list behind it. |
Two more facts. The durable workflow keyed on the tour (workflowId = tour.id) owns only the reminder timers and reacts to three signals carrying a date and a time — it has no opinion about who hosts. And the tour decider is not a slot picker at all: it classifies what an inbound message wants (confirm, reschedule, cancel). Neither has to learn about people.
The schema, in Gera's shapes
No new tables, no partition moves, no migration. Every row below is an attachment hook on a node that already exists — a company, a building, or a person — written dated, read by the one resolver.
| The row | Lives on | What it carries, and why |
|---|---|---|
ATTACH#calendar#primary | a person or a building, or the company | Which provider, which calendar, and a pointer to one credential in the vault. This is the only new placement — the same row already exists on buildings and on the company today. Situs's three people get ours (a PropFlow-hosted calendar) because they have none; the row takes their real Outlook or Google the day they connect one. |
ATTACH#assignment#<person>#leasing | the company or one building | Who shows homes here. Carries the days she covers, the areas or buildings she covers, which calendars her visits are written to, and a priority. Placing it on a building instead of the company is how "she only covers Fruita" is said. |
ATTACH#schedule#leasing#<rule> | the company or one building | The rule that picks between the people: whoever owns the calendar, first-and-backup, or rotate. A sick day or a swap is a second schedule row on a higher layer with a date window, which masks the one below it for those days only. No row is edited to take someone out for a week. |
ATTACH#setting#tour.* | the company overridable per building | The three numbers from the client call: minimum heads-up (default 60 minutes), minutes per home (default 15), and the cap on homes in one visit (3). Read by the resolver, never by a prompt. |
ATTACH#preference#<person>#leasing | a person | Static per-person settings: which days she tours, morning/afternoon preference, a daily tour cap. Feeds the ranking step (fold step 8), never reasoned about live. Not built until a client asks — named here so the row shape is reserved. |
ATTACH#setting#tour.gapMinutes | the company overridable per building | The clustering gap between two back-to-back showings — Situs's own hand rule is 15 minutes. Same settings-row pattern as the settings cleanup; default 15. |
ATTACH#setting#tour.confirmWindowMinutes | the company overridable per building | How long before a showing the auto-confirm text fires. Camellia runs 60 minutes today; Situs wants 120. A per-company number, not a constant. |
HOLD#<calendar>#<start> | keyed by the calendar | A short-lived claim on a slot while a caller is deciding. Keyed by the calendar, not the building — otherwise two buildings sharing one person's calendar each think the 10:00 is free. |
LRB#<node>#leasing | a person | Least-recently-booked counter, advanced inside the booking write. This is all a fair rotation needs — it is how Calendly, HubSpot and Cal.com all break ties. |
| Four fields on the tour | the tour | assignedPersonId, the calendars the event was written to, which rule decided it, and the length. All optional: a tour written before this exists reads exactly as it does today. |
The pool: full comparison table (§10)
| Merge everyone's free time | One shared calendar | Rotate, with rules | |
|---|---|---|---|
| In one line | Offer any time anyone is free; decide who goes at the moment of booking. | Everyone writes to one team calendar; busy is busy, nobody owns a visit. | Offer the free time of whoever is up next under the rule, by day and by area. |
| What the caller gets | Most times The union. Two people who each work half the week look like full coverage. | Fewest times Anything anyone blocks removes the slot for everybody. | Fewer times Only the person whose turn it is, so a busy person hides a free colleague. |
| Who goes | Decided at booking, from the people free at that time. Fair over the month via the counter. | Nobody. The team sorts it out in the morning. | Decided before the slot was ever offered, which is why it offers fewer. |
| Reschedule | Keep the same person if she is free at the new time; otherwise re-pick. | Nothing to decide. The event moves. | Re-pick from the rule, which can hand the visit to someone who never heard of it. |
| Cancel | Release the slot, step the counter back so she is still next in line. | Delete the event. | Same, plus the rotation is now off by one unless corrected. |
| Double-book risk | Real and must be handled. | Real, and worse across buildings sharing the calendar. | Lower — fewer people are ever offered the same slot. |
| Verdict | Recommended It is the only one that answers "two people, Monday and Wednesday" without losing coverage. | Keep as an option Right for a company that genuinely runs one desk. | Not first Trades the caller's choice for internal fairness. |
The Situs question, concretely: two or three people show apartments there. Under the recommendation they keep their own calendars — ours to start with, since they have none — availability is the merge of all three, and the rotation counter only breaks ties between people who are both free. Days and areas are rows on the assignment.
A5 · Appendix — open bugs and risks, in full
| Risk | Why it is real | What removes it |
|---|---|---|
| It contradicts a ruling made the same night | The decision record for 2026-09-10 says, in as many words, one calendar per operator company — "not one per leasing officer and not one per building" — and the code that shipped carries that sentence in its header. D1 reopens it. | Gera confirms the widened placement before step 2 starts. The collect-walk framing means nothing already built is discarded. |
| The read/write split | It has already happened once: the previous attempt moved availability to a person's calendar and left every write on the building's, which would have offered slots and booked them somewhere else. | Step 2's exit test requires book, reschedule and cancel all read back from the person's own calendar. One step, both halves, or the step is not done. |
| Latency on the voice path | Slots are computed live at the start of every call and are deliberately never cached. One calendar read becomes one per person per day. | Measure a pool of three against the current single read before step 3 ships, fetch the pool's free/busy in one provider call where the API allows it. |
| A calendar we cannot read looks free | This is a shipped failure mode in a real product: HubSpot treats a member with a disconnected calendar as always available. Our own previous attempt had the same shape. | Unreadable means dropped from the pool with a named reason, and an empty pool says "no calendar connected" rather than offering the whole week. |
| Situs arrives before the engine does | Two weeks versus a November slot on the program ladder. | Either the thin slice is pulled forward, or Situs launches with three people on one shared calendar. Decide it now rather than in the week of their launch. |
| The multi-home change is the only non-additive one | Every other step adds a row or a field. A visit covering several homes changes the relationship between a tour and a home, which every existing reader of a tour touches. | Keep the primary home exactly where it is and carry the extra homes beside it, so old readers keep working. Also why it is last in the ladder. |
| Pooled people and owner consent | A leasing person attached to the company, showing a home whose owner never agreed to a shared leasing operation, is a case Gera's design flags as undecided and which fires on ordinary stock at go-live. | The consent filter is a step in the fold and must be asserted by name in the tests. |
| A rota edit rewrites the week | If a booked visit re-derives its host from current rows, changing the rota on Wednesday moves Thursday's visits. | The tour keeps a snapshot of the rule it was booked under. Already in Gera's design; must be in the test. |
A6 · Appendix — what every other product does
Two research passes, 2026-09-10: general-purpose schedulers for the mechanics, leasing tools for the domain. The mechanics are a solved problem with well-known names; the domain-specific parts we need — a visit sized by its contents, several homes in one visit — are almost entirely unsolved in the market.
How the pool picks a person
- Calendly offers "maximize for availability" versus "optimize for equal distribution" — the latter keeping nobody more than three meetings ahead. Host choice is priority tier, then longest-without-a-booking, then random. Rescheduling re-pools by default unless "use same hosts" is set. calendly.com/help/round-robin-distribution-overview
- HubSpot picks "the user who has gone the longest without being assigned a meeting," with optional weights. A member whose calendar is disconnected is assumed always available. knowledge.hubspot.com/meetings-tool/…
- Cal.com has priority tiers plus percentage weights, falls back to least-recently-booked, supports fixed hosts alongside a rotating pool, and documents out-of-office delegation at the booking layer. cal.com/help/event-types/round-robin
- Microsoft Bookings assigns staff randomly, with its FAQ stating there is no way to ensure equal distribution. It is the one product that documents the double-book race: first booker wins. learn.microsoft.com/…/bookings-faq
- Merging free/busy across people is a first-class API call on both platforms we care about: Google's
freeBusymerges overlapping intervals, and Microsoft Graph'sgetSchedulereturns merged availability for up to 20 entities in one request. learn.microsoft.com/en-us/graph/outlook-get-free-busy-schedule
Minimum notice, buffers, duration
- Minimum notice is universal, no standard default. Cal.com defaults to 120 minutes; Google's appointment schedules enforce a 1-hour floor; Calendly, HubSpot, Bookings and Acuity all leave it to the operator. cal.com/docs/…/minimum-notice
- Buffers are separate from notice and asymmetric where they exist: Bookings blocks before and after; Acuity calls it padding; Calendly has no buffer, relies on start-time increments. learn.microsoft.com/…/set-buffer-time
- Duration that depends on what was selected does not exist. Calendly offers up to four fixed durations the booker chooses from. Bookings and Acuity explicitly do not sum services into one appointment. calendly.com/help/…multiple-durations…
- Nobody does travel time. Calendly's own community answer is that it has no such feature; Acuity and Bookings offer only fixed padding. community.calendly.com/…travel-time…
The leasing tools
- Rently is the only product that batches several homes into one visit: "Wayfinding" gives GPS navigation between units with a one-time code per unit, averaging 2.6 units per tour. use.rently.com/blog/…wayfinding…
- Self-guided versus agent-led is the fork that decides the notice default across the category: ShowMojo, Tenant Turner, Rently, Funnel, Knock, AppFolio and Yardi RentCafe all offer self-guided tours, effectively defaulting to zero notice — Rently reports 70% of tours completing within two hours of booking. ShowMojo FAQ
- Showing length has no published default anywhere; the quoted industry range is 30 to 60 minutes. Nothing supports 15 minutes as a default, which is what adjacent scattered homes actually need.
- Lead-to-agent routing is never documented. Funnel describes centralised agents with travel-time logic; Knock and Anyone Home describe assignment from guest-card data; EliseAI schedules against "agent availability." Not one publishes the algorithm. funnelleasing.com/…agentic-ai…
- Writing the showing back to the PMS is table stakes. ShowMojo syncs completed tours into AppFolio guest cards in real time; EliseAI creates the guest card and syncs tour details; Rently creates the guest card and calendar entry at booking. None publishes a payload spec. ShowMojo × AppFolio
A7 · Appendix — the change ladder, in full
Ten steps. Every one ships dark — deployed with the behaviour off, proven at a test property, turned on for a customer only as a separate decision. Every one has a single test that has to be green before the next opens.
| # | Step | What changes | The exit test |
|---|---|---|---|
| 1 | The walk returns a list in flight, dark, PR pending (2026-09-14) for Western Slope | The one chokepoint collects instead of stopping at the first hit: building's own calendar, else the company's people and the company's shared calendar, else none. Callers take the first entry. | A replay of real Camellia tours is byte-identical, and a building with no calendar of its own still books on the company's exactly as it does tonight. |
| 2 | A calendar on a person in flight, dark, PR pending (2026-09-14) for Western Slope | Signing in writes the calendar hook on the person who signed in. Read and every write — book, reschedule, cancel, both conflict checks — move together. | At a test property: book, reschedule and cancel a real event on that person's own calendar and read all three back from the provider. This is the test PR #7615 could not have passed. |
| 3 | Merge the free time next, supported by design; not needed while Western Slope has one agent | Availability becomes the union of the pool's free time minus active holds. | Two calendars with different busy blocks produce the union; a calendar we cannot read is dropped with a named reason, never counted as free. |
| 4 | The policy rows | Minimum heads-up and minutes-per-home as settings on the company, overridable per building. | A 1:15 pm request with a one-hour heads-up offers 2:30 first; a two-home request offers 30-minute slots; a company with no rows behaves exactly as today. |
| 5 | Assignment and the rule | Days, areas and priority as rows; the rule picks the host; the tour records who, which calendars, which rule, how long. | Three people with different days and areas produce the same host for the same input every time, and the fairness counter advances exactly once per booking. |
| 6 | The right person is told | The invite goes to the assignee with the prospect's context in it. The company mailbox keeps sending the prospect's email. | On a pool of two, the assignee receives the invite and the other person does not; the prospect's confirmation is unchanged. |
| 7 | Holds, keyed by calendar | A short-lived claim on the slot plus a read-back after every write. | Two bookings for the same time on one shared calendar, arriving from two different buildings at once: the second is refused with a reason, not written. |
| 8 | Cover, in the engine | A dated row masks someone; a declined invite re-hosts; an ended role re-hosts. No buttons anywhere. | Decline an invite: the visit is re-hosted, the prospect's time does not move, and the slot is never silently freed. |
| 9 | Two homes, one visit | A list of homes beside the tour's primary home, capped at three, one calendar event, length from the rule. | Two adjacent homes book as one 30-minute visit; cancelling it cancels the whole visit; every existing reader of a tour still works. |
| 10 | The PMS learns | The showing written back to the client's PMS with the assignee on it. | A booked visit appears on the guest card in AppFolio, read back from their API, with the host named. |
Owners. The pure functions — the fold, the merge, the ranking — are Gera's, as step 8 of his ladder already says. The rota facts and the Situs calendars are Fede's. Steps 1, 2 and 6 are touring-side work that does not wait on the rest of his program, because they change one function, one sign-in write and one recipient.
Nothing on this page is built. Every step in the ladder ships with the behaviour off; turning any of it on at a real property is a separate step and Fede's explicit call. Open with Gera: the calendar placement in D1, and whether the fold in §6 is the shape he wants for step 8 of his program.