Phone line and hand-off architecture

Proposed 2026-10-28, reworked 2026-10-29 on Fede's notes · pending Fede's review · nothing built, nothing turned on

Today, at each customer Live settings and code, Oct 29

Numbers show the last four digits only.

What a caller getsCamellia (JP&Co)Western SlopeSitus Group
GreetingOpen: "Hi, it's Clara at Camellia Apartments, what can I help you with?"Open: "Thanks for calling Western Slope Property Management. How can I help you?"Open: "Thanks for calling Situs Group. How can I help you?"
Spanish offeredNo (building line, no language answer)154 of 233 calls (30 days)Yes
Asks for a person, office hoursRings the office line ending 2043 (the building's leasing and "something else" lists)A prospect, a named person, a vendor, or a resident's noise, billing or lease-break ask: Kat, then Bev, 20 s each. Else a message (repairs below)Nothing set: a message
Asks for a person, after hoursA messageA message (repairs below)A message
"Something else"Office line ending 2043 in hours; else a messageNothing set: a messageNothing set: a message
RepairsClara's maintenance agent (live)Clara (supervised); a caller who asks for a person goes to the call center ending 8684, at any hourClara (supervised)
EmergencyEmergency phone ending 0100; no on-call list, alert chain offAlert chain: three staff in turnAlert chain: the PMS on-call person, then two more people
Modules the voice line checksLeasing, maintenance, renewals, resident services: all onLeasing on, maintenance on (supervised); renewals and resident services offLeasing and maintenance on (supervised), resident services on, renewals off

Same at all three: office hours Mon to Fri 9 to 5; nothing set means a message; no office-phone fallback. On real Western Slope calls the "leasing, maintenance, or something else?" greeting last played Oct 10; now it comes only mid-call, when a caller gives nothing useful. Resident services on calls is the lease and billing agent, off at Western Slope since Sep 22.

The design: one engine, branching on each customer's modules Proposed — pending review

Fede, Oct 28: one shape for "a very simple forward or a very complex operation." Oct 29: "you need to consider what modules are enabled… if they're enabled we transfer this to the sub-agent. If it's not enabled, we're not able to help with that so we would forward to human or take a message." Maintenance and the emergency chain keep their own lists and dial paths.

Open greeting "How can I help you?" Existing; the menu fallback goes Topic classifier what the call is about, or who is asked for. Existing Is that module on? leasing · maintenance · renewals · resident services Per customer. Switches exist; voice reads all 4 on That module's agent Clara handles it. Existing off, or a person is asked for (maintenance keeps its own path) Schedule office hours. Existing Directory people with a phone. Partly new Rules topic or name, in order. New Resolve when the caller asks reads the schedule, the directory and the rules New: one function, one place (today: fixed at the greeting) in office hours after hours One dial path no answer: next, then a message Existing, extended After hours Clara helps or takes a message; no transfer Reaches a person's phone, a group line on their phone system, or an outside service such as a call center Unchanged: maintenance and the emergency chain Their own lists and dial paths, as today. Emergency rings at any hour. Nothing set means a message A number that forwards into Clara is never a destination. A simple customer fills one row; a complex one fills many. Same engine. Language (the Spanish prompt and remembering it) is a separate fix; see Language.

What changes, before → after

PieceTodayAfterStatus
GreetingAn open question at all three; the menu only when a caller says nothing usefulOpen greeting and a topic classifier; the fallback menu goes tooExists; one fallback to remove
Module checkVoice reads all four switches. Each "off" has its own rule: leasing off, Clara answers with no tours; maintenance off, call center or list or message; renewals or resident services off, the catch-all, else a messageOne check for all four: on → that module's agent; off → the directory in hours, else a message (maintenance keeps its own)Exists for all four, handled three ways; the single check is new
Where "who to ring" livesAbout ten settings; transfers fixed at the greetingOne resolver, picked when the caller asksNew
DirectoryLists of a name and a number, one per call type; a link to the PropFlow person is optionalOne directory: the same people, plus "asked for by name: ring me / take a message", "handles", "order"Exists; three fields new
Staff phone at sign-upRequired in Team settings and ring lists; optional in the admin user API; not asked when accepting an inviteRequired on every path that creates a staff personFix
Dial pathOur hunt line for routed calls; the call platform's built-in transfer still on the specialist agentsOne dial path for every person or topic askExists; specialists move
Person transfers after hoursA message, except Western Slope repairs (call center)Same, now a stated rule; maintenance and emergency unchangedExists
Spanish prompt"Para español, diga español" at some customers, not allSame prompt everywhere; the caller's language is remembered for calls, texts and emailFix, see Language
Maintenance, emergencyOwn routes, lists and dial pathsUnchangedNo change

Decisions for Fede

QuestionOptionsRecommendation
Who can be asked for by name?(a) every "ring me" entry; (b) only entries on a topic rule; (c) anyone on staff(a)
Spanish prompt at every customer(a) yes, same wording everywhere; (b) per customer(a), with the memory fix first
The one shape, and the same shape at three customers

A directory entry

FieldHoldsStatus
WhoA PropFlow person with a role, or a group line on their phone system, or an outside serviceExists as a name and a number; the person link is optional
NumberWhat Clara dials; never a line that forwards back into ClaraExists
Asked for by name"Ring me" or "take a message for me"New
HandlesTopics this entry takes, including topics of modules the customer has offNew
OrderPlace in line for a topicExists per list; new across topics
HoursCompany office hours; per entry laterExists

Three customers, one table

CustomerModules on (voice)EntriesRules
Camellialeasing, maintenance, renewals, resident servicesOne on-site managerAny person ask, in hours: that person. Else a message.
Western Slope (RingCentral)leasing, maintenanceKat then Bev as today, "ring me"; everyone else "take a message"Any person ask, or a topic whose module is off, in hours: Kat then Bev. Else a message.
Situs Group (Crexendo)leasing, maintenance, resident services13 staff and 2 VA seats from their phone-system export; group lines at onboardingTopic rules per group line; by name, the person's choice; else a message.

Shapes per Fede's ruling, Oct 28. Maintenance and emergency: unchanged at all three.

Rails, and how a call flows
  1. Clara answers with the open greeting. The classifier decides the topic or the person asked for. (Sean, Oct 18 and Oct 27; Fede, Oct 27)
  2. Topic of a module that is on: that module's agent. Maintenance and emergency: their flows, unchanged.
  3. Topic of a module that is off, or a person ask, after hours: Clara helps or takes a message. No transfer (Fede, Oct 28). Maintenance and emergency keep their own paths.
  4. In hours: the resolver reads the directory, the named entry or that topic's entries in order.
  5. One dial path rings them; no answer means the next entry, then a message.
Where the directory comes from

Fede, Oct 29: "right now how it works is you have to be added to PropFlow as a user with a role, right, and then that would make you discoverable in the system… we'll need to check if we require a phone number when people sign up. We should."

Language: the Spanish prompt and remembering a caller's language

Fede, Oct 29: "once we interact with someone we should remember their language preference… we shouldn't ask again on the next call." Read on 2026-10-29 from the code, the change history and 30 days of real calls at the three customers.

LayerTodayVerdict
The offer "para español, diga español"Plays only when the call is treated as company-wide or the company named Spanish in its settings. Camellia: never (0 of 118 calls). Situs: every call (20 of 20). Western Slope: 79 of 233 calls had no offer, the ones filed under an apartment building or from staff.Inconsistent, by settings not by policy
Switching when the caller says "español"Works; the language is saved on the person and later texts go out in Spanish (Western Slope caller ending 5740, Oct 9).Works
Switching when the caller speaks Spanish without the wordFrom Sep 12 to Oct 27 a caller speaking full Spanish sentences got English; a robot test call to Situs on Oct 19 heard English for over two minutes. Fixed Oct 27; it overshot once on Oct 28 and flipped a mostly English call to Spanish.Fixed Oct 27, thin proof
"Sí, gracias" to the offerReal: Western Slope, Oct 27, 6:05 PM Central. Two words counted as too few to switch; Clara stayed in English, the caller kept speaking English, and English was saved at 6:07 PM.By design, which you flagged: a Spanish "yes" is not a switch
Remembering on the next callA caller saved as Spanish is greeted in Spanish but hears "for English, just say English" every call. A change merged Oct 29, not yet seen on a real call, stops offering Spanish to a caller saved as English, and almost everyone is saved as English after one ordinary call.Half built: English is remembered, Spanish is re-asked
Text and emailTexts read the saved language. Email never reads or saves it; it answers in the language the email was written in.Email missing

Every channel, 30 days

Only 4 of 346 automated texts and emails to prospects and tenants went out in Spanish, all to one person. Clara's own written replies follow the caller's language; the sends made on her behalf read the saved language, which is usually missing.

LayerTodayVerdict
Email lead writing in SpanishClara replies in Spanish, but no language is saved, so the tour confirmation text and all four reminders went out in English (a Camellia lead, Oct 23: Spanish email and English text in the same minute).Broken, most hit
Text lead writing in SpanishThe detector misses about half of ordinary Spanish texts (short words count as English, common words are not on its list), so texters are rarely saved as Spanish.Broken
Guest cardThe language lives only on our person record; it never reaches the AppFolio guest card or a note. Spanish website forms are missed.Not built
Tour confirmation email and calendar file, application emails, work-order created, rating ask, vendor relays, renewal outreachEnglish-only copy, about 20 sends.Not built
Tour reminders, quiet-lead follow-upsRead the saved language and have Spanish copy; the follow-up still opens with the required English "It's Clara with…" and home facts arrive in English.Partly
Spanish routing at SitusNothing in the product routes Spanish speakers anywhere; staff answer Spanish texts by hand in AppFolio.Not built

What to change (fixes, no switch): one offer policy at every customer instead of per-settings luck; a Spanish "yes" ("sí", "sí gracias", "sí por favor") counts as a switch; a saved language is used on the next call, text and email with no re-ask in either direction, and one sentence to switch back; the audit rows above marked broken get the same treatment.

Ideas from Crexendo, RingCentral and Comcast, and what goes away
Vendor ideaOurs
Schedule: open hours, holidaysHours (holidays later)
Destination: user, queue, menu, outside number, voicemailWho and Number; a message is our voicemail
Rule: by time, caller, number dialedHandles and Hours
Ring order, timeout, overflow to the nextOrder, one at a time; next entry, then a message

Unverified: RingCentral ring strategies and overflow, outside numbers inside a Crexendo queue, dial by name; the Comcast guide predates a rename. Not borrowed: menus, dial by extension, hold queues, agent presence. Clara is the attendant.

Goes away: the leasing ring list and the older leasing hand-off setting; the two "everything else" settings; the "two modes" of Oct 7; the call platform's built-in transfer for person asks. Maintenance and emergency keep their settings, lists and dial paths.

Build order, dark
  1. Resident services: one switch instead of two (voice already reads the lease and billing one).
  2. Directory table and card on the company page: today's ring-list people plus the three new fields; phone required on every staff-creation path.
  3. Resolver: one function picks when the caller asks, proven by replaying real transfer history unchanged.
  4. One dial path for person and topic asks.
  5. Ask by name, honoring each entry's choice.
  6. Delete the old settings after a supervised week.

Nothing turns on soon (Fede, Oct 28). Steps 1 to 4 must replay today's calls unchanged; step 5 and anything callers notice wait for Fede.

Sep 21 – Oct 15Appendix: incidents and earlier designs

Running overnight

Details and history

Everything older, moved here unchanged, newest first. Click a row to open it.

Oct 7 – 15Where things stood, the Oct 15 incident, the decisions then open, the caller-ID mockup, and the two-mode design (superseded Oct 28)

Where things stand

  • Oct 15: a law firm was filed as a leasing lead after Clara handed its call to the leasing ring list. Fix in review: who is calling is checked first, and a hand-off is never the reason. Incident write-up.
  • The Calls & messages page does not show what the engine does. At Camellia it says "Talk to a person: Not set" while the engine rings Joanna from a ring-list row no screen shows, and sends leasing asks to the office phone through a hidden fallback.
  • Notes: every note Clara actually took was delivered. The problem is capturing it. About 3–5 real lost messages since Sep 25, at Camellia and Western Slope.
  • The notes fix (one after-call path, no backup) is being proven overnight by replaying every eligible call. Nothing enabled.
  • Routing: phone routes don't use Gera's shared lookup (property → group → company). The rebuild is being proven on a branch overnight; nothing that changes callers is merged.
  • Yale 25 Station and Situs would ring nobody for several call types today. Neither is live.
  • Engineering alerts for broken hand-offs are live.

Incident Oct 15: a law firm's call was filed as a leasing lead Fix in review

A law firm called Western Slope and asked for Jay by name. Clara sent the call to the leasing ring list, and after the call she filed the caller as a prospect and armed the leasing follow-up texts. The caller's number was already on file as a vendor. The card was archived and its follow-ups stopped the same day. Nothing was written to AppFolio, because the front-door line has no AppFolio property behind it.

When (MT)What happened
Oct 14, 12:38 PM#19632 merged. Any hand-off to the leasing ring list now counted as a reason to file the caller.
Oct 15, 2:48 PMA law-firm caller says "Jay Taylor, please." Clara hands the call to the leasing ring list. Kat answers and passes it to Jay. A privileged legal conversation follows.
About 20 s after Clara's part endsThe after-call reader sees only Clara's part, which is one line. The AI says "no topic, not a lead." The leasing hand-off rule overrides it, a card is made, and the follow-up texts are armed. Nothing checked that the number belongs to a vendor.
Oct 15, eveningThe card was archived and its follow-ups stopped. The harness (#20217) and the fix (#20221) were opened. A gap found on the way: archiving a lead stopped its first follow-up texts but not the "went quiet again" texts for leads who had replied. Fixed in #20231.

Root cause. #19632 (our own session, 10/14) widened the signal and relied on the AI reason alone, with no deterministic gate and no logging. The rule it widened had already been wrong 5 out of 5 times: every card it made was not a leasing call. The cards were the lawyer, Jay's own ask, a photographer's sales call, a job applicant and a hardware-store vendor.

Before AI reads Clara'spart only Sent to leasing? Make a card,even when the AI said no Card + follow-up textsno identity check, no log After Who is calling?vendor · staff · tenant→ never a cardhas a card → update it Unknown caller only:AI reads the WHOLE call,the person's part included.A hand-off is never the reason Card only if about leasingone log line per call:filed or not, and whichrule decided

Fede's decision order (Oct 15)

#Who is callingResultProof
1A known vendorNever a cardReplay of the lawyer and two other vendor calls, plus a planted break that turns the test red
2Staff of any role, or a current tenantNever a cardJay's own call and a tenant who said "leasing", one planted break each
3Someone who already has a cardThat card is updated, never a second cardA returning applicant, plus a planted break
4Anyone elseA card only if the whole call was about leasingNoah's call still makes a lead from the person's part. Two planted breaks ("any leasing hand-off makes a card", "read Clara's part only") turn it red

What the 90-day pull shows

MeasureNumber
Western Slope phone leads Clara made32, of which 12 were not leasing (38%)
Stopped by this fix7 of the 12 (5 hand-off cards, 1 vendor, 1 staff member)
Already stopped before it2 (a tenant, and an "operator" ask the AI now declines)
Still open3: two internal test calls and one software sales call. Follow-ups filed.
Real Western Slope leads that came only from the hand-off rule0 of 18
Cards the AI alone said yes to52, of which 8 were not leasing (15%)
Full-call judge, alone vs with identity checks first16 of 20 right vs 19 of 20 right
Camellia, the other direction11 real leads on transferred calls got no card

Next: pick the outcome, not just "make a card?" Proposed — pending review

Fede: "Do I create a guest card? Do I create a tour? Do I send an application?" The after-call step should choose one outcome per call: guest card, tour, application link, team note, or nothing. Identity runs first, as above. The verdict and its reason are logged as ids and labels, never the call's words. The 90-day pull is the test set: 256 Western Slope calls and 134 Camellia transfers, each already labeled with the right outcome. The bar is 98% of real leads caught and 95% of cards correct.

Harness: evals/fast-tier/postcall-prospect-capture/replay/ (13 scrubbed real calls) and src/__tests__/postcall-capture-replay.test.ts; subscription runner scripts/replay-postcall-capture-subscription.ts. Before the fix, 7 of the calls made a wrong card. After, all pass, and all 7 planted breaks go red. Not yet proven on a live Willows call. Incident: #20208.

Decisions for you

Decided (Fede, Oct 7) "Take a message" is the default when nothing is set, and onboarding confirms each call type was looked at.

  1. Leasing's hidden fallback to the office phone. (a) keep, (b) replace with explicit settings copied from each client's number, (c) delete. Recommend (b).
  2. The ordered fix list (table below). Recommend yes to the "no switch" rows once the overnight proof passes.
  3. Caller ID on transfers. (a) keep the caller's number everywhere (today), (b) a choice per destination on the Calls page: the caller's number or the line the call came in on, default = today, (c) always the client's own Clara line. Recommend (b). Nobody in the field decides by destination type; everyone makes it a setting. The only fix for a silenced personal phone is a known number saved once.
  4. Emergency work-order chain (Western Slope, Oct 7 call): build it as the leasing ring list with a different trigger, an emergency-category work order. Recommend yes once the inventory below confirms what exists.
  5. The three "your call" items: a property default list for on-site clients; retire the live note tool; move Saturday from office hours to tour hours at Camellia and Yale. Recommend deciding each after the morning results.
#StepKind
1After-call reader runs for every client (delete the test-only gate)fix, no switch only after the overnight replay passes
2"No message" never drops a call we know who + what forfix, no switch
3Screener or voicemail is not a person; ring the next onefix, no switch proven on a branch (128 transfers replayed, 2 flips, 0 real people); Fede Oct 8: not yet
4Property default list for on-site clients, read from the stored modeyour call changes who Camellia and Yale callers reach
5No call ever ends in nothing: a failed dial takes a messagefix, no switch
6Nightly check: every call ended with a person or a delivered notefix, no switch
7One office-hours store and one "is it open" checkfix, no switch; Saturday to tour hours at Camellia and Yale is your call
8Every agent on the one connect step; delete the old button and leftovers; routing onto Gera's lookupfix, no switch
9Retire the live note tool; Clara says "I'll pass that on"your call after step 1 is proven

Where the phone line is going, and the gaps Proposed — pending review

At altitude: two facts carry the rest. First, anything set inside the voice vendor's agent configuration is shared by every client, so it can never differ per client. At dozens of customers with different phone setups, every hand-off has to run through one dial path we own, where who to ring, which number shows and whether a person answered are data, not code. Second, that data is the settings the customer sees on the Calls & messages page, read through Gera's shared lookup, so the page and the engine can never disagree. Everything else is implementation inside those two.

Call on theclient's line13 agents, one step Call typeleasing · maintenanceemergency · other · WO flag Who to ringGera's shared lookupnothing set = message One dialcaller ID per destinationone verdict, no keypress After the callnote · notice · cardfrom the transcript Settings the customer sees = settings the engine readsone row per call type on the Calls & messages page · each destination shows its last transfers · every leg recorded · one nightly check: every call ended with a person or a delivered note

Pillars, each a ruling already made

PillarWhat it meansRuling
Clara is the phone systemStaff use their own cell, nothing to install; the whole call is recorded end to end, after pickup and after any transfer.9/19
Ring lists are peopleAn entry is a person with a number, rung in order; Clara is the middleman and never reveals a cell. No key press ever: a person is told from voicemail by listening.9/20, 9/23
One hand-off logic, all agentsThe same ask gives the same outcome on every agent; one notice builder for every notice; greeting is a setting, routing is one core.9/21, 9/22
Two modes, one coreCentralized (company, per call type) or on-site (property default); nearest set level wins; nothing set = take a message; no hidden fallback.10/7
Live tools only when the caller needs the answerTours, calendar, transfers. Notes, notices, guest cards come from the transcript after the call. One way, no backups.10/7
Never infer, always observeCaller ID is a fact per destination, never guessed from the number; every destination proven with a real call at onboarding; every transfer gets a verdict and every destination shows its results.10/8
Emergency chain = same engineThe ring list with a different trigger: an emergency-category work order. Not a second dial path.10/8

Caller ID lineage: 9/20 open trade-off (caller's number vs Clara's line) → 9/23 caller's number everywhere → 10/8 a choice per destination. Older text in the history below that says "press 1 stays the fallback" or "staff still see the original caller's number" is superseded.

Gaps between today and that picture

GapToday (proven)Kind
Three dial pathsClara's connect step (2 agents), the ring-list and maintenance-menu dial, and the vendor's built-in transfer on 6 agent configs that can never be per client.architecture
Ten who-to-ring lookupsGera's shared lookup exists and is used only by the office-hours check. The routing rebuild on it is proven on a branch, same answers at Camellia, Western Slope, Yale.architecture
Eight pickup verdicts that disagreeThe ring-list dial advanced correctly past a silenced phone on 9/20; the connect step logged a screening robot as "connected" on 10/8.architecture
Page and engine read different settings"Talk to a person" reads one key; the engine rang Joanna from another no screen shows; leasing falls to the office phone through a hidden fallback.architecture
Caller ID is one fixed ruleCaller's number everywhere since 9/24; no per-destination fact; carrier acceptance of the incoming line as the shown number not yet proven.architecture
Nobody watches the destination7 detectors, none checks the whole call or a destination's track record; Camellia went two weeks unnoticed.architecture
Office hours in two stores plus knowledge text5–6 separate "is it open" checks; Camellia's Saturday tour window read as office hours.architecture
Notes depend on a live toolAbout 12 note paths; the after-call reader runs only on the test bench; the single after-call path is not yet ready (40 of 71 reproduced, 3 real losses).architecture
Emergency on-call list is darkSettings card and keys exist; rows only at the test companies; the ring-list engine has no emergency trigger; the chain, timeout and email leg are unbuilt. Gera's lane, contract on the bridge.architecture
Yale 25 Station has no maintenance or emergency destination; Situs has no lineNeither is live; both are onboarding answers, not code.onboarding data

Nothing in this section is built or switched on. Each gap closes as a fix with no switch, proven by replay and real calls on our own lines, in the order on the fix list above.

The caller ID setting, as it will look

One new line under each person on the Call routing card: Shows: the caller's number (today's behavior, the default) or the line the call came in on. Only PropFlow staff see the select; the customer sees the list as before. Change Joanna's select below and the phone shows what she would see.

Call routingEdit
Office hours
Mon–Fri 9 AM–5 PM
When a leasing caller asks for a person, Clara rings
  1. Joanna · ending 2043
    Shows: new
When a maintenance caller asks for a person, Clara rings
  1. Maintenance line · ending 1057
    Shows:
When any other caller asks for a person, Clara rings
  1. Joanna · ending 2043
    Shows:
Calls Clara can’t help with forward to
Camellia office · ending 1057
Shows:
Maintenance calls go to
Clara
Joanna’s iPhone when a leasing caller is transferred
incoming call
(720) 555-0142
Unknown caller
Silenced by “Silence Unknown Callers”

Proved on real calls, Oct 9

Two real calls into the Fairhaven test line, each asking for a person. The call was passed to a PropFlow test phone. We read what that phone showed from Twilio.

CallSettingTest phone showedTwilio call ID
1, first legNot setThe caller’s number (+17205942061)CAe6057f3d1097ba872c09b64a6e561b18
1, second legNot setThe caller’s number (+17205942061)CAb41e1533f87698508747ae48f0d9fa2a
2, first legLineThe Fairhaven line (+17208071724)CA52fdb7476133b7d0f21f701f5c6bc4b5
2, second legLineThe Fairhaven line (+17208071724)CA39fbf855f2497596c2c60e8c31116ef9

Turning the setting on changes what the person’s phone shows, and turning it off puts it back. The setting was cleared afterward.

Turned on at Camellia, Oct 9, 2:04 PM Mountain. Fede’s call: Joanna’s row at JP&Co is set to “the line”. Fede then called the Camellia line from his cell at 2:16 PM and asked for a person; Clara rang Joanna and her phone showed the Camellia line, (844) 510-1007. Twilio shows the leg to Joanna leaving from the Camellia line.

LegFrom → toTwilio call ID
Fede into CamelliaFede’s cell → (844) 510-1007CA33e2ae28bc602725d1eeef197e6cd5c6
Clara to Joanna(844) 510-1007 → JoannaCAdb303d3690435abf80462f52f57f6c40

Known gap: the app does not show this choice for Camellia today. The control renders only beside people on the company-level ring lists, and JP&Co stores Joanna on the building. Follow-up issue 18329; the redesign (one routing card at both the company and the building level, each row labeled with where it is stored) is paused pending Fede.

Every route still reaches a person after the shared lookup went live, Oct 9

The routing change that passes the call’s group to every lookup (the third routing PR) is live in production. We re-proved the Willows test company’s routes with real calls: the proof script resolved each route from the stored settings, then dialed the answer. Every dial reached our test phone. Two placeholder emergency numbers on the test buildings were refused by the script’s own safety list, which is the guard working.

RouteResolved fromReachedTwilio call ID
MaintenanceCompany ring listTest phone, picked up at onceCA74801f8b2bead842ae3ed56d50229cb2
Everything elseCompany ring listTest phone, picked up at onceCA8054034a0c50099f19d2c0a9f9d5e4a9
EmergencyCompany emergency routeTest phone, picked up at onceCAa29b0b6d8fa1f85baf0e2e12988d4f8d
Talk to a personProperty handoff settingTest phone, picked up at onceCAafc1c8861c4bac2824292f683fe46add
Emergency phone on the property recordProperty recordTest phone, picked up at onceCAf5858a3bd3e2f0640c124c57b7432cc0

These calls were placed at 2:01 PM Mountain from the code on main that includes the routing change. An earlier set at 1:10 PM ran from a stale checkout without it and was replaced; those five calls (CA903bd4…, CA7f7cad…, CAf1eb74…, CA113d28…, CA1dd74a…) reached the same test phone and stand as the “before”.

Before: the same five answers, resolved without the group step. After: the same five answers through the shared lookup, each one proven by a real call. No customer’s routing changed; the before/after on real customer settings was byte-identical in the PR. One note: the proof script does not look at the clock, so running it after closing time would dial the same five numbers and prove nothing new. The after-hours message desk is covered by its own eval scenarios, not by this script. The Fairhaven test company could not be used for this run because one of its buildings is missing the test flag. That building (“Company line bench building (live)”) is not created by any code in the repo, so we left its record alone for whoever owns it; the Willows test company covers the same routes.

What customers asked for on the phone line (client calls Sep 1 – Oct 7)

Read from the raw per-speaker transcripts of nine client calls: Western Slope (9/4, 9/9, 9/16, 9/22, 9/30, 10/7), MMG Denver (9/18), ConAm/Yale (9/29, 10/1). No JP&Co or Situs call in the window. Asks we already meet are left out (forward the main line, ring a list then take a message, written summary with transcript and recording, staff texting Clara, no duplicate guest cards).

AskWhoWhere we are
Emergency chain: call, text and email the on-call person at once, 5-minute timeout, keep trying until someone acknowledges. Issue types from the work order (AC, heat, water, gas, habitability).Jay 10/7, Jason 9/30, Hugo (MMG) 9/18Not built. Gera's lane. The two Western Slope calls disagree on who is first (Cheryl on 9/30; maintenance manager, then Jay on 10/7); confirm with them before building.
Escalations go to Kat by text, not the shared leasing inbox ("it's gonna get drowned").Jason 9/22Building (Clara as coworker on Kat's channel).
Retire the AppFolio after-hours number printed on 220 magnets, or forward it to the main line.Jay 10/7Nothing planned.
After-call summaries into a ticket or shared inbox, not an email distribution group.Jay 10/7Email to the shared inbox today; no ticket option.
Police calls go to Cheryl; "if this is an emergency, call 911, we're not the police."Jason 9/30No rule for law enforcement or 911.
Ask each staff person how they want to be reached (text, email, call).Jay 10/7Not built; every notice goes the same way.
MMG: one main number for all properties, residential vs commercial routing, commercial after-hours.Hugo 9/18Not supported (one number serving many properties).

Two signals. Nobody asked about caller ID, but Jay saved the after-hours number "so I know it's an emergency", which is the per-destination setting above. Nobody wants a key press to accept a call, but staff do expect to acknowledge an emergency, so the emergency chain needs a defined "got it" step that is not press-1.

The design

Superseded 2026-10-28 by the design at the top of the page: the "two modes, one core" shape below is replaced by the one-engine design (one shape for every customer). Kept for history.

Centralized (Western Slope) set at the company one destination per call type On-site (Camellia, Yale 25) one property default for every topic a call-type list overrides only where set One routing core, on Gera's shared lookup nearest set level wins: property → group → company one hours store, one "is it open" · every agent, same connect step CALL TYPE Leasing Maintenance Emergency Everything else Named person A person ring chain, in order screener or voicemail = next A call center where the client uses one never the line that rings Clara A message nothing set, or the chain ran out Clara: "I'll pass that on" After the call: the transcript reader writes the note the only note path, whenever no person took the call · no note without who + what One notice builder → the client's shared inbox suggested category · matched record · transcript · recording link Nightly check: every call ends with a person or a delivered note anything else alerts engineering

Correction to the Oct 7 inspection (in History): a per-property "everything else" list does exist (today's Camellia fix used it). Missing is the rule making it the default for every topic in on-site mode.

Never infer, always observe (Fede, Oct 8)

  • A number tells us nothing reliable about what is behind it: an enterprise line can forward to a personal cell, and each carrier rewrites caller ID differently. So no rule by destination type and no company-wide hardcode.
  • Caller ID is a fact per destination on the Calls & messages page: "shows the caller's number" (today) or "shows the line the call came in on" (each client's own Clara line; the engine always knows it). Default = today, so nothing changes on merge; Camellia's fix is one click on Joanna's row.
  • Every destination is proven with a real call at onboarding, after forwarding is on (already on the playbook). Real calls, not carrier models.
  • Every transfer gets one verdict (person / screening robot / voicemail / no answer) and each destination shows its last results on the page: "Last 5 transfers: 0 reached a person". A failing destination surfaces within a day, not two weeks.
  • The settings the customer sees are the settings the engine reads: one row per call type (leasing, maintenance, emergency, everything else), "take a message" when nothing is set, no hidden fallback.
  • The emergency chain is the same engine: the leasing ring list (people in order, 20 s each, pickup told from voicemail without a key press) with a different trigger: an emergency-category work order instead of "a caller asked for a person". Client's own description (AppFolio today): emergency issue types (no heat, A/C, water, gas, habitability), maintenance manager → Jay → technician, call + text + email at once, 5-minute timeout, same chain in business hours, a tenant who merely claims an emergency also escalates. Gera owns this lane (Fede, Oct 8); the alignment contract is on the bridge. What exists on main today (proven): the on-call list card and keys (dark; rows only at the test companies), text and email to the list, a one-person after-hours relay call, the ring-list engine with no emergency trigger, and an LLM urgency judgment (no per-client issue-type list; AppFolio's "Emergency" syncs as "high"). Missing: the trigger from a work order, the chain with a timeout, call + text + email at once, business-hours use, tenant-claimed escalation. Nobody on Gera's side has a PR or branch for it yet (Smith, Oct 8).
  • Proven Oct 8 on our own lines (Willows ring line → bench, SIDs CA25c0c3e8, CA5453c598): the carrier accepts a line we own as the shown number, on a dial inside a call and on a call we start ourselves; with nothing set the destination sees the original caller. A number we don't own is ignored silently, so the dial must check the line is ours and log when it can't honor the setting. Decided (Fede, Oct 8): build it.
  • Precision on "today": the caller's number shows on Clara's connect step, the staff ring list and the vendor's built-in transfer; the plain forward to an outside call center shows the property's line, and the maintenance press-1 bridge shows its own bridge number. "Unset" keeps whatever each path does today, so nothing changes on merge.

Caller ID in the field (Oct 8, sources in history): Twilio shows the caller's number by default; Smith.ai, Google Voice, Dialpad and Retell each offer one setting (their line or the caller's) per account, device or transfer step; EliseAI and RingCentral don't document it; Apple's answer to a silenced personal phone is "save the number as a contact".

Every setting: where it lives, what happens when it's empty

SettingLevels: today → targetEmpty todayEmpty under the design
Client modecompany (stored, routing ignores it) → company, routing reads it—centralized
Property default (on-site)doesn't exist as a default → property—fall to the call-type list, else message
Leasingproperty, company (group skipped) → all threeblind dial to the office phone on one agent; message on anothermessage
Maintenanceproperty, company (group skipped) → all threea failed direct dial ends the callmessage
Emergencyproperty phone field only → all three, a chain of peoplenobodymessage + flagged missing at onboarding
Everything elsepeople list all three, office line company-only → one list, all threemessage, if the AI remembersmessage
Named persondoesn't exist → staff recordsnobodymessage for that person
Office hours2 stores, property wins → company, property only on the client's wordcompany hourscompany hours
Note recipientcompany shared inbox → sameno email (Situs)flagged missing at onboarding

"All three" = property, group, company; nearest set wins. The group level is never read on a call today.

Per client: what each call reaches

ClientLeasingMaintenanceEmergencyEverything elseNamed person
Camellia
on-site, live
Todaydepends on the agent: message, or blind dial to the office lineClara's maintenance agentproperty emergency numberJoanna in hours (set today, unproven); message afternobody
Designproperty default (Joanna) for every topic in hours, through the chain with the screening check; emergency chain any hour; message otherwise
Missingproof on a test line shaped like Camellia's; screening check; an emergency chain
Western Slope
centralized, live
TodayKat, then Bev (20 s)outside call centernone on Clara (the call center covers it, inferred)message, by Jay's choice, if the AI remembersnobody
Designsame, at companyClara, from Fridaymaintenance manager → Jay → technicianmessage, no ringmessage
Missingemergency chain at company; every note to the shared leasing inbox with suggested category, matched AppFolio record, transcript and recording link (no test pins the recording link today)
Yale 25 Station
on-site, not live
Todayoffice phone, by accidentnobodynobodynobodynobody
Designproperty default for every topic once set; message for everything until then
Missingproperty default, emergency chain, maintenance destination; Saturday out of office hours
Situs
centralized, no line
Todayno phone line, no team email
Designcompany destinations per call type once a line exists; everything is missing today

System design (Oct 8): "one way to hand off a call" is a decision on the Clara platform vision page, beside "one picture / one door". This page stays the owner of the call-type routing design, the fix list and the proofs.

Oct 7, eveningCall routing end to end: today’s flow, six root problems, why it ended up this way, the screening risk, evidence

Call routing: end-to-end inspection and holistic design (Oct 7 evening) Proposed — pending review

Read-only, Oct 7 evening: no calls, texts or emails. Times Mountain. "Inferred" = not proven live.

TL;DR

  • One call runs through ~10 "who do we ring" lookups, 2 routing engines and 8 pickup checks. The same ask lands differently per agent.
  • The note is the weak link, not the email. 19 promised notes never went out since Sep 25 (about 3 certainly real, 2 probable). All 21 notes taken were delivered.
  • Today's Camellia fix is probably not a fix yet (inferred): it rings a number that answered with a screening prompt on Sep 29.
  • Design: two modes, one routing core on Gera's shared lookup, nearest set level wins, nothing set = message, note written after the call. One way.
  • Three decisions below. Two proofs run overnight; nothing turned on.

Inputs: Fede's rulings, 10/7

  • Centralized (company, per category) or on-site (property default for every topic). One core.
  • Nearest set level wins. No silent office-phone fallback. Nothing set → message.
  • Never strand: a person, or a note when we know who + what.
  • Live tools only for tours, calendar, transfers. "I want just one way."
  • Notes must not regress: replay every eligible call first.
  • Routing moves onto Gera's shared lookup (on a branch).

Today: one call, end to end

STAGE · HOW MANY WAYS IT IS DONE WHERE IT BREAKS 1. Call comes in 2 ways to tell whose line it is; real clients use the old one 2. An agent answers 13 agents: 2 use the connect step, 6 still have the old button Same ask, different answer Camellia leasing: message, or blind dial to office 3. Who do we ring? ~10 separate lookups · 2 routing engines 5–6 "is it open" checks · 2 hours stores + text Nobody to connect Camellia since Sep 25: 12 tries, 0 connected a direct dial that fails ends the call 4. Did a person pick up? 8 different checks; none spots a screening prompt Screener counted as a person Sep 29 Camellia: "record your name…" = connected 5. The note ~12 paths; main one is a live tool the AI must remember slowest on the line: 7 s at the slow end, 29% talked over Note never taken 19 promised, not sent (about 3 certain) since Oct 4 "no message" is dropped silently 6. Team email 1 new engine + ~15 old builders; engine on at 2 of 4 clients Delivery works 21 of 21 notes taken were delivered 7. Who watches the whole call? 7 detectors; none checks "person or note, every call" ~9 behaviors test-only run on one sandbox no real caller dials

Each stage is done several ways, each choosing its own levels. Breaks on the right are real incidents since Sep 25.

Six root problems

#Root problemEvidence, in plain wordsIncident it caused
1The note depends on the live AIThe AI must remember to call a slow tool mid-call. In 15 of 15 non-leasing misses it never fired. Every after-call safety net is test-only, retired, log-only or very narrow.19 promised notes not sent; the Camellia unit-416 applicant (5 calls)
2Routing is not one core and ignores the client's mode~10 lookups, 2 engines. The client's mode is already stored (on-site at JP&Co, centralized at Western Slope); routing never reads it.Camellia rang nobody for 12 days after the Sep 25 connect change
3"Connected" is not checked, and a dial can end in nothing"Connected" means three things: the phone company accepted the redirect, the machine check said "not a fax", or a voice was heard. A screener and a greeting are voices.Sep 29 screener counted as connected
4Test-only gates~9 behaviors run only on one sandbox. The main test lines can't reach them, so "proven on test" proves nothing about clients.Connect change "proven" on a test property set up unlike any client
5Office hours have no single home2 stores plus knowledge-base text; 5–6 "is it open" checks. Property data overrides the company card at both live clients, so card edits do nothing there.Clara told Camellia callers the office is open Saturday (9/29, 10/1)
6Nothing watches the whole callNo check that every real call ended with a person or a delivered note.12 days of "nobody to connect" logged as ordinary info

The section above's "8 of 15 reached nobody" is Camellia only; 19 here is Camellia plus Western Slope.

Why it ended up this way

  • June: agents could only act through tools, so the note email went inside a live tool.
  • Each miss got another path beside it, never "should this be live at all?" (first asked Oct 7).
  • The after-call reader (Sep 9) shipped as a test-only backup, then got pinned to one sandbox (Sep 11).
  • New settings never carried old data over. Camellia's only routing was its office phone; Sep 19–25 dropped it, proven only on test companies.

Moved to the top of the page (2026-10-07 restructure): the target design diagram, the "every setting" table and the per-client table are under The design.

Decisions for you

  1. "Take a message": explicit setting or default?
    (a) An explicit "message" setting per call type. (b) The default when nothing is set. (c) Default, and onboarding confirms each call type was looked at.
    Recommend (c): no new switch per client, matches Jay's "other → message", catches a forgotten emergency. Decided (Fede, 10/07): (c)
  2. Leasing's hidden fallback to the property office phone.
    (a) Keep. (b) Replace with explicit settings copied from each client's office phone. (c) Delete, no copy.
    Recommend (b): fits the no-silent-fallback ruling and keeps Camellia's and Yale's reach until you change it.
  3. The ordered fix list (the inspection's 9 steps; supersedes "Fix order" above).
    Recommend: yes to the "no switch" rows once proven; the three "your call" rows wait on the morning results.

Moved to the top of the page (2026-10-07 restructure): the 9-step fix list is under Decisions for you; the overnight box is under Running overnight.

Evidence (code, ids, PRs)
  • Code read at origin/main cd584d7249; prod DynamoDB, ElevenLabs and Twilio read with GETs only.
  • Counts: destination resolvers human-handoff-route.ts (resolveHumanHandoff 266-352, resolveCatchAllTransfer 641-675 org-only, catchAllTransferVars 732-794, huntTicketChain 1094-1120), ring-list-route.ts:195-275 (groupId accepted, never passed by personalization/route.ts:1604-1623), maintenance-transfer-route.ts:264, transfer-numbers-settings.ts:272. Two engines: isCallRoutingV2 at personalization/route.ts:1638; call-routing-v2-policy.ts no prod callers (inferred). "Closed now" at personalization/route.ts:1895, 1974, 3248, 3434; human-handoff-route.ts:517; handle-capture-unknown-caller-note.ts:672.
  • Test-only gate: test-property-ids.ts DARK_FEATURE_BENCH_IDS = ['yale-sandbox']; gated: hangup-message-backstop.ts, pm-page-team-note-fallthrough.ts, tools-leasing.ts:10103, call-integrity-directive.ts:163, lead-outcome-sweep.ts:123, prospect-summary.ts:476, bedroom-range-directive.ts:117, postcall-guest-card-reconcile.ts:105, language prior.
  • Pickup: hunt-chain.ts:473-480 (machineDetection Enable), :593-595, :733-737; connect-caller.ts:143-180 (no action URL, no AMD); transfer-transcript-verdict.ts:98-215 (no screening phrases); renewal voice-reach-classifier.ts:121-126 has them. Sep 29: PROP#1773625953462 CONV#conv_voice_ea11a7c8-2fd9-4431-9b2a-4ded0d267df1, Twilio CA2c028c8e631cc40e1fd7345ba0d9c84c / CAbff15c67a471a4cd6bd798b7227d531c, transferOutcome connected, basis far_side_audio.
  • Live tools (400 recent conversations, not filtered for tests): capture_unknown_caller_note 47 fires, 13 talked over, p95 7.0 s; connect_caller 48, 32 nobody to connect; native transfer_to_number Leasing 12x, Resident Services 11x since 9/28.
  • Messages: 19 promised-unsent since 9/25 (Camellia 7, Western Slope 12); certain lost: Camellia 85d05708, f2f7a9d9; Western Slope 31823381. 21 of 21 tool calls delivered. NO_MESSAGE_SKIP #16483 (2026-10-05). Notice engine teamNotifyEngine true on org_jpco, org_c42d9150 (prod read); absent on Situs org_a3376147, ConAm.
  • Prod config: Camellia ring_list.catch_all = Joanna Wieser +17209362043 (10/07 11:38 MT) = officePhone; emergencyPhone +17209890100. Western Slope human_handoff Kat +19702488394 → Beverly +19702122638 (20 s), maintenance.intake_route +19706958684. Yale PROP#1773625952029 nothing set; officePhone ends 9448. operatingModel: JP&Co per_building_team, Western Slope and Situs centralized.
  • Resolver: Gera's foldEffective, scope/resolve-effective.ts:539 (#7766, 2026-09-11), used today only by office-hours-chain.ts:303. Settings registry keys.ts: catch_all 884-912 (company only), ring_list.catch_all 747-775 (all levels). Settings page reads/writes routing at company only (company-call-routing.ts:132,145).
  • History: #1993 (Jun 6 tool), ADR-0057 #2394 (Jun 16, email inside the tool), #6660, #7508 (Sep 10 after-call reader, dark), #7801 (Sep 11 gate to one sandbox), #10009 (Sep 19 catch-all company-only, "NO FALLBACK"), #10820 (Sep 22 per-property list), #12350/#12377 (Sep 25 connect step), #13801/#13603 (Sep 29 ledger retired), #16135/#16357 (engine), #16483 (Oct 4-5).
  • Western Slope call, 2026-10-07 (raw transcript): anything else went to a voicemail emailed to the leasing shared inbox; Jay wants suggested category, matched person, transcript and recording link; emergency order "maintenance manager, then me, then maintenance technician".
  • Proof tooling reused overnight: scripts/team-notify-replay (readiness.ts, score.ts 27-point rubric with SHIP_BAR, rulings-judge.ts, blind.ts); compose.test.ts snapshots (no test pins a transcript or recording link).
Oct 7Inspection: Camellia rang nobody since Sep 25, lost messages, two operating modes, route by name, the earlier fix order

Inspection 2026-10-07: current state, what's broken, what's missing Proposed — pending review

Inspected 2026-10-07. All times Mountain. Facts are verified unless marked "inferred".

TL;DR

  • Since Sep 25, a Camellia caller who asks for a person reaches nobody. 12 connect attempts, 0 connected. An approved applicant called 5 times about a lease date mistake; nobody.
  • Cause: the new connect step only knows a company-wide "everything else" destination, and no live client had one. Before Sep 25 those calls rang the office line.
  • Fixed today for Camellia (rings Joanna in office hours; no real call has tested it yet). At Western Slope, ringing nobody for "everything else" is what Jay asked for on the 10/7 call: take a written note to the shared inbox instead. The real gap everywhere is that the note often never goes out (both Camellia callers before 9 AM on 10/7 were promised one; none sent).
  • Fede's ruling: two operating modes, centralized and on-site, one routing core. Every route ends with a person or a saved message. Nothing else is built yet.

Two operating modes Decided (Fede, 10/07)

CENTRALIZED OPERATIONS e.g. Western Slope Destinations at the company level one list per category of issue Leasing: two leasing staff, in order Maintenance: outside call center More categories will come ON-SITE TEAMS e.g. Camellia, Yale 25 Station Destinations at the property level one default list covers every topic "any topic is just the on-site manager" a category list overrides it only where one is set One routing core for every call topic → is that module on? → office hours → result same core, two places to read the destinations from Never strand: a person answers, or a message is saved a list that runs out always ends in a message with the full context Today the code mixes the two "everything else" can only be set company-wide; a property default list does not exist

Centralized clients attach destinations to the organization; on-site clients attach them to the property. That is the same org-or-property attachment Gera's portfolio architecture defines, so nothing new is being invented there.

Current state per client

ClientModeLeasing person-askEverything elseMaintenanceEmergencyHang-up noteOn the new model?
Camelliaon-siteoffice line (on-site manager) in hours; in-hours leasing connect never yet exercised livefixed today 11:38 AM property list rings the on-site manager in hours, message after hoursno destinationlegacy emergency phoneoffpartly office line and legacy emergency still carry traffic
Yale 25 Stationon-site, not live yetoffice linenonemodule offnoneoffno old paths only
Western Slopecentralizedtwo leasing staff in order, 20 s each (old-style setting)none, by choice Jay, 10/7: note to leasing@ insteadexternal call centernone on 90 propertiesoffpartly leasing uses the legacy handoff setting
Situsnot routablenonenonenonenoneoff; no team email at allno nothing configured
Test org—setsetsetseton in one sandbox onlyyes which is why tests passed

No client is fully on the new model. Only the triage and unknown-caller agents use the new connect step; five other agents still carry the old transfer button with blank legs. JP&Co's office hours (Mon–Fri 9–5, weekends closed) stand, matching Camellia's website: Fede decided 10/07 that calls respect the configured office hours and nobody is rung on Saturdays.

What's broken, worst first

#BrokenPlain proof
1"Everything else" connects to nobody at every live client since Sep 25.Camellia: 12 connect attempts, 0 connected (11 "everything else", 1 leasing after hours). An approved applicant for unit 416 called 5 times, Sep 29–Oct 7. A JP&Co staff member asked for management Oct 6, 11:59 AM, in hours; nobody rang.
2Messages silently lost. Of 15 Camellia calls since Sep 25 where a person or message was asked for or promised: 7 notes saved, 5 emails confirmed delivered (1 more probable), 8 reached nobody.Five leaks: nobody to connect; caller hangs up or gives no reason, so no note is saved; the team-side backup for unkept promises was retired (inferred: it went with the 9/29 change that stopped promise texts to callers); since Oct 4 the notice system drops any call with no message, even when we know who called and why; the hang-up safety net is on in one sandbox only, dark 28 days, no proof.
3Pickup check cannot tell a screening prompt from a person.Sep 29: an older agent transferred to the office line, hit "record your name… stay on the line", and was recorded as "connected".
4Clara improvises when there is nobody to connect.Oct 1, 12:14 PM, in hours: Clara said "the office is closed" while the call data said open. Inferred: the model filled the gap. Office hours parse fine everywhere; one bad day voiding the week is a weakness, not the cause.
5Wrong language on email.The applicant emailed Oct 7, 10:44 AM; Clara replied in Spanish because the website form tagged her as Spanish. Email skips the written-language check. Another session owns the greeting/footer side.
6Clara tells callers the office is open Saturday. Tour hours and office hours got mixed.Camellia's stored property knowledge lists Saturday 1–4:30 as office hours; Clara quoted it on Sep 29 and Oct 1. That window is the weekend tour slot, by appointment only, typed in by hand around May 10–16 when the Camellia team announced it; it went into the only hours field that existed then (the row keeps no history, so the writer cannot be named). Fede flagged the mix-up on Sep 16 and Sep 20; the fix was planned and never done. Yale 25 Station has the same problem (Saturday 9–4 in office hours since its March seed, no tour hours set). See History below. The website says Mon–Fri 9–5, and so does the company setting. Hours live in two places and disagree; the decided design already says office hours are their own setting, not tour availability.
7Her record is stale and split.Our record says "application pending" while AppFolio shows a signed lease starting Nov 1 (sync lane). She exists as two person records (identity lane).
—Blast radius at other clients: not counted yetThe count stopped before it finished. Rerun pending Fede's yes.

No cross-client leak: an Oct 1 "missed call" email to Western Slope was a Western Slope call. Recipients are always read from the call's own property or organization.

Why nobody knew

  • "Nobody to connect" is logged as ordinary information. No monitor watches transfers; the metrics checker has no voice or transfer check.
  • "Transfer outcome unknown" shows only in the app. Unkept promises feed a daily number that nothing alerts on.
  • The one voice alert is a daily regression watch posting to an internal channel.
  • Calls look fine on the surface because Clara says something polite.
  • The change was proven on the test property, which has had an "everything else" person since Sep 23, never replayed against each live client's settings. The promised proof calls were never posted. Same shape as the 9/30 AppFolio sync arms incident.

What's missing to put every client on one model

Fede: "we want every client and customer in the same new architecture." The decided design (one routing core, people lists, no keypress, caller's own number, message with full context, one notice builder, proven destinations at onboarding) still lacks five things:

GapWhat it means
Per-client modeA client is either centralized or on-site. Decides where destinations are read from. Does not exist.
Property default listOne list per property that covers every topic unless a category overrides it. Does not exist; "everything else" is company-wide only.
Migration plan for existing clientsNone written. Camellia was explicitly left on single-number forwarding pending a rehearsal; the old paths (office line, legacy emergency phone, legacy leasing setting, property hours shadowing company hours, old transfer button on five agents) still carry live traffic.
MonitoringNothing alerts when a route ends with nobody. Engineering alerts are being built now (see fix order).
One home for office hoursFede, 10/07: "the website should not be mutating company settings." Office hours live in the company setting, with a property override only where the client says so. Tour availability stays a separate setting. The website tool is for concessions only and never writes a setting Clara acts on; the code that could overwrite office hours from the website is dead today and should be deleted so it cannot be revived.
The bar: never strandEvery route, at every client, in every mode, ends with a person on the line or a saved message that reaches the team. A route that can end anywhere else is a bug, not a setting.

New: route by name Design only, not building yet

"Can I talk to the manager?"a name, a role, or "anyone on staff" Staff directory from staff records organization level for centralized clients, property level for on-site routable: property managers, leasing, maintenance. Never company admins. Clear name, in hours Clara connects caller's own number shown cell number never read aloud Outside their hours per person or team Clara takes a message for that named person Ambiguous or unknown "two people named that, which one?" or take a message never guess, never strand Today: a Camellia caller asked for a leasing person by first name on Oct 7, 8:28 AM nobody was reached

Clara stays the middleman on every leg. The directory is the staff records we already keep; nothing is typed twice.

Open questions, with a recommendation:

  1. Who is routable by name? (a) only people on a ring list; (b) every property manager, leasing agent and maintenance person on the staff record; (c) anyone on staff. Recommend (b), yes/hold.
  2. Unknown name, in hours: (a) message for "the team"; (b) fall through to the property default list; (c) ask once, then (b). Recommend (c), yes/hold.
  3. Hours for a named person: (a) the property's office hours; (b) per-person hours on the staff record, else (a). Recommend (b), yes/hold.

Test harness first, before any fix

The 10-06 page already proposes one harness: a grid of company × conversation kind × channel × situation, run on the real decision code with nothing live, red first with planted-wrong controls. This folds into that grid; no second plan.

  • Voice axis added: each route (leasing person-ask, everything else, maintenance, emergency, named person) × client shape (centralized, on-site, nothing configured) × in or after hours.
  • Baseline first: what each cell does today, on test properties only. Expected red: "everything else" at every live shape, screening-prompt pickup, "no message, no email".
  • Never a call to client staff (Fede: "no test calls"). Live-client rows are dry-resolved; the real call happens on the test property with the same shape.
  • Re-run after each fix; every fix links its before and after grid.

Fix order

Fede has approved none of these yet. Decided already: the two operating modes, calls respect configured office hours, and the website tool limited to concessions.

  1. Engineering alerts: nobody to connect, transfer outcome unknown, unkept "I'll leave a note". Deduped per client per hour, live clients only. Live 10/7 ("nobody to connect" and "outcome unknown"; the unkept-promise alert is still to do).
  2. Hang-up backstop becomes the always-on never-strand path. Dark 28 days, no proof, already listed as "delete now?" on the feature-flag page. It is a fix-shaped switch: delete the switch, keep the path. Recommend: yes awaiting Fede
  3. "No message, no email" reversed. Known caller and reason always produce a notice. Recommend: yes awaiting Fede
  4. "Everything else" falls back to a property default list, then the company list, then a message. Fixes Yale before go-live; Western Slope keeps no catch-all person by its own choice. Recommend: yes awaiting Fede
  5. Screening and voicemail detection: "record your name" is not a pickup. Recommend: yes awaiting Fede
  6. Promise backup, team side only: an unkept promise emails the team again, never the caller. Recommend: yes awaiting Fede
  7. Website tool: concessions only. Delete the dead code that could overwrite office hours, phone or amenities from the website; fix the "website" source label on manually entered concessions and the "Refresh from website" button that saves nothing. Office hours come from the company setting (property override only on the client's word); tour availability stays separate. Recommend: yes awaiting Fede
  8. Camellia: move Saturday from office hours to tour hours; same check for Yale 25 Station (Saturday 9–4). Recommend: yes awaiting Fede
  9. Finish moving office hours to one company home (the Sep 22 cleanup, never done). Recommend: yes awaiting Fede
  10. Old transfer button retired on the five remaining agents. Recommend: hold awaiting Fede until the baseline is green on 1–5.
  11. Route by name. After Fede's three answers. Recommend: hold awaiting Fede

History of the office-hours mix-up

  • May 10–16: Saturday 1–4:30 typed by hand into Camellia's office hours when the team announced the Saturday tour window. No separate tour-hours field existed.
  • Aug 15: separate hours proposed. Sep 16: Fede, "a discrepancy between office hours and touring hours." Sep 20: Fede, Camellia's Saturday is "just for tours… not an office hour."
  • Sep 17: the separate tour-hours setting exists; booking and voice read it since Oct 3–5. Nothing is set at Camellia, so booking falls back to the mixed office hours.
  • Planned, never done: restore Mon–Fri 9–5 and move Saturday to tour hours; the build check that blocks hand-typed hours; the Sep 22 "office hours to the company setting" cleanup.

Doc drift: this page's older caller-ID and pickup-mode entries predate the 9/23 rulings (original caller number shown everywhere; no keypress ever). Read those two rulings as current.

Evidence (ids, PRs, file references)
  • Connect step introduced Sep 25: four PRs plus one urgent stopgap replacing the generic transfer tool on the Triage and Unknown Caller agents. The stopgap's replay table listed Camellia's blank "everything else". No PR checked live-client config. Promised proof calls ("person-ask with catch-all set connects", call ids on the PR) never posted.
  • Camellia since Sep 25: 12 connect-step calls, 0 connected (11 everything-else, 1 leasing after hours). Sep 29 screening-prompt call recorded as connected via the older agent's office-line transfer. Oct 1 12:14 PM "office is closed" with in-hours call data.
  • Message audit: 15 calls with a person or message asked/promised; 7 notes, 5 emails delivered (+1 probable), 8 reached nobody. Leak (d) started Oct 4; leak (e) hang-up backstop dark 28 days, one sandbox only.
  • Applicant: Camellia unit 416, lease start Nov 1 in AppFolio; 5 calls Sep 29–Oct 7; email Oct 7 10:44 AM answered in Spanish (website form locale "es"; email path skips the written-language check). Two person records. Our record still "application pending".
  • Staff member asking for management: Oct 6 11:59 AM, in hours, nobody rung. Name ask: Oct 7 8:28 AM.
  • Western Slope Oct 1 missed-call email: a Western Slope call; recipients read from the call's property/org.
  • Test property: "everything else" person set since Sep 23. Camellia fix: property list written Oct 7 11:38 AM MT, dry-resolved.
  • JP&Co company hours entered Oct 5 by a PropFlow team member, Mon–Fri 9–5, weekends closed; Camellia website says the same. Stored property knowledge (KNOWLEDGE.officeHours) says Sat 13:00–16:30, present by 2026-08-29 (test comment) and 2026-09-18 (harness snapshot); that is the by-appointment tour window. The 10/07 timestamp on the row is the nightly concession watcher (13:00 UTC, Camellia and Yale 25 only) stamping its run; it writes only concessions, concessionSource and lastScrapedAt. Likely a human write path (knowledge API / details editor); origin under investigation. The website office-hours write path is dead code. Decided 10/07 (Fede): configured hours win, no Saturday ringing.
  • Harness: the 10-06 page's Step 0 (team-notify e2e grid, ~216 cells, 4 planted controls). Flag page: /a/feature-flag-sprawl-2026-09 (hang-up backstop, "delete now?").
Sep 19 – Oct 7Decision log: every decision recorded on this page

Decisions

Sep 23Caller ID on every hand-off: what the vendors support (decided)

Caller ID on every handoff: what the vendors support (2026-09-23) Decided (9/23)

MechanismCaller IDDestinationsWhisperPickup detectionSetup
ElevenLabs blind transferPreserves originalOneNoneNoneNative Twilio numbers only, JSON editor only (docs)
ElevenLabs conference transferNot documentedOne, no ring groupYes (agent_message)Nonedocs
Twilio <Dial> from the inbound callPassthrough by defaultsequential, up to 10Per-number urlmachineDetection, action fallbackDial / Number

Chosen combination: blind transfer for single outside lines. For the staff ring list, our hunt step pushes TwiML <Dial callerId={From} sequential> onto the live call — Twilio's own published ElevenLabs handoff pattern (Twilio blog).

Must be proven by a real call:

Risks: STIR/SHAKEN attestation C on passed-through numbers (spam-label exposure on staff cells); error 21210 if the binding is lost (retry once from the business number); CallToken TTL undocumented; the first endpoint that connects swallows a sequential hunt.

Status: Proof in progress via the voice fast lane; PR #11075 + hunt-route PR; merge = deploy to every production agent, on Fede's yes.

Sep 22At a glance: a call today and the future as discussed Sep 22

At a glance: today and where it's going

These two pictures are the whole page in miniature — what a call does today, and where the design is headed. Everything below is the backup for these two pictures.

Today — a Western Slope‑style client (as of 9/22)

Caller Client's public numberforwarded to Clara's line Clara greets with the client's menu“Leasing, maintenance, or something else?” Leasing Maintenance Something else Clara handles it books tours, answers questions person ask: rings Kat, then Beverly office hours; skips a voicemail Forwarded to the call center shows Clara's number, not the caller's List is empty today so Clara just takes a message AFTER THE CALL, THE TEAM GETS an email notice, a thread with the transcript (forwarded leg, word for word), and the recording

One fixed menu, three lanes. The team finds out afterward — by email, transcript and recording.

Future — as discussed 9/22 Proposed — nothing built

Caller Clara greets per the org's settingcustom menu, or “How can I help you today?” One intent classifiera menu answer is just a hint One routing layertopic → is that module turned on for this client? Clara handles it module on Rings the list office hours, skips voicemail Call center forward real number when turned on Catch-all person e.g. the property manager Takes a message nobody available ALSO TRUE IN THIS DESIGN “Connect me to [name]” reaches that person directly — pending client OK Staff handoffs still show Clara's line, never a raw cell Forwarded-call transcripts show only the human conversation — no robot menus One shared notice format covers every path

One greeting setting, one classifier, one routing layer. Menu or open question, the routing underneath never changes.

What changes between the two

WhatTodayFutureStatus
The greetingFixed 3-option menu: leasing, maintenance, or something elsePer-client setting: keep the menu, or switch to an open “how can I help you today?”Proposed
Deciding what the call is aboutThe caller's menu pick decides everything downstreamOne intent classifier decides; a menu pick is just a hint to itProposed
Reaching a specific person by nameNot possible — the caller has to guess a lane and hope“Connect me to Jay” matches the person by name or role directlyProposed, pending client OK
Caller's number on a call-center forwardThe call center always sees Clara's number, never the caller'sCan show the real caller's number instead, per client, using a Twilio featureProposed — proof running
“Something else” callersGo to an empty catch-all list today — Clara just takes a messageGoes straight to the property manager (or the catch-all list, once a client fills it in)Proposed, confirming with client
Who staff calls come fromAlways Clara's line, never a raw cellSame — unchangedDecided
Sep 22Showing the caller’s own number when Clara forwards a call (decided)

Showing the caller's own number when Clara forwards a call Decided (9/22)

Full analysis and research (9/22)

What happens today

Every forward — a staff ring list, an external forward to a call center, or the catch-all office line — presents Clara's own line as the caller ID at the destination. Staff and outside destinations never see the actual caller's number on their phone screen.

That is a deliberate choice for staff (below). It is an accident for everyone else, and it broke a real call on 9/22.

What happened at Western Slope: a tenant called about maintenance and was forwarded to the client's outside maintenance call center. That center's own phone tree offers "press 1 to get a text." The tenant pressed 1. The text went to Clara's line, not the tenant's phone — because Clara's line was the caller ID the call center's system saw. Conversation conv_voice_47fe63f4, SMS thread 5d06dcb1, written up in PR #10775's body. Fede, seeing it: "I thought we fixed this."

What was "fixed" on 9/20 was a different problem. That ruling is about staff handoffs — the ring list that calls Kat, Beverly, Gera, and so on. Fede's reasoning there: Clara is the middleman, staff never see caller cells and callers never see staff cells, so staff save one number once (Clara's) and a call from it always means a real handoff. That reasoning does not apply to an external call center's own phone tree, which is not a person and does not get "Clara is the middleman" — it just needs the real number to text back.

Why the first attempt at a fix was pulled back

PR #10775 shipped a per-org voice.forward_caller_id setting ('caller' vs 'clara_line') in its first round. Round-2 review proved the premise wrong and the 'caller' mode, the setting, and its helper files were deleted in the same PR before merge:

Twilio only lets a forwarded call present the caller's own number when that number is the current call's own inbound "From." Both places PropFlow dials out from — the ring-list leg and the external-forward leg — are calls placed from the property's own line, not from the tenant. That's proven live: Twilio call CA16cded30f4fffbb359d16bf988c1cf33, documented in the route's own header at hunt/route.ts:49-56. Presenting the tenant's number there is presenting a number the account doesn't own on that leg — Twilio's <Dial> rejects it with error 13214. A reviewing bot caught this before it shipped, citing the same line numbers and a live call SID (CA429a4a046a720585bcf7be5dc0c5b648 / child CAace5f915729ae1c3901e06676b18d36a).

PR #10775, still open, says the real fix plainly: "A future redesign that dials the destination from the ORIGINAL inbound leg (where the exemption would actually apply) can reintroduce a caller-ID setting when it's real." That's Option A below. Follow-up issues #10787–#10791 track the leftover pieces.

The 9/22 proof will show whether the call token lifts this restriction on the transfer leg; if it does, the deleted setting comes back in this form.

How the call is built today (why this is hard)

Clara runs on ElevenLabs' native Twilio integration, not a SIP trunk: the inbound call registers with ElevenLabs (elevenlabs-client.ts:97-155) and ElevenLabs holds that leg directly. When Clara transfers, its transfer_to_number tool places a new, separate outbound call from the property line into PropFlow's own hunt/forward logic (hunt/route.ts) — it does not hand over the caller's original inbound leg. That new call's own "From" is the property line, which is why callerId: to (the property line) is what Twilio will actually accept there (hunt/route.ts:851-857, hunt-chain.ts:306-312).

LegWhose call is itWhere it's held
Caller's original inbound callTenant → property's public numberElevenLabs' native Twilio integration (media stream), CallSid stored on the conversation (types.ts:10223)
Transfer legProperty line → PropFlow's hunt routehunt/route.ts — this is the leg whose "From" everything downstream (ticket lookup, AMD, recording) keys off today
Destination legPropFlow → staff cell / call center / office lineTwilio <Dial>, callerId currently forced to the property line

The caller's original inbound CallSid is already known and stored — it lands on the conversation record at transfer time (personalization/route.ts:347, saved incoming/route.ts:237). So the number Option A needs is there. What's missing: (1) Twilio's call-update helper in this codebase today only tears down a call (client.ts:182) — nothing redirects a live call to new instructions yet; and (2) the hunt route resolves which caller a ring ticket belongs to by matching "From" to the property line (hunt/route.ts:643-673) — a redirected inbound leg would arrive carrying the tenant's own number as "From," which breaks that lookup as built. Both are buildable; neither is built.

Options

OptionHow it worksWhat the destination seesCaller experienceTwilio rule it relies onEffortRisk
A0. Twilio call token on the existing dial DecidedPass the inbound leg's CallToken and callerId=caller on the outbound <Dial>/Calls.create from the transfer leg — no new call flow, no leg redirect.The tenant's real numberNo changeTwilio call-forwarding caller-ID changelogS if the proof passesLow — no new call flow
A. Redirect the caller's own inbound call Fallback if the token proof failsAt transfer time, update the caller's original Twilio call (not the transfer leg) with new instructions: Calls(inboundSid).update(twiml=<Dial callerId={callerNumber}>…). The destination is dialed from the leg where the caller's own number actually is the "From."The tenant's real numberNo new hold; the call already leaves Clara at transfer today. A brief instruction swap, not a second call.Caller-ID pass-through applies to a call's own current inbound "From" (Call resource; Twilio's call-forwarding caller-ID changelog, which describes a callToken mechanism for exactly this)L — needs a live-call-update helper (doesn't exist yet), a stored inbound CallSid on the hunt ticket, and reworking ticket lookup so it doesn't require this leg's "From" to be the property lineMedium — unproven on the wire; touches recording, AMD, and ticket resolution, all of which currently assume the property line is "From"
B. Pass the number another way, keep Clara's line as caller IDWhisper the number before bridging ("caller is 970‑xxx‑xxxx, connecting"); SIP custom headers / UUI if the destination is SIP; or sendDigits to type the caller's number into the destination's own IVR digit-by-digitStill Clara's line, but a human or IVR receives the tenant's number by voice or by digitsNo changeNone of these change callerId — whisper and sendDigits are plain <Dial> features; SIP headers need the destination to run SIP, which Western Slope's call center likely doesn'tS (whisper, sendDigits) / M (SIP headers, needs destination support)Low, but narrow: whisper only helps a human who answers live, not an automated "press 1" flow like Western Slope's. sendDigits is the one piece of B worth testing against that specific IVR this week.
C. Text the tenant's number to the call center after transferSend an SMS with the tenant's number to whatever inbox the call center readsDepends entirely on whether that center accepts and reads inbound SMS to a number Clara can reachNo changeNot a caller-ID mechanism at all — ordinary SMSSUntested whether Western Slope's center accepts this; doesn't fix the "press 1 to text" flow itself, which is the actual thing that broke
D. Dedicated per-client forwarding number, CNAM'dGive the forward its own Twilio number with CNAM set to the client's name ("Western Slope")Not the caller's number — a labeled business name instead of "Clara" or "Unknown"No changeCNAM: 15-character limit, US landlines by default, mobile opt-in only, 48–72 hours to propagate to carriersSLow, but partial: fixes confusion about who's calling, does not fix the wrong-number text

Also checked and ruled out for this problem: pre-verifying every tenant's cell as a Twilio Verified Caller ID is a one-time, human-in-the-loop setup per number (answer a call, read back a code) — not workable per-call for tenants who call once. SHAKEN/STIR attestation (A/B/C) is a separate carrier-trust signal, not something we set directly; a forwarded call whose caller ID doesn't match its real origin tends to land at a lower attestation level and reads as more spam-like to carriers — another reason Option A (real exemption, real match) beats spoofing the number outright. A2P 10DLC registration governs SMS/MMS trust only; it has no bearing on voice caller ID.

How other AI phone platforms handle caller ID on transfer (researched 9/22)

Comparison of how Retell AI, Vapi, Bland AI, Synthflow, and Twilio document caller ID behavior when transferring or forwarding calls to humans.

PlatformMechanismCustomer setting?LimitationDocs URL
Retell AISIP DIAL (warm: from/P-Asserted-Identity headers) or SIP REFER (cold)Yes: "Displayed Caller ID" (Agent's Number vs. User's Number)Retell Twilio numbers support both modes; Retell Telnyx supports cold transfer (SIP REFER) only. Fails if provider doesn't support caller ID override.Transfer call docs
VapiNot documentedNot foundRequires SIP provider support for custom caller ID. Documentation does not address caller ID behavior explicitly.Transfer call tool
Bland AIProxy agent dials the human (SIP DIAL inferred); configurable caller ID on proxy callYes: "Proxy Agent Number" setting (what number the AI calls the agent from)Must be E.164 format and from the same Twilio account as the original call. If not specified, system auto-selects from available numbers.Warm transfer docs
SynthflowSIP REFER (cold: passes original) or SIP DIAL (warm: new leg, default shows agent number)Yes: "Caller ID" setting with three options (Agent's number, Caller's original number, Custom number)Original caller ID on warm transfers only works with Synthflow-purchased numbers. Custom caller ID requires provider support. Twilio bring-your-own accounts need "allow any caller ID" capability (not available on most accounts).Call transfers docs
Twilio (native <Dial>)callerId parameter on <Dial> verbYes: specify callerId (any valid E.164)For inbound SIP calls: "callerId cannot be the To or From number, as there is a risk of caller ID spoofing." Must be a Twilio-owned number or verified number. For the caller's own inbound number to show on a dialed leg, that leg must be redirected from the inbound call itself, not a new outbound call.TwiML Dial documentation

Twilio callerId exemption, exact wording: "In the case of an inbound SIP call, callerId cannot be the To or From number, as there is a risk of caller ID spoofing." This restriction applies to new outbound call legs. The Twilio call-forwarding caller-ID changelog describes a callToken mechanism for re-directing the caller's own inbound leg to a new destination, which would allow that leg's "From" to be used as the callerId without violating this rule.

Decision for Fede

  1. Staff ring-list handoffs (Kat, Beverly, Gera, etc.): (a) Recommended, no change: keep Clara's line as caller ID, per the 9/20 ruling — Clara stays the middleman, staff never see caller cells. (b) Show the caller's number instead.
  2. External call-center / office-line forwards (the case that broke 9/22): (a) Recommended: build Option A — redirect the caller's own inbound call so the real number shows, once the ticket-lookup rework is proven. (b) Ship Option B's whisper/sendDigits as a stopgap this week while A is built.
  3. Ship the stopgap now, in parallel with building A? Recommended: yes, if a single test call proves the call center's IVR accepts a typed-in number via sendDigits — that's a day of work and directly un-breaks Western Slope's "press 1 to text" flow without waiting on the leg redesign.

Proof plan

Real calls between our own lines only, before anything touches a customer or a personal cell: Clara test line +1 720 807 1724, robot +1 720 594 2061, bench +1 970 715 6952. Show the destination's actual caller-ID display for each option, screenshotted or logged. Then a Willows rehearsal. Then Fede's yes, per customer, before it goes live anywhere real — same ladder as every other change on this page.

Sep 22Current state as of Sep 22, and the known gaps

Current state (as of 9/22) Proposed — pending Fede's review

How a call to a Western Slope‑style client works today, end to end.

  1. Clara answers on the property's own number (forwarded to Clara, or native). Greeting is the client's name, plus a Spanish prompt.
  2. Clara asks what the call is about and reads the client's module switches: leasing, maintenance, renewals — each independently on or off. A module that's off forwards to whatever line the client already had for it (call center, main line); a module that's on, Clara handles it herself.
  3. Leasing (module on): Clara answers leasing questions and books tours herself. "Talk to a person" rings the leasing ring list — one or more named people, in order, resolved from their profile's phone number, never a hand-typed number. Each ring is 20 seconds, accept mode is auto (Twilio answering-machine detection, no keypress; a voicemail greeting is not a pickup, the chain moves on) — corrected 9/22 after Kat's cell took a voicemail and Beverly never rang.
  4. Maintenance: emergency check runs first, before anything else, any hour — an emergency always rings the maintenance on-call list. Non-emergency: if the client has a call center, forward there (module off); if maintenance is on, Clara handles it, same ring-list shape as leasing. No renewals ring list exists — renewals is a pure on/off switch, off routes to the client's catch-all office line.
  5. "Something else": during office hours, forwards to the property manager (single person today; multiple PMs with fallback order is unbuilt). After hours, Clara takes the full message, no truncation, and emails a clean summary.
  6. Caller ID on every forward: Clara's own line, everywhere — staff ring list, call center, office line. Deliberate for staff (Clara stays the middleman, nobody exchanges cells). An open problem for call-center forwards (see above): a center's own "press 1 to text" reply lands on Clara's line, not the caller's.
  7. Afterward: every forwarded leg is recorded and transcribed, never deleted. A stranded/failed transfer still writes a conversation row. Staff get one clean notice per handoff outcome (connected, no-pickup, message taken) through one shared notice builder used by every phone agent.

Known gaps (named in the 9/22 Fede–Sean discussion or open on this page already)

GapWhat happens todayStatus
Caller ID on call-center forwardsCenter sees Clara's line; a "press 1 to text" reply goes to Clara, not the callerDecided, proven on our lines; not built
One hunt chain per lineOnly leasing and maintenance have ring lists; can't ring different people for different topics on the same call in one passBy design today, not a bug
Customer-forwarded main line as "something else" destinationRings back into Clara and drops the caller (Western Slope 9/22, three real calls bounced)Fixed in the routing rule 9/22: a customer-forwarded number is never a valid transfer destination; take a message instead
"Leasing / maintenance / something else" forces a guessAn AppFolio screening rep asking for "Jay Taylor" had to pick a bucket and picked leasing, landing on Kat's voicemail instead of JayNamed by Sean 9/22, addressed below with name/role matching
Multiple property managers on "something else"Only one PM destination supported; no fallback order if they don't pick upOpen, named by Fede 9/22 (Situs Group may need it)
Sep 22Long-term vision from Fede and Sean; the greeting is a setting

Long-term vision (from Fede + Sean's discussion, 9/22) Proposed — pending Fede's review

Source: Slack #transcripts, thread starting 1790089098.984989 (Sep 22, ~1–2 PM MT), live call notes with Fede, Sean and Gera. Quotes are verbatim from the transcript; anything not a direct quote is marked inference.

The shift in framing: stop thinking about the phone line as an old-school three-way menu (leasing / maintenance / something else) and move toward Clara understanding the call's real intent and routing on that, the way Camellia already does with an open "how can I help you" instead of a menu. Sean: the trigger for this was a caller (AppFolio's screening rep, asking for Jay Taylor) who had to guess a bucket and got misrouted. Fede agrees with the goal but is wary of complexity: "will greatly increase complexity on the phone line" and "we should take our time and do it right so we don't regress."

What they discussed

  • Open-ended intent routing, not a fixed menu. Fede's stated near-term compromise: keep "something else" going to the property manager (not leasing) as a cheap fix now; name/role matching ("I want to talk to Jay" or "I want to talk to leasing") is the next step — Fede: "probably in a day or less" to build and test.
  • Simplified mental model (Sean's, which Fede endorsed): leasing and maintenance auto-trigger from what the caller says ("I want to lease a unit," "my toilet is broken"). Everything that isn't clearly one of those two is "something else" and forwards to the property manager automatically, unless the caller says it's an emergency, which escalates.
  • Direct staff request, by name or role. If a caller asks for a specific person or role ("connect me to leasing," "let me talk to Jay") and that person is a PropFlow user with a phone number on file, Clara matches and connects them directly, with consent language.
  • Replacing the client's phone system, not just sitting on top of it. Fede, on where this goes: "essentially replacing their whole phone line because that's what RingCentral does today... it's kind of like you're no longer sitting on top, but mostly replacing all the phone infrastructure." Sean ties this to the sales pitch: clients could drop or downgrade RingCentral and keep a bare landline, "shorten your tech stack and save money with us."
  • Broader buckets discussed by Fede beyond leasing/maintenance/something-else: an explicit maintenance-emergency path (built, not hooked up for every client yet); a "time-sensitive but not emergency" bucket for things like a police officer with a search warrant, a vendor at the door, or a tenant asking how to pick up keys — during office hours, route these to the property manager.
  • Model risk, named by Fede: routing accuracy is the real risk of going open-ended ("making sure Clara understands and routes it correctly is what makes me a little nervous"). He wants more real call volume to train/tune against before switching off the current three-bucket menu, and prefers to ship the simpler pieces first (PM-catches-everything-else, then name/role matching) before attempting fully open-ended routing.
  • Inference: the "replace the phone system" direction implies staff numbers/extensions eventually live inside PropFlow (consistent with the existing routing model and the 9/20 ruling that staff use personal cells, not a company line) rather than each staff member keeping a separate carrier extension.
Quotes, verbatim
Sean"I think we should have the phone open with 'thanks for calling Western Slope, how can I help you today'… I think the call routing will work better if we make it more of an open question so that the caller doesn't have to guess if it's leasing, maintenance, or something else… kind of like what we're doing at Camellia."
Fede"I don't love that whole leasing/maintenance/something else because that's like an old school phone system… it just gets really complicated because there's a lot of variation of what people are calling about… we would need a lot of data before we get to that."
Sean"If someone says they want to lease an apartment or they have an issue with their unit, our system will recognize whether that goes down the leasing or maintenance workflow. If it's not one of those two, it's something else and just automatically forward[s] to the property manager… that kind of plays into our sales pitch — you can shorten your tech stack and save money with us."
Fede"I like your simplified mental model… I agree with pitching kind of like replacing the phone line, because they might be able to reduce their bill… I think we could simplify: if it's 'something else' and it's business hours, we always call the property manager."

Greeting is a setting; routing is one core Proposed — pending Fede's review

Fede's ruling, 9/22 ~3:10 PM MT. One voice agent, one intent classifier, one routing layer for every customer — never two architectures or two voice agents.

  • The greeting is a per-customer (organization) setting with two modes: a custom menu greeting (e.g. "leasing, maintenance, or something else" — the safe starting point for a client like Western Slope), or no custom greeting, which falls back to the generic Clara greeting, "how can I help you today?"
  • Either way, the caller's first words go to the same intent classifier. A menu answer like "leasing" or "maintenance" is just a hint that helps the classifier nail the intent — not a separate code path. Routing behind the classifier is identical in both modes: topic → is that module on for this client → Clara handles it, or it goes to a ring list, a call center, or a message.
ModeWhat the caller hearsWhat the classifier getsWhat's different downstream
Menu greeting"Leasing, maintenance, or something else?"Caller's answer, as a hintNothing
Open greeting (default)"How can I help you today?"Caller's own wordsNothing

Why: the three-bucket menu "is like an old school phone system" (Fede, on the 9/22 call), but the team wants more real call volume before dropping it for everyone. Making the greeting a setting lets each client move from the menu to open-ended at their own pace, with zero change to routing underneath.

Nothing built yet, no PR. When built, this ships as a per-organization setting; the default for every existing client is whatever greeting they already have today.

Sep 22Worth building now: the low-hanging fruit

Worth building now (low-hanging fruit) Proposed — pending Fede's review, nothing built

Each row is one piece of the vision above that's buildable with what exists today, roughly ranked by effort. Recommendation only — nothing here ships without Fede's go, per the standard rollout ladder.

ItemWhat it unlocksDepends onEffortRecommendation
"Something else" always forwards to the property manager (no leasing guess)Fixes the exact Jay Taylor / AppFolio-screener miss from 9/22; matches Sean's simplified modelExisting PM destination field; needs the fallback-order case if a client has >1 PM (Situs Group)S — part-dayBuild now — single-PM case is a small change; note the multi-PM gap as a follow-up, don't block on it
Name/role matching ("connect me to Jay" / "connect me to leasing")Lets a caller reach a specific person or role directly instead of guessing a bucketRing-list person records already carry name + role; needs an intent-extraction step in the call prompt plus consent languageS/M — Fede estimated "a day or less" to build and testBuild now — cheap, directly requested by Fede this session, low regression risk since it only adds a new way to reach ring-list people that already exist
Caller-ID redirect on forwards (Option A, above)Fixes the call-center "press 1 to text" misfire; caller's real number shows on any forward, not just Clara's lineNew live-call-update helper (doesn't exist), moving ticket lookup off "From = property line," re-proving recording/AMD against the new leg shapeL — needs a redesign of how the transfer leg is built, touches recording/AMD/ticket resolutionBuild now, but scoped as its own PR chain — already scored "recommended" above; not truly low-hanging (L effort) but highest-pain open bug on the page
Open-ended "how can I help you" intent routing (replacing the 3-bucket menu)The actual long-term vision: Clara reads intent instead of a caller guessing a categoryMore real call volume to tune against (Fede: routing accuracy is the risk); a broader intent taxonomy than today's 3 bucketsL — open research + iteration, not a fixed scopeLater — Fede's own call: wants more data first, ship the two items above before touching the menu itself
Multiple PropFlow-user destinations for "something else" with fallback orderHandles clients with more than one property manager (e.g. Situs Group)Same ring-list-person infrastructure as leasing/maintenance, reused for a PM listS — reuses existing ring-list code pathLater, on demand — build when a client actually needs it (Situs Group onboarding is the likely trigger)
Replace the client's phone system entirely (staff numbers/extensions inside PropFlow)The stated end state: client drops or downgrades RingCentral, becomes a sales pitch ("shorten your tech stack")Everything above proven first; a much bigger scope (extensions, voicemail, possibly outbound dialing UI for staff)XL — multi-week, not scopedNever build blind — real direction, but needs its own design doc and a customer who wants to actually cut RingCentral before scoping it
Sep 22A topic model that grows: what callers ask about and who handles it

A topic model that grows: what people really call about, and who handles it Proposed — for discussion, nothing built

The problem in one line. "Leasing / maintenance / something else" is too narrow — real calls include emergencies, timely-but-not-urgent things, move-ins, balances, renewals, vendors, verification desks and people asking for a person by name. This section proposes a wider topic list, one routing table, and a ladder that promotes a topic from "a human handles it" to "Clara handles it" only when it is genuinely bulletproof. Nothing here is built and nothing changes today.

What our own callers actually call about

Read-only pull from production, 2026-09-22. Western Slope = 40 voice calls in 30 days across 70 properties; Camellia/JP&Co = 658 voice calls over 5 months (May 1 – Sep 22). Willows (1,394 calls) is our test property and is excluded. Dead air is the single biggest bucket — 317 of Camellia's 658 calls and 9 of Western Slope's 40 had no caller speech at all. The table below counts only calls where someone actually said something: 341 at Camellia, 31 at Western Slope. Where the stored topic was the catch-all "none", 51 Camellia calls and all 13 Western Slope calls were read and labelled by hand.

TopicSub-topics seen in real callsCamellia (5 mo)Western Slope (30 d)
Leasingavailability, pricing, tour booking, application status91 — 27%17 — 55%
Vendor / contractoron site now, needs access, job status, invoice, market-data callers73 — 21%3 — 10%
Renewalsoffer received, want to renew, want to go month-to-month44 — 13%0
Resident servicesparking, amenities, packages, neighbor and noise complaints31 — 9%1 — 3%
Maintenance request (routine)plumbing, HVAC, appliance, electrical21 — 6%6 — 19%
Rent / balance / paymentwhat do I owe, how do I pay, a charge I don't recognise17 — 5%0
Ask for a person by name new"can I talk to Jay" — no topic given at all22 — 6%0
Sales / spam / robocallfake "business listing" verification scripts11 — 3%0
Move-out / turnovernotice to vacate, deposit, inspection7 — 2%0
Verification desk newemployment, income or rental-history checks by a lender or screenernot labelled3 — 10%
Owner / investorwould you manage my home; owner statement questions01 — 3%
Move-inkey pickup, utilities, move-in dateseen but not yet a stored topic (the 9/22 key-pickup call)
Emergency / urgent-not-emergency / collections / lease questions / authorityflood, fire, no heat, lockout; police with a warrant; late rent; pets and lease termsno stored topic exists for any of these — they land in the catch-all today
No contentcaller never spoke, or hung up before speaking317 of 658 calls9 of 40 calls

What the numbers say. (1) Leasing is our biggest real bucket and it is growing at Camellia — 10% of volume in May, 22% in September. (2) Vendors are the surprise: a fifth of Camellia's real calls, and the current menu has no bucket for them. (3) Maintenance is only 6% at Camellia because their calls go to a call centre — at Western Slope, where Clara takes them, it is 19%. (4) Renewals are lumpy, not seasonal: they cluster on lease-expiry dates. (5) Half of everything that reaches us is silence; any topic model must have a clean "nothing to act on" exit.

What the outside data says

SourceWhat it saysQuality
Hemlane maintenance dataPlumbing 25%, HVAC 12%, appliance 12%, electrical 8% — four categories are ~70% of maintenance volume; 3.3 requests per unit per yearVendor data, 193k work orders
AppFolio renter preferences 202666% of residents pick the phone for an emergency; 51% email and 43% text for anything routineSurvey, n not published
Buildium emergency guideEmergency = gas, fire or smoke, burst pipe or sewer backup, total loss of power, no heat or AC in extreme weather, broken exterior lock, structural damage. Everything else is next business dayVendor help doc
Property Meld triageThree tiers with response clocks — emergency 1 hour, urgent 24 hours, routine scheduled. "Not every after-hours contact is an emergency"Vendor help doc
EliseAI escalation behaviourBuckets: leasing, payments, renewals, maintenance, concierge. On an emergency the AI tells the resident it is transferring, then calls the on-call technician while the resident holdsVendor help doc
Vapi PM template / Bland / RetellAll three verify caller type (tenant vs prospect vs vendor) before routing on topic, and warm-transfer with the full transcript attachedVendor docs and templates

Where we differ from the industry picture. Every vendor taxonomy is built around maintenance and leasing. Ours is too — but vendors calling in (21% at Camellia) and verification desks (10% of Western Slope's real calls) appear in no published taxonomy we could find, and both are the exact calls that misroute today. Nobody publishes a real call-mix benchmark, so our own numbers are the only honest baseline we have. RealPage Knock and Zuma both hand off on the same rule — if the answer is not in the knowledge base, roll to a person rather than guess (Zuma, RealPage); heating emergencies spike ~300% in December–January and leasing peaks May–August at month-end (seasonality, leasing season). The one thing everyone agrees on and we do not do yet: identify who is calling before routing on what they said.

The routing table

Identity beats the stated topic. A tenant who says "leasing" is asking a renewal or lease question, never a prospect — they never get a guest card. A caller on a vendor's number who says "maintenance" is a vendor, not a work-order intake. Identity is resolved first, then topic, then the module switch.

There are exactly four outcomes. Clara handles it end to end. Transfer to a person — a role's ring list. Forward — the client's call centre, emergency line, or office line. Take a message — full context, emailed to the team.

TopicIdentity neededModule that owns itModule ON and bulletproofModule OFF or not bulletproofOffice hours vs afterWho gets the notice
EmergencyanyoneEmergency (checked first, before every other rule)Forward to the emergency on-call list, any hourForward to the client's emergency or main line; if neither, ring maintenance on-callSame both waysMaintenance on-call + org admin, immediately
Urgent, not an emergencyanyoneCatch-allTransfer to the property managerTransfer to the property managerHours: transfer. After: messageProperty manager
Maintenance requesttenantMaintenanceClara takes the work order into the PMSForward to the client's call centre or maintenance lineSame both waysOnly if it failed
Leasingprospect (never a tenant)LeasingClara answers and books the tour; person-ask rings the leasing listForward to the leasing line; after hours, messageHours: ring list. After: messageLeasing list
Move-intenant (future)Resident servicesClara answers keys, utilities, date from the knowledge baseMessage, enriched with their move-in date from AppFolioHours: transfer to PM. After: messageProperty manager
Move-outtenantTurnoverClara logs the notice and explains deposit and inspectionMessageHours: transfer to PM. After: messageProperty manager
Rent / balance / paymenttenantBillingClara reads the balance and explains how to payMessage — never guess a numberHours: transfer. After: messageOffice
Collections / latetenantBillingLevel 0 for now — always a personTransfer to the officeHours: transfer. After: messageOffice
RenewalstenantRenewalsClara handles the offer, the yes, and month-to-monthForward to the office lineSame both waysOnly on a decision
Lease questionstenantResident servicesClara answers from the client's knowledge base onlyMessageHours: transfer. After: messageProperty manager
Resident servicestenantResident servicesClara answers parking, amenities, packages; complaints always go to a personMessageHours: transfer. After: messageProperty manager
Vendor / contractorvendorMaintenanceShipped behavior overrides this proposal: every vendor call goes to a person, in office hours, even with Maintenance on — Clara never takes a vendor call herselfTransfer to the property manager — a vendor at a door cannot waitHours: transfer. After: messageProperty manager
Verification deskanyoneCatch-allLevel 0 — never Clara, this is a records releaseTransfer to the officeHours: transfer. After: messageOffice
Authority / officialanyoneCatch-allLevel 0 — never ClaraTransfer to the property manager; message carries the full statement verbatimHours: transfer. After: message + flagProperty manager + org admin
Owner / investoranyoneCatch-allLevel 0 — never ClaraTransfer to the officeHours: transfer. After: messageOffice
Ask for a person by nameanyoneCatch-allMatch the name or role to a PropFlow user and ring themTransfer to the officeHours: ring them. After: message for that personThe named person
Sales / spam / robocallanyonealways onClara ends the callClara ends the callSame both waysNobody
Everything elseanyoneCatch-allTransfer to the property managerTransfer to the property manager; never back to a client-forwarded main lineHours: transfer. After: messageProperty manager
No content——No notice unless we have both a topic and a name; otherwise silence and they call backSame both waysNobody

The promotion ladder

Every topic sits at a level, per client. A topic moves up one level at a time, never two, and only on Fede's word.

  • Level 0 — human only. Clara recognises the topic and routes it. She does not attempt an answer.
  • Level 1 — Clara collects, then hands off. She gets the name, the property, the ask, then transfers or takes a full message.
  • Level 2 — Clara answers from the knowledge base. Anything that needs an action still goes to a person.
  • Level 3 — Clara acts, a human is told. She writes to the PMS, books, logs — and the team gets a notice.
  • Level 4 — Clara acts silently. Routine, high-volume, boring. Notice only on failure.

The gate to promote a topic is all four of: the module is on for that organisation; a replay set of at least 25 real recorded calls on that topic scores 100% on the right outcome; zero "I'm not sure" or "I can't look that up" endings anywhere in the eval; and a one-switch drop back to level 1 that a session can throw without a deploy.

TopicWestern Slope todayCamellia todayHonest note
Leasing3 — Clara books tours and writes guest cards3The only topic genuinely at 3. Nine days live at Western Slope.
Maintenance request3 — Clara writes the work order0 — forwarded to their call centreCamellia never exercises it; Western Slope's 6 calls are not a proof set.
Renewals03Camellia's 44 renewal calls are our strongest replay set anywhere.
Rent / balance01Clara has the balance but we have not proven she reads it correctly out loud.
Move-out / turnover017 calls in 5 months. Not enough to promote.
Vendor1121% of Camellia's real calls and still level 1. The biggest unclaimed win.
Emergency0 — built, not hooked up0Merged dark 9/21–9/22. No client has an on-call list configured.
Everything else on the list0 — no stored topic exists for them at allMove-in, collections, lease questions, authority, owner, verification.

The "never embarrassing" guardrail

One classifier, one routing layer, both greeting modes (see greeting is a setting). A menu answer like "leasing" is a hint into the same classifier as an open "how can I help you" — never a second code path.

The rule: the classifier names the topic, then the router reads that topic's level for that organisation. Clara is allowed to attempt an answer only at level 2 or above. At level 0 or 1 she does not try and fail — she says the handoff line plainly ("Let me get you to the right person") and routes. She never says "I'm not sure" or "I can't look that up", because at those levels she was never going to look it up. That sentence existing in a transcript is a bug, not a soft failure, and it blocks promotion for that topic.

A topic Clara cannot classify at all is not a failure either — it falls to "everything else", which has a person behind it during office hours and a full message after hours.

Risks, and what not to change now

  • Do not touch Western Slope's greeting. Nine days live, three real bugs fixed this week. The menu is working. Every idea here is additive.
  • Do not widen the taxonomy in the live classifier first. More topics means more ways to misroute. Widen the routing table on paper, prove the classifier offline against recorded calls, then turn topics on one at a time.
  • Our sample is small. Western Slope: 31 real calls. Any topic under ~25 real calls cannot be promoted, which is most of this table.
  • Prompt work stays at the portfolio layer. Western Slope changes must not shift Camellia.
  • Dead air is half our volume. Before anything else, make sure a wider taxonomy does not start inventing topics out of silence.
  • Promotion is a customer-facing switch, always Fede's call. No topic goes up a level because an eval passed.

Four questions for Fede

QuestionOptionsRecommendation
Do we adopt one wide topic list now, even though most topics sit at level 0?(a) Yes — the list is the routing table, levels control behaviour. (b) No — keep three buckets and add topics only as each is built.(a). The list costs nothing until a level moves. It is how we stop "something else" from being a dumping ground.
Vendors are 21% of Camellia's real calls and have no bucket. Do we give them one?(a) Own topic, level 1, transfer to the PM. (b) Own topic, level 2, Clara confirms the job from the work order. (c) Leave them in "everything else".(a) first. Biggest miss in the data, smallest change. (b) later once (a) has a replay set.
What is the proof bar to promote a topic?(a) 25 real recorded calls at 100%, zero "I can't look that up", kill switch to level 1. (b) 50 calls. (c) Judgement call each time.(a). 50 is unreachable for most topics at our volume; a judgement call is how a topic gets promoted on a good feeling.
Emergency is built but no client has an on-call list. Who turns it on first?(a) Ask Western Slope for their on-call people this week. (b) Prove it at Fairhaven first, then ask. (c) Wait.(b) then (a). Prove on our own numbers, then one conversation with the client.
Sep 22Routing V2 proof of concept: what we found

Routing V2 proof of concept — what we found (Sep 22) Built dark — nothing turned on at any client

Built tonight, switch off

Five pieces shipped tonight behind a company switch that is off everywhere, so nothing changed for any client. We also built a proof runner: it lists every number a company's routing could dial, then dials each one.

PieceWhat it does
The switchNew routing on or off, per company. Off everywhere today.
Emergency first hopRings the first person on the on-call list, any hour, before any other rule.
Maintenance list = routine person-askA person-ask about maintenance rings the maintenance list, like leasing.
Catch-all fold"Something else" rings the catch-all list instead of dead-ending in a message.
Topic foldThe first place the caller's topic actually picks the route.

Number fence: the runner dials only numbers we own and hand it — the bench line +1 970-715-6952 and two of our test phones. Anything else is refused before dialing.

The policy, as the transcripts state it

CallIn hoursAfter hoursNobody answers
EmergencyRing the on-call list, before every other ruleSame — ring, never a messageDown the list, then an "emergency, nobody answered" notice
Leasing, caller asks for a personRing the leasing list in order, 20s eachTake the message, email itClara offers to take a message
Something elseRing the property managerTake the message, email itTake a message
Timely maintenanceClient's own route (their call center)SameA forwarded-call record; never a missed call
An empty listTake a messageTake a message—

Design flaws, biggest first

  1. We don't know what the call is about when we pick the destination. The topic never steers the route. Fix: carry the topic forward into the transfer decision.
  2. "Timely" is not defined anywhere. A non-emergency police call or a vendor at a door falls into "something else". Fix: Fede's call below.
  3. An emergency rings one person and stops. Nobody else is tried, nobody is told. Fix: go down the list, then alert.
  4. Clara can't ring the on-call list mid-call. Only the after-hours maintenance path reaches it. Fix: one transfer tool usable anywhere, with a test.
  5. A company with no office hours on file has no row. It reads as closed all day and never rings anyone. Fix: add the row, pick the default.
  6. Two of the three lookups can't tell "nobody set this up" from "the read timed out." A blip reads as an empty list, so Clara takes a message when she should ring. Fix: all three report the same three answers.
  7. The older person-transfer path skips the middle rung. A group-level list is silently ignored. Fix: walk the same three rungs everywhere.
  8. Nothing writes the emergency route. It can only be configured by hand. Fix: a setter, like the ring lists have.
  9. The voice log prints staff names to the console. Names land in raw logs nobody scrubs. Fix: log an id.

Decisions for Fede

Where we can prove it

Fairhaven TEST is ready: one destination, the bench line, and the runner plans a real dial to it. The Willows cannot be proven yet — its person-transfer row still points at two personal cells, so the fence refuses every leg. Point that row at the bench line first; until then there is no Willows proof.

Facts that corrected the brief

Where the code is

PRConcern
#10953The company switch
#10955Emergency first hop
#10956Ring lists; maintenance as routine person-ask
#10959Topic fold
#10960Integration branch to main
#10954Proof runner: enumerate and dial every destination
Sep 21Emergency line proposal, and the Sep 22 recording fixes

Emergency line Proposed — pending review (2026-09-21)

What it is: a company-level on-call list for genuine emergencies, one list per asset type (residential, commercial) — same ring mechanics as the leasing on-call list (ring the primary, then the next), but it rings any hour and ignores office hours. A configured list is on; no list is off. Gera's outbound relay call and no-answer ladder for a dropped caller stay exactly as they are.

Every call gets sorted into one of three tiers:

TierWhat lands hereWhere it goes
Ring nowLife-safety property emergencies (gas, fire, flood, no heat in winter, injury) and authority/urgent-access callers — police with a search warrant, fire department, a process server, a welfare check, a break-inThe on-call list rings immediately, any hour
Office lineTime-sensitive but not an emergency — a vendor drop-off, a delivery, a utility call, a tow, an inspectorThe office main line, any hour
MessageEverything elseClara takes the message

When it's ambiguous, Clara asks one question, once: "So I get you the right person — do you need someone right now, tonight?" Authority callers skip the question. If anyone is in danger, Clara says to call 911 first, before anything else.

One rule for every agent (Fede, 2026-09-21): the same ask gets the same outcome no matter which Clara agent takes the call — triage, maintenance, an unknown caller, leasing, or the after-hours desk. One shared tier rule, not one per agent.

Rollout: built dark. Proven overnight on the Fairhaven test line, bench number only — no calls to real people. Turning it on for a client, and who is on their list, is the client's and Fede's call, made after that proof.

Questions for the client, tomorrow

PR chain

PRWhatStatus
#10663Settings keys — emergency.route.residential / emergency.route.commercial carry the on-call people listMerged
#10676Resolver — resolveEmergencyHandoff + the 'emergency' verdict role, hands the list to the hunt ring line, hours ignoredOpen
#10686Shared tier rule — same ring-now / office-line / message ask gets the same outcome in every agentMerged
NextEmergency hunt ticket + urgency field, nobody-answered notice, tool wiring, settings page, decision logNot yet opened

Frozen contract and the Smith/Gera revision behind this section: session 001-59 scratchpad, 2026-09-21. No new registry keys — this reuses Gera's existing emergency.route.* keys with the people-list schema.

Recording and record-keeping fixes (2026-09-22)

Live for all customers, bug fixes, no gate:

Sep 20 – 21What’s proven live, and Western Slope’s first day

What's proven

StatusWhatEvidenceNext step
Proven live, twiceCore path: caller asks Clara for a person, primary answers, call is bridgedStand-in rehearsal: 2 of 2 accept runs through Clara's front door, ticket acceptedBy set within 17 s, no email; 2 of 2 nobody-answers runs recorded the caller's message and wrote exactly one handoff entry each (02:18:45Z, 02:20:00Z)Western Slope switched on 2026-09-20 21:35 MT on Fede's yes: Kat Barker first, Beverly Villar second, 20 s each, office hours Mon–Fri 9–5 Mountain, weekends take a message. See the switch-on record.
Proven liveRing chain advances past no-answerTest 1: rang Fede (3s, dropped), advanced to Gera (8s, voicemail), fell to the message pathShips as the Phase 1 default
Proven livePress-1 acceptTest 2: Fede accepted, ticket acceptedBy set, bridgedStays as the fallback mode
Now the default (2026-09-22)Automatic pickup on confirmed human (reported ~3.6s)Test A3: verdict human landed at +3.6s inside a 12s legAt the time, AMD returning unknown on Fede's real answer twice in run 3 kept press-1 as the production mode. Shipped in PR #10835: automatic pickup (AMD) is now the default (human_handoff.acceptMode absent = amd); press-1 is the fallback, used only when AMD returns unknown.
Proven liveVoicemail detected and skippedTest A / A2: Gera's leg scored machine_start, chain fell through. Stand-in run 03:03Z: person 1 (voicemail) skipped after 8 s, person 2 rang 2 s later, accepted, bridged call recorded 15 s.—
Off by default (Fede, 9/20 night)Ring-time textTwilio SMS SM0d9468531fa7c48c19eb19979c079204, delivered 20:37:46Z / 14:37 MT to Fede's phone, 2s after ring start: “Test Prospect, prospect is calling. Missed it? Call or text this number back and Clara will connect you.”Turned off by default after Fede's live test: the text arrived but the callback path was not reliable. Stays a per-customer setting, default off (#10336 merged).
Proven liveDecline advances to the next personRun 3, call B: Fede declined, Gera's phone rang 0 s later, nobody picked up, the caller was offered to leave a messageDone. Update 2026-09-22 (#10835): automatic pickup (AMD) is now the default accept mode; press-any-key is the fallback for an AMD unknown verdict, not the default anymore.
Proven liveCaller-number passthrough (carrier level)Test C: Twilio presented the caller's own number, not Clara's lineStays off as a platform setting — see the caller-ID decision below; removal merged #10223 (part 2 #10225 closed, already covered)
Built, not yet provenWhisper terse (name/role only)Decided 2026-09-20: whisper content cut to "who, about what"; shipped in #10158, merged into #10157's branchWillows probe call to prove it live
Off by default (Fede, 9/20 night)Text-on-ring#10212 merged tonight; per-customer off setting #10250 merged 9/20Default off since #10336. Core is: a caller who wants a person reaches the primary, then the secondary.
Proven liveRecording of every transferred legStand-in accept run 03:01Z: Twilio recording RE fc948af3… (16 s) on the bridged call, link rows stored against the conversation (#10351)Per-property setting, default off. Turn on per customer.
Proven liveForward to the client's maintenance line, and its recordingStand-in rehearsal 03:24Z: a robot tenant on the Willows line asked for maintenance; Clara said "I'll connect you with the maintenance line now" and transferred straight to the stand-in number (Twilio leg 17 s, completed). The full call, maintenance segment included, is on the caller-leg recording RE79038d72… (39 s), kept on the conversation with the transfer time stamped.Done. Note: a direct forward (no phone-menu step) has no separate recording on the forwarded leg; the audio lives on the caller's leg. Western Slope's maintenance route is a direct forward, so this is the shape it gets.
Removed (decided 9/20)Clara blind-transfer leg for passthrough#10058 — being closed as superseded now that raw-number passthrough is removedSuperseded by issue #10205; no further work
Proven liveOffice-hours routing (ring vs. message)Same Sunday evening: closed hours took a message (03:55Z); hours opened for the window rang the list (03:01Z, 03:06Z).Done.
Proven live at company levelOne ring-list-per-role settingsFairhaven test company (line +1 720 807 1724), list written at the company level only: accept ticket 03:06Z resolved to fairhaven-homes and was accepted; busy stand-in wrote exactly one no-pickup handoff at 03:09Z.Done.
Built, not yet provenHandoff outcome on the activity ledger#10211 merged tonightWillows run 3 tonight
Built, not yet provenBench voicemail greeting#10210 merged tonightWillows run 3 tonight
Proven liveMessage after hours03:55Z (Willows closed): robot prospect asked for a person; Clara said the office was closed, took a note with the caller's number and question, nobody was rung, no ticket. Transcript conv_0401m30y7q2zeawt2dqdkj1hg4x9.Done.
Proven liveRing list read by the call (verdict fold)Stand-in rehearsal 9/20 night: every call read the Willows list and rang the stand-in line.Run 3 tonight at Willows
Failed, fix in flightCallback routing / unknown-verdict handlingTest A2: AMD status-callback 403s in prod; Test C: verdict can land after the <Dial> action already moved onFix the signature check and unknown-handling before the next live pass — no PR number in this doc yet

September 21: first day live at Western Slope, what broke and the fix

Western Slope's staff phones run on RingCentral. Their network answers a call within 1 to 2 seconds. Our "press 1 to accept" prompt waited 5 seconds, heard nothing, and dropped the staff member. Callers who asked for a person never reached Kat or Beverly.

Decision (Fede, Sept 21): plain forward is the default. Ring the first person 20 seconds. Whoever or whatever answers is connected, with no prompt and no voicemail detection. No answer or busy moves to the next person. If nobody answers, Clara takes the full message. If the client's phone system sends a call to voicemail, that is their system doing its job. Press 1 and automatic detection remain as per-company options, off by default.

After: live in production at 4:14 PM MT Sept 21 (#10567). Proven at 4:16 to 4:27 PM MT on our own test numbers only.

ScenarioOutcome
First person answersConnected, no prompt heard, both sides recorded (call CA12e8b62b978077cc7f864765d847a9e7)
First person busySecond person rang 0 seconds later and was connected (CAb56ae0aa5adf9e22e183376f9d7cd319)
First person picks up for 1 second and dropsSecond person rang and was connected (CA6133e7679e6e7138fa577d226fe5749f)
Nobody answersClara took the message, one missed-call notice (conversation conv_voice_602a5d2c-a30c-4079-8102-4170f64cfc6c)

Also shipped the same day: missed-call emails carry the whole message with nothing cut (#10591); the conversation page shows who was rung and what happened (#10575, #10587); every staff leg is recorded from the first second (#10563); playback starts at the real pickup (#10585).

Still open: "something else" callers go to the office main line instead of staff cells (#10586, in review); a phone tree on the forwarded line gets labeled as a voicemail (issue #10626); confirmed on the real Western Slope line Sept 21 at 4:34 PM MT: Kat answered in about 15 seconds, no prompt, both sides recorded.

Sep 20Today vs. new design: the Sep 20 diagrams

Today vs. new design

Today

Caller Property's public numberforwarded, unchanged Clara answers Conference transferto ONE number — the PM's personal cell Picks up Voicemail Both count as "answered"chain dies here — no further attempts No recording ofthe transferred leg PM's caller ID showsClara's number, not the caller's

One destination, one attempt. A machine pickup ends the chain exactly like a human one — no signal reaches the team.

New design

Caller Property's public numberforwarded, unchanged Clara answers Clara needs a person.What is it about? Leasing Maintenance Renewals Everything else MODULE ON FOR THIS CLIENT? EMERGENCY? MODULE ON FOR THIS CLIENT? OFFICE OPEN? On → Clara handles it Ring the maintenance on-call listany hour — even with a call center On → Clara handles it OPENRing the leasing listor the office number then — office open? NO — CALL CENTER? OPENRing the leasing list Forward to their call centernever ring their people CLOSEDTake a message CLOSEDTake a message NO — CLARA HANDLES. OPEN? OPENRing the maintenance list CLOSEDTake a message IF THE MODULE IS OFF INSTEAD IF THE MODULE IS OFF INSTEAD Forward to the client'sown line for leasing Forward to the client's ownline for renewals HOW RINGING WORKS Rings the list in order Voicemail is detected and skipped — chain moves to the next person Short whisper: caller's name and role Optional, off by default: a text naming the caller arrives as the phone rings Picks up on answer — no key press needed Every leg is recorded Staff always see Clara's number, never a raw cell Nobody picks up?Clara takes a message — always

One line, one contact. Clara asks what the call is about, checks whether that module is on for this client, then checks office hours and rings the right list — off forwards to the client's own line, voicemail is skipped, and nobody picking up always falls back to a message.

Sep 7 – 20Original research: the problem, market deep dive, vendors, options, live proof runs

Details and history

Everything below is the research, run logs and vendor mechanics behind the summary above. Tables, ledgers and call/session IDs are kept in full; surrounding prose is cut.

The problem in one paragraph

A missed hand-off is a dead end today. Clara's hand-off uses ElevenLabs' conference transfer, a black box: no control over the team member's caller ID, no signal when nobody picks up, and the missed-call log shows Clara's number, not the caller's.

Unless the team member opens Clara's follow-up email, the call is simply lost — the most-reported failure in AI-receptionist setups like ours, including two RingCentral community threads describing our exact configuration.

Deep dive for 2026-09-08: how the market does it, and the vendor tools we are not using

Ask from Fede: "Don't reinvent the wheel — if Twilio and ElevenLabs already solve this, tell me." Target experience: everything recorded including the hand-off leg, the team phone shows the original caller, a failed hand-off returns the caller to Clara for a message, and extension habits still work. All claims below are sourced.

1. The four target behaviors are standard features across the market

Behavior we wantWho documents itWhat they call it
Team phone shows the original caller on hand-offRetell ("Displayed Caller ID: User's Number"), Vapi (callerId = customer number), Synthflow (Twilio-purchased numbers only), My AI Front Desk, EliseAICaller ID passthrough, gated behind a setting, works best on Twilio-owned numbers
Detect voicemail or no answer, then return to the callerSynthflow (Human Detection Timeout, six outcomes), Retell, Bland (Hold Timeout), Funnel Leasing (2–3 attempts then the office line), PATLive, EliseAITransfer fallback
Record the whole call including the hand-offMy AI Front Desk, Dialpad, EliseAI. Caution: Amazon Connect recording stops once an agent leaves an externally transferred call — not a universal baselineContinuous recording
Reach a person by name, extension, or keypadDialpad, RingCentral, Nextiva, GoTo (directories); Retell, Vapi, Bland (keypad in parallel with speech)Directory routing; keypad in parallel with speech

The mature human-receptionist services (Smith.ai, Ruby, Abby, Nexa, PATLive) don't publish what caller ID staff see on transfer. PM-native AI vendors (AppFolio Realm-X, Knock, Entrata, Colleen, ResMan, Yardi) document almost none of the four; only EliseAI and Funnel are specific. Keypad-during-AI-conversation is a 2024–25 feature most AI-receptionist startups dropped.

2. What best-in-class sounds like when the human does not answer

  1. Clara waits for live speech before speaking, so her whisper never plays into voicemail (Synthflow's pattern).
  2. No live pickup in the window → the attempt is marked failed with a reason (timeout, voicemail, busy, error).
  3. Clara returns to the caller: "No one was available. I can take a message, or help you directly?" (EliseAI's line.)
  4. The message becomes a structured task with the caller's number, not a raw voicemail.
  5. A repeat caller isn't made to re-explain (Funnel).
  6. Final fallback: the office main line, never a dead end.
  7. One continuous recording throughout.

3. Extensions: what modern desks do instead

4. Vendor tools we are not using

ToolVendorWhat it gives usWorks with our current native setup?
Fallback URL on the phone numberTwilioBackup TwiML if ElevenLabs is unreachable — ring a person or take voicemail. Five-minute console setting. None of our lines has one.Yes; confirm it still honors on an ElevenLabs-imported number.
Keypad input during the conversation (dtmf_input_settings)ElevenLabs, added 2026-08-31Callers press digits mid-call. Serves the extension habit.Yes, untested for reliability; bench at the Willows.
Blind transfer modeElevenLabsOnly mode documented to preserve the caller's number on the team phone.Yes, one flag; loses the whisper and no-answer signal.
Voicemail detection system toolElevenLabsDetects a voicemail greeting.Presumed yes, accuracy unpublished.
Call token on the dialed legTwilioCarries the caller's verified identity onto our own dialed leg with proper trust signing.Only if we run the dial ourselves.
Answering machine detection on the dialed legTwilio94% accuracy, near 100% in message-end mode.Only if we run the dial ourselves.
Dial action callback with the dial outcomeTwilioThe mechanism for "nobody picked up, bring the caller back."Only if we run the dial ourselves.
Whisper and "press 1 to accept" on the dialed legTwilioTeam member hears who's calling before the bridge.Only if we run the dial ourselves.
Dual-channel recording on the dialed legTwilioGuaranteed human-leg recording.Already true at call start; stays true under our own dial.
Caller name lookupTwilioName, not just number, on the phone and in the email. US only, small fee.Yes.
Voice Integrity registrationTwilioRegisters our numbers with carrier spam-analytics programs.Yes.
ConversationRelayTwilioTwilio-first architecture, ElevenLabs voices. Native keypad, hand-off, recording.No — different architecture, large rebuild.
Workflows visual builder, transfer nodesElevenLabsSuccess/failure edges on tool nodes.Yes, but transfer failure-edge behavior isn't fully documented.

Confirmed gaps on ElevenLabs: no documented caller ID on conference transfers, no ring timeout, no resume-after-failed-transfer, no transfer outcome in the post-call webhook, and an open bug where the transfer cuts Clara off mid-sentence.

5. Three ways to get there, sized

PathWhat it gets usWhat it cannot doSize
A. Stay native, configure harderFallback URL, keypad, blind-transfer caller ID, voicemail detection, name lookupNo reliable "bring the caller back"; no whisper on blind; conference caller ID stays undocumentedSmall — settings plus a Willows bench
B. Native conversation, our own hand-off through TwilioEvery target behavior: real caller ID with trust signing, whisper, voicemail detection, return-to-Clara, structured message, continuous recording, keypad/name routingNeeds a custom transfer tool and a bridge handlerMedium — the shape Retell, Vapi and Bland already implement
C. Twilio-first (ConversationRelay or SIP trunk)Cleanest controlRebuild of the call layer; ElevenLabs SIP mode has no whisperLarge, not now

6. Proposed staging (for Fede to accept or change)

This week (Western Slope goes live on forwarding):

  1. Fallback URL on every production line, pointing at a human line.
  2. Turn on the missed-call email for Western Slope.
  3. Team directory on the property record: name, role, direct number, old extension. Repoint "talk to a person" off the main number.
  4. Willows bench: blind transfer and the new keypad input.

Next two to four weeks, one property at a time:

  1. Path B for the hand-off: own dial, whisper, voicemail detection, return-to-Clara. Western Slope first, then Camellia.
  2. Recognize known callers by number, route straight to their person.
  3. Register numbers with Voice Integrity; add caller name lookup.

Questions for support before relying on anything: ElevenLabs — conference-transfer caller ID, ring timeout, resume-on-failure, recording after the agent leaves. Twilio — does the fallback URL fire on an ElevenLabs-imported number, and can we regain the caller after our own dial reports no-answer.

Sources for this section

What we run today (Camellia, Yale 25 Station, Western Slope test line)

What the vendors actually support (documented)

ElevenLabs transfer to number

Twilio (we own the number and account)

RingCentral

How peers do it

Most-reported pitfalls in the field

RankPitfallSource
1Caller ID replaced by the AI's number on the transfer legRingCentral forums, Retell community, Canadian carrier reports
2ElevenLabs conference transfer cutting off earlyElevenLabs / Twilio field fix
3Voicemail mistaken for a person, so the AI "succeeds" into a voicemail boxField reports
4Codec and touch-tone distortion on multi-hop bridgesField reports
5Echo and half-duplex audio right after a transferRetell community
6Unowned caller ID blocked or spam-flaggedTwilio spam remediation docs
7Per-leg cost stacking — Twilio bills each leg, plus a small fee to receive a transferred callTwilio billing docs
8Single-vendor outage with no fallbackElevenLabs status history
9Ring-timeout and busy-line handling gapsField reports
10Recording consent on the transferred leg in all-party-consent statesRecording-law surveys

Options

OptionWhat it isFixesLeaves broken / cost
AKeep everything as isNothing — cheapest, zero reworkDead-end missed calls, late no-answer detection, the cut-off bug
BSwitch ElevenLabs to blind transferCaller ID on the team phone shows the real callerLoses the whisper; still no no-answer signal or failover. Not enterprise-grade.
C
recommended
Own the hand-off on Twilio: Clara's transfer becomes a tool call to our backend, which dials the team member with the caller's real number (or Clara's, per property), whispers who's calling, detects voicemail, rings up to N seconds, and brings the caller back to Clara on no-answerReal no-answer signal; two-channel human-leg recording comes free; full control of caller ID, ring timeout, voicemail detectionRewiring every specialist agent's transfer tool, a new bridge handler, a Willows bench, an overnight soak, then a one-property-at-a-time rollout. Spam-label risk on the real caller's number — default is Clara's number plus whisper, "show the caller" as a per-property switch.
DTwilio-first with ElevenLabs behind a SIP trunkMaximum control and recording regardless of transfer modeBigger rebuild; SIP mode has no whisper; had the July incident. Not now.

Option C was called "own the dial" in the June design note, deferred then as too much rewiring.

Recommendation

Answers to Fede's direct questions

Does "press extension X" go away?

Yes, once the main number forwards to Clara. Callers say the name; direct lines still work for anyone who already has them.

What does Kat's phone show today on a hand-off?

A PropFlow number, not the caller's. Option C can show the real caller instead, with the spam-label trade-off above.

Are hand-off calls recorded?

Yes — the whole call, hand-off included, today and under option C. Only SIP REFER would lose it, and we don't use it.

Shipped 2026-09-19 — before → after

WhatBeforeAfter
"Talk to a person"A message plus the missed-call email.Clara rings each person on an ordered list (~20s each), then falls back to the message and email. Only Willows has a list today.
Vendors calling inClara worked the call herself.Every vendor goes to a person: staff in office hours, the closed-office line after hours. Live for all customers.
Everything else Clara can't serveNo forwarding option.Forwarded to the client's own office line, set per company. Dark for Western Slope until they set a number.

Proof

How the stack actually works (vendor docs, 2026-09-19)

Principle (Fede, 2026-09-19): use what Twilio and ElevenLabs already solved — recording, ring-one-at-a-time dialing, voicemail detection, staffing queues — instead of hand-building any of it.

Vendor featureWhat it actually does (documented)Source
Twilio call status completedMeans audio flowed — a person, a menu, or a voicemail box. Never proof of a human. We decide "answered" from the keypress, not the status.Call resource
<Dial record="record-from-answer-dual">Dual-channel recording of whatever leg it's on, from answer. Attach to every Dial leg and each one records itself.Dial
<Dial sequential="true"> with several <Number> nounsCalls "up to ten agents one after the other," in order — a native ring-chain, no custom hop webhook needed.Dial
Dial timeoutDefaults to 30s per leg; Twilio adds ~5s buffer, so a "20s ring" runs closer to 25.Dial
Dial callerIdOn an inbound-triggered dial, shows the original caller by default. To show anything else, the value must be a number we own or have verified — never spoofed, never the caller's number on an inbound SIP leg.Dial
Answering Machine Detection, DetectMessageEnd"Close to 100% accurate" in the US, $0.0075/answered call. AsyncAmd=true lets the callee connect immediately while detection runs in the background.AMD FAQ, AMD
TaskRouter "call" instructionDials a worker's cell from a Twilio number, runs TwiML on the worker's leg — the hook for a "press 1 to accept" Gather. Default timeout, optional dual-channel record.TaskRouter
TaskRouter time-of-day routingEvaluates in UTC, not property-local — a build detail, not a design choice.Time-of-day routing
TaskRouter on-call rotationNot built in. Time-of-day routing is a static weekly schedule; "who's on call" has to be our own setting, flipping a worker's Activity.Time-of-day routing
TaskRouter pricing~$0.06/task after the first 100/month free. Not on Twilio's own pricing page — treat as an estimate.Twilio pricing search, unconfirmed on twilio.com/pricing
ElevenLabs transfer_to_numberA conference transfer: destination dialed and added to a conference with the caller, agent removed. Not a blind forward, not SIP REFER.Transfer to number
How an ElevenLabs agent ends a callNot only end_call: a max_duration_seconds ceiling (60–7,200s) also ends it, plus a separate channel-specific inactivity timeout.Conversation flow

Clara as the phone system (target architecture, 2026-09-19)

Listen: 4-minute episode

Harness: a real-call ring-chain harness (#10030, #10033 and two siblings) is the acceptance test for everything below; nothing turns on at Western Slope until two clean sweeps.

Two hard requirements (Fede, 2026-09-19): the whole call is recorded end to end, including after pickup and after any transfer. Staff use their own cell phone — nothing to install. Ring lists are plain cell numbers.

How the whole call gets recorded

Every dial or transfer leg carries <Dial record="record-from-answer-dual"> — the caller-facing leg, the ring-line leg, and the leg to whichever cell finally picks up. Each leg produces its own recording, listed separately on the conversation record — Twilio does the recording, not code we wrote.

LegRecorded todayReal calls checkedFix
Caller ↔ call center (after Clara hands off)0 of 35 — never, not intermittent35Add record="record-from-answer-dual" plus a recording status callback to both bridge builders (bridgeTwiml, maintenanceBridgeTwiml); recording attaches to the conversation. In flight, PR to follow.
Caller ↔ ClaraRecorded at start on every call; audio deleted afterward by policy unless a human transfer is detected → 3 of 31 kept31A retention rule (agents/clara/lib/voice/post-transfer.ts), not a recording bug. Misclassification causing most deletions fixed 2026-09-18 (#9565, #9716, related #9621). Policy itself still contradicts "record every leg, always" — a "keep every call recording" company setting, default off, is in flight.

Root cause on the transfer leg: the hand-off opens a new outbound call from the property line to a PropFlow bridge number, with no ParentCallSid link back to the inbound call, and the bridge TwiML never had a record attribute. ElevenLabs never sees this leg either. Example: 2026-09-18 23:58 UTC, 351s with Clara then 323s with the call center — both real human legs, but the Clara-side recording was misclassified and deleted, and the transfer leg was never recorded; 2026-09-18 19:36 UTC, same double gap, caller hung up after 37s. Both examples predate #9565/#9716. The Sentry "not eligible for recording" issue is harness noise from a fake CallSid, not a real call. Both fixes ship dark, default off, Fede turns on per customer.

Live proof, 2026-09-20 (Willows)

Real calls from PropFlow tester numbers into the Willows ring line (+1 970-615-5171) with a seeded handoff ticket, staff list Fede then Gera — Twilio's own call records are the evidence.

TestWhat happenedEvidence
Ring chain, nobody presses 1Rang Fede (3s, dropped), advanced to Gera (8s, voicemail), fell through to the message path; total 39sTwilio call CA8c25dd2ef82efccd4b289a66dc006bd8, child legs CAec39c37672d49133904f2a7fdec788df (Fede) and CA8f9803a8a89a28f049b314b469842dbb (Gera); ticket acceptedBy empty
Ring chain, Fede presses 1Accepted; caller bridgedTicket HUNTTICKET#+17206795566 / 2026-09-20T15:56:22.110Z acceptedBy +1 404-285-9387 at 15:56:34Z; Fede's leg 16s
RecordingCaller's leg recorded on every runRecording on CA8c25dd…, 22s
Voicemail detection, voicemail-style greeting on bench number +1 970-715-6952Machine, both modesMachineDetection=Enable → machine_start (15s); DetectMessageEnd → machine_end_other (28s)
Voicemail detection, Fede answering "hello"Human, twiceanswered_by=human on two calls (26s, 12s)
Staff phone with "Silence Unknown Callers" onAnswers and drops in 2–3s, no whisper, no accept; chain advances correctlyFour legs of 2–3s before Fede saved the ring line as a contact

Open trade-off — "Silence Unknown Callers" drops the ring unless the number is a saved contact. Showing Clara's line works once staff save it once; showing the real caller (the platform passthrough setting) gets silenced. Fede to decide: (A) real caller's number, onboarding tells staff to allow unknowns; (B) Clara's line, staff save one contact.

Automatic pickup, live run 2 (2026-09-20 afternoon)

Second round over a tunnel into the Willows hunt chain, testing automatic pickup end to end: a plain human pickup, caller-number passthrough, a bench "voicemail," and a decline. Fede answered B and C normally and declined E; test D (the product path through fastlane) wasn't run — Willows was still in message mode. All times MT.

TestChainAnsweredByWhisper served (verbatim)Accepted byVerdictCall SIDs
A 12:13Fede → Gera, flag still on press-1both legs null"Sam Prospect, prospect. Press 1." (inside a <Gather>)noneChain-advance PASS; confirmed the writer-script bug belowFede CA7f94866f…581d 9s, Gera CA2366b9…b387 4s
A2 12:24Fede → Gera, AMD onFede unknown 18s; Gera machine_start 78s"Fede test caller, prospect."noneFAIL — AMD callback 403'd, Fede's pickup never counted; chain fell to Gera's voicemail, then message-taking (RE5e745f…)Fede CAac976c…f8c, Gera CAa38ab0…6ea1
B 12:40Bench "voicemail" → FedeBench unknown 8ssame plain whisperbench leg (wrong)FAIL, design flaw — the bench rig speaks immediately, scored unknown; with unknown = accept, the chain stopped at the fake voicemail and Fede's phone never rangBench CA631a8f…12b0
A3 12:41Fede → Gera, AMD on, signature bug patchedFede human 12s, verdict at +3.6ssame plain whisperFede, automaticallyPASS — automatic human pickup, no keypress, verdict at 12:41:11, chain stoppedFede CA7c9557…fef2
C 12:43Passthrough ticket, Fede onlyFede unknown 3s, from +17205864069 (tester's own number)same plain whisperFedePassthrough PASS at carrier level; but the 3s leg ended before the ~10s-late AMD verdict, so the <Dial> action already called it "not accepted" and the caller fell to message-takingFede CA35322f…2c95
E 12:44Fede declinesFede unknown 9s, status completed; no Gera legsame plain whisperFede (wrong)FAIL — decline hit carrier voicemail, scored unknown within 6s, and unknown counted as accept: chain stopped, caller bridged into Fede's own voicemail instead of advancingFede CA0363da…8fc2
DProduct path via fastlaneNot run — Fede was on the phone for E; Willows still in message mode, needs the handoff-mode change first—

Headline: automatic human pickup PASS (test A3, 3.6s). Passthrough PASS at the carrier level (test C). Decline FAIL — reaching carrier voicemail scores unknown, and unknown is today treated as accept, so the chain stops instead of advancing (test E). Two mechanical defects: the AMD status-callback 403s in prod (test A2), and the accept verdict can land after <Dial>'s action already moved on (test C's late-verdict race). Separately, the writer script arming AMD mode wrote to a local settings file instead of prod DynamoDB on its first run (test A) — the prod-table assert passed while the actual write missed the table.

Design change needed, at the time: only a confirmed human verdict should auto-accept; unknown should fall back to whisper-plus-press-any-key, not silent accept; machine should advance the chain, not stop it. Update, 2026-09-22 — shipped in PR #10835: automatic pickup detection on staff ring-list calls is now LIVE. Default is Twilio AMD (human_handoff.acceptMode absent = amd) — no keypress needed on a confirmed human. Press-any-key is the fallback, used only when AMD returns unknown. Still open: machine_start currently hangs up the call; Fede is deciding whether to treat it as unsure instead. Per-client routing (which mode a given customer runs) is tracked on the Clara onboarding playbook, the source of truth for that.

Write ledger (all restored, verified): ring line webhook pointed at the tunnel 10:44, restored to prod 12:44. Bench line webhook pointed at the tunnel 12:29, restored 12:44. huntAcceptMode flipped press1→amd in prod Dynamo 12:23, restored to press1 12:45; callerIdPassthrough untouched (false) — the signature and unknown-handling defects are still open. No Camellia or customer data touched; dev server and tunnel stopped, worktree clean.

Run 3 and the stand-in rehearsal (2026-09-20 night)

Fede tested from his own phone first. What happened, in order:

Fede's ruling: prove it on lines we own before calling him again. The rest of the night ran on the stand-in line (+1 970 715 6952, a test number that answers and presses 1, plays a voicemail greeting, or reports busy on request). Blocker found and fixed on the way: the site's login wall was refusing Twilio's calls to that test line (#10346).

Scenario (through Clara's front door, Willows)RunsResult
Prospect asks for a person, primary answers2Pass both. Person accepted within 17 s of the ring, ticket marked accepted, no missed-call email.
Primary busy, nobody answers2Pass both. Caller recorded a message, transcript captured ("please call me back"), exactly one handoff entry per call about 90 s after the call.
Person 1 voicemail, person 2 answers1Pass. Person 1 skipped after 8 s, person 2 rang 2 s later, accepted, call recorded.
After hours (office closed)1Pass. Clara said the office was closed, took a note with number and question, rang nobody.
Accepted call is recorded1Pass. 16 s recording on the bridged call, stored against the conversation.
Company-level list (Fairhaven test company, its own line)2Pass. Accept resolved to the right property and was accepted; busy stand-in produced exactly one handoff email.

Every test setting (stand-in lists at Willows and the test company, Sunday hours, stand-in line behaviours) was restored afterward; Camellia's routing checked identical before and after. Harness grader gaps filed as #10362.

Maintenance forward and its recording (03:24Z)

Setup: the Willows company's maintenance route pointed at the stand-in line for the test (written 03:24:06Z, ended 03:35:28Z, back to unset). Recording of transferred legs was already on for Willows.

Western Slope switched on (2026-09-20, 21:35 MT)

Fede's words: "once you're highly confident this works, then you can go ahead and do Kat first, Bev second." Written at the company level, so every Western Slope property gets it:

What others do, and what Clara should do (2026-09-20)

Decided (Fede, 2026-09-20)

Fede's framing: vendor behavior is a reference, not a template — Clara is AI-first, not a phone tree.

Next build: text-on-ring (who, why, caller's number, tap-to-call), dark at Willows, plus a contact-card onboarding step. Filed for later: staff call Clara back for a summary of a handed-off call.

SystemCaller ID on the staff cellScreening
RingCentralOriginal or business number, caller's choicePress 1
DialpadOriginal or Dialpad numberSay your name, press 1
OpenPhone / QuoOriginal callerStates name before ringing
Google VoiceOriginal or GV number toggleStates name, press *
GrasshopperCaller's choicePress 1 / 2
Ooma OfficeCaller's choicePress 1 — so the cell's own voicemail can't swallow the call
Zoom PhoneOriginalPress 1, unless a company contact

AI receptionists and leasing assistants converge on a warm transfer with a briefing, no keypress:

VendorCaller IDHandoff
EliseAIOriginal callerWarm, context passed, configurable fallback
Smith.aiCaller's number when caller ID is onWarm, receptionist briefs
RubyRuby's number, keeps staff phones privateWarm
Property MeldBusiness phone only, cells hiddenWarm
GoodcallLocal numberWarm with summary
SynthflowAgent's number by default; original caller only on enterprise Twilio numbersWhisper first
BlandConfigurableProxy agent briefs, then merges

Recommendation (AI-first):

  1. The staff phone shows Clara's line; staff save it once at onboarding, and Clara texts the contact card herself.
  2. When the phone rings, Clara also texts who, why, the caller's number, and one tap to call back.
  3. Automatic pickup runs on the direct dial; press 1 is the fallback.
  4. Later: staff reply to the text ("call back in 10") and Clara relays it.

Consequence: the platform passthrough setting stays built and OFF, not the default. Option A (real number as default) requires staff to disable unknown-caller silencing and breaks under iOS 26 screening.

One routing engine for every call — Proposed, pending Fede (2026-09-20)

Proposed — pending Fede

Problem: three separate settings answer one question today — message-vs-transfer mode, the ring list, and the company office-line catch-all. Clients differ only in headcount: Camellia one (Joanna), Western Slope two, others three or four.

Proposal: one ring list per role (leasing, maintenance) at the company level, property-level only when it truly differs. Business hours decide, not a mode: during hours a caller who wants a person is always rung; nights/weekends Clara takes a message by default. Decided (Fede, 2026-09-20): a maintenance emergency after hours always rings, weekends included. Leasing is never an emergency — after hours it takes a message. Client inputs are the two lists plus office hours, a setting of its own, separate from tour availability.

List contentsWhat happens
One entry = office numberForward to the main line
One personCamellia today
Two people in orderWestern Slope today
Three or fourSame list, longer
Rotation (later)The order changes by schedule, same object
List exhaustedClara takes a message, always, not a setting

Removes: the message-vs-transfer switch, the separate catch-all setting, per-property duplicates. Keeps: the ring chain as built, recording, automatic pickup.

Fede's module layer (2026-09-20): leasing, maintenance, and renewals sell separately. A client can have leasing on and maintenance off. The ring list above only runs once a call has already been routed to the right function — the missing layer decides, per client, whether that function is Clara's to handle at all, or should forward straight to the client's own existing line for it.

PieceWhere it livesUsed by phone calls?
Module switch per client (leasing/maintenance/renewals on or off)Admin company pageNo — not read by voice
Maintenance handle-or-forward setting per propertyMaintenance intake route settingYes — Western Slope's call center
Company office line for everything elseCatch-all settingYes — off everywhere today
Human handoff transfer/message per propertyHandoff route settingYes — Camellia
Coworker/autonomous switch per propertyProperty handoff modeYes, but not about modules
Ring lists per roleNew, PR #10209, darkNot yet live

Today these five pieces are separate and only two of them (maintenance, human handoff) actually decide anything on a live call. The module switch exists at the company level but nothing in the voice path reads it yet.

The engine: one decision per call, run in order — topic, then is that module on for this client, then is the office open, then the result.

Smith's registry review 2026-09-20: resolver reuses the existing forward-to shape (maintenance.intake_route generalized per function), emergency resolves via emergency.route.*, renewals stays a module; two questions with Gera (renewals row vs topic; ring_list as successor of transfer.<role>).

TopicModule on?Office open?Result
LeasingOnOpenRing the leasing list
LeasingOnClosedClara takes a message
LeasingOff—Forward to the client's own leasing line
MaintenanceEmergency—Ring the maintenance on-call list, any hour — even if the client has a call center
MaintenanceNot an emergency, client has a call center—Forward to their call center, never ring their people
MaintenanceNot an emergency, Clara handles itOpenRing the maintenance list
MaintenanceNot an emergency, Clara handles itClosedClara takes a message
RenewalsOn—Clara handles it
RenewalsOff—Forward to the office line
Everything else—OpenOffice line, or the leasing list if there's no office line
Everything else—ClosedClara takes a message
Any list exhausted——Clara takes a message

What staff receive on a handoff

Two people can be pinged about one missed call (the rung person by text, the owner by email); the text stays off until Fede turns it on.

MomentWho gets itWhat it saysStatus
Phone ringsThe person being rungShort whisper: caller's name and role; Clara's number on the screenIn the pickup PR (#10157), armed
Phone rings (optional)The person being rungA text naming the caller and topic, no phone number; reply goes through ClaraLive, on by default, per-customer off switch (#10250)
Nobody picks upEscalation owner, else the property inboxMissed-call email: who, when, what they asked, quote, call-back lineExists today, on at Willows only; follow-up #10246 makes the call-back line go through Clara instead of the raw number
Any outcomeThe activity ledger on the recordHandoff accepted / no pickup entryMerged (#10211)

What changes in the build chain: one routing resolver that reads the module switch, a per-module forward-to number (new company-level setting, one per module), office hours, and the ring lists. The maintenance handle-or-forward setting and the office-line catch-all fold into it — they stop being separate settings and become the module-off destination for their own module. Camellia's typed number is one row in that resolver, not a special case.

#StepPRStatus
1The ring-list building blocks and the lookup that reads them#10209Merged
2Company office hours become their own setting#10208Merged
3Fold the hand-off decision (office hours + ring list) into one verdict#10213Merged 9/20
4Add the per-module forward-to number and fold the module switch into the same routing decision—Building
5The call route reads the new resolver instead of the old separate settings—Not started
6Clara's phone tree learns leasing and renewals forwarding, not just maintenance—Not started
7The ring ticket carries which role it's for (leasing, maintenance, renewals)—Not started
8Maintenance emergencies after hours ring the on-call list—Not started
9Writer and backfill scripts to set the new settings from what's live today—Not started
10A settings screen, "Phone routing," for clients to see and edit this—Not started
11Retire the old separate settings once the new ones are proven—Not started

Module off = never Clara; a client with a few buildings on Clara models as module on + company forward-to + those buildings set to Clara.
If module status can't be read during a call, Clara does today's behavior, never forwards to nothing.

Current state

Caller Customer main lineforwards to Clara ClaraElevenLabs agent on a Twilio number transfer_to_numberconference transfer Ring line+1 970-615-5171our hunt webhook Rings one at a timewhisper "press 1" (#10030)record-from-answer-dual Nobody accepts<Record> a messagemissed-call email to team Vendors / after hoursorg's own office linecatch-all
Live today: Clara answers, transfers into the ring line, our webhook hunts one person at a time. Vendors and after-hours calls skip Clara and go straight to the customer's own office line.

The routing model

Phases Sep 19 plan, superseded

Kept for history. Phase 1 shipped (our own dial on the ring chain). Phase 2 (on-call rotation) is now an onboarding answer on the ring list, not a build. Phase 3 (Twilio TaskRouter) is dropped: call-center queues are legacy VoIP, and the target is one dial path on Gera's lookup, described under Where the phone line is going.

  1. Phase 1 (now): replace the hop-by-hop hunt webhook with Twilio's own <Dial sequential="true"> across the ordered ring list, each <Number> carrying AMD, record="record-from-answer-dual", and a 20s timeout. The "press 1" whisper is now the fallback, not the default — Twilio's own docs confirm sequential is a native ring-chain, not something we're building.
  2. Phase 2: on-call rotation settings, per role — drag order, an "on call this week" pill, optional weekly auto-rotate, rendered into the ring list at call time.
  3. Phase 3: Twilio TaskRouter, once a team outgrows a short list. Workers with role attributes, queues per role, per-target timeouts and escalation. ~$0.06/task after 100 free/month (unconfirmed on Twilio's pricing page). No native scheduling — our own setting still decides who's on call.

Later — with the renewals lane (Fede, 2026-09-19)

Not a phase of this rollout — picked up later. Facts gathered now so the option is scoped when that lane resumes.

What's possible: Twilio Branded Calling shows the business name (Basic 15–32 chars; Enhanced adds a logo and call reason) on T-Mobile and Verizon only, not AT&T. Needs a Trust Hub business profile per brand, one bundle per display name. Multiple customer brand names under one PropFlow account needs secondary customer profiles (ISV model) and our primary profile approved as an ISV Reseller/Partner first. ~7 days brand review plus 1–2 days carrier propagation; per-call pricing unpublished.

Cheaper, narrower options: CNAM (legacy caller-name lookup, 15 chars, mostly landlines) is $0.01/lookup, no setup. SHAKEN/STIR A-attestation earns a "verified" mark and lowers spam risk on its own. Voice Integrity (~$10/month) registers our numbers with carrier spam-analytics programs. None of these show a name — just fewer spam flags.

SMS has no equivalent — 10DLC carries no brand name; RCS is the closest analog, a separate project.

Proposed, when this lane resumes: Step 1 — Voice Integrity, SHAKEN/STIR, CNAM per customer number now, cheap, no extra paperwork. Step 2 — Branded Calling per customer once we have their EIN, address, and a signed one-page authorization. Showing the customer's own name is the recommendation over "PropFlow."

All of this is registration with Twilio, never a code change alone — every submission is Fede's call. Sources: Branded Calling, Basic · Branded Calling, Enhanced · CNAM · Voice Integrity · Secondary compliance profiles · Alphanumeric senders (SMS)

RingCentral / call center comparison

RingCentral / call centerClara + TwilioStatus
Auto-attendant menu ("press 1 for leasing")Clara asks and understands; no menuWorks today
Ring groups / hunt (sequential or simultaneous)<Dial sequential="true"> ring list per role, whisper acceptPlanned (Phase 1) — hop-by-hop version works today
Business hours / after-hours rulesVendors and after-hours already skip to the office lineWorks today
On-call rotationPer-role settings, static two-person now, weekly rotation laterPlanned (Phase 2)
Voicemail to emailMissed-call message recorded and emailed to the role's teamWorks today
Continuous call recording through a transferrecord-from-answer-dual on every legDark — audit running, see above
Staff caller ID on callback (sees the real caller)See the decision belowOpen question
Call center queues, escalation, per-agent timeoutsTaskRouter workers/queues per rolePlanned (Phase 3)
Branded caller ID on outboundTwilio Branded Calling / Voice Integrity / CNAMLater — with the renewals lane, not this rollout
Desk phones, extensions, landline behaviorNot needed — staff use their own cellBy design

Decided (Fede, 2026-09-20, supersedes 2026-09-19): handoff calls always come from Clara's line.

Decisions (Fede)

Reply with a letter per decision, or say nothing and the recommendations ship, dark, on the ladder above.

Context block for agents

Ring line: +1 970-615-5171 (our hunt webhook, Western Slope today). Whisper "press 1 to accept": #10030. Ring-chain harness (acceptance test for everything in this section): #10030, #10033, plus two sibling PRs. ElevenLabs transfer tool: transfer_to_number, conference mode, agent leaves on join. Recording: attach <Dial record="record-from-answer-dual"> to every dial/transfer leg, one recording row per leg — never one stitched file. TaskRouter time-of-day expressions are UTC, not property-local. Nothing in Phase 1–3 turns on at a real property until two clean sweeps of the harness pass — see the standard rollout ladder at the top of this repo's CLAUDE.md.

ReferenceRelated pages and sources

Sources

Internal: docs/planning/unknown-caller-transfer-no-answer-detection.md (June 2026 design, options 2a–2d); agents/clara/lib/voice/post-transfer.ts (recording at answer, outcome classification); config/phone-registry.json; agents/clara/lib/voice-agents/prototypes/western-slope-portfolio-triage.ts (bridge callerId).

AboutOld subtitle, change stamps, the Sep 22 status note, and the full index of old sections

Old subtitle and change stamps

How we host a property's phone number and hand a caller off to a person — what's proven live, what's decided, and the research behind it.

Proposed 2026-09-07 · deep dive 2026-09-08 · live-tested and restructured 2026-09-20 · current state + long-term vision added 2026-09-22 · topic model proposed 2026-09-22 · inspection and two-mode design 2026-10-07 · call routing end-to-end inspection and holistic design 2026-10-07 evening

PropFlow Docs