Superseded Direction: Clara, the whole system. This page is the data appendix; its autonomous branch, evening roll-up and decision 2 are rejected.
Current state of every way Clara reaches the team from a prospect's turn, why there are so many, where it breaks, and the one structure underneath it: the catch-all property a phone call never leaves.
Proposed — pending review. Map and options only; nothing built. Written 2026-10-08 after the first real self-guided tour (Thomas Rossi, Northridge, Western Slope). Fede: "I thought we had simplified into one." "Something fundamental here." "Don't jump to fixing."
| Measured | Oct 7 tour, 5 hours | Target |
|---|---|---|
| Emails to the PM | 11 | 3 |
| Texts to the host | 10 | 3 |
| Emails that came through the after-reply backstop | 8 of 11 | 0 |
| Doors that can page staff from one prospect turn | 5 (A–E), plus 5 more from timers and tours | 1 |
| Duplicate locks that share nothing | 4 | 1 |
| PRs since Aug 1 touching escalation or team notices | 152 | — |
| Where the tour was filed | catch-all "front door", no unit | Northridge 565-102 |
Two structures, one root. The noise is five send doors added one incident at a time, each with its own idea of "already sent". The misfiled tour is the catch-all building a phone call is pinned to at ring time and never leaves; every downstream tool (guest card, application, escalation header, knowledge) then fails for that prospect. The Oct 4–5 "one door" work unified host texts only; escalation emails never went through it.
Overnight, Oct 8: one team-notification engine was built and measured, and it is held for your decision. The Oct 7 night goes from 11 emails to 4 plus one evening roll-up, and silent stalls from 45 to 0. Five yes/hold decisions are in section 0.
Contents
Built and measured overnight, held for Fede. Nothing is live. Fede, Oct 8 02:53Z: "a single way that's not too noisy but also doesn't drop things… I'll make the decisions in the morning."
| Replay of every real conversation, Mar 30 to Oct 8 | Today | Engine, roll-up at day's end | Engine, first note now recommended |
|---|---|---|---|
| Oct 7 tour prospect: emails to the PM | 11 | 0 now + 1 roll-up | 4 now + 1 roll-up |
| "I'll check with the team" and nobody was told | 45 | 0 | 0 |
| of those, at Camellia | 36 | 0 | 0 |
| Same-day, on-site asks held until evening | — | 6 | 0 |
| Prod's 69 escalation emails | 69 sent | 9 now, 60 to roll-up | 49 now, 20 to roll-up; 0 dropped |
| Team emails per active prospect-day: Western Slope | 0.247 | 0.200 | 0.259 |
| Camellia | 0.036 | 0.068 | 0.078 |
| All customers | 0.073 | 0.093 | 0.112 |
| Staff answers released to the prospect correctly | 18 of 18 | 18 of 18 | 18 of 18 |
| Test cases from this night the engine owns | 0 of 6 | 3 of 6 as chosen (per topic); 6 of 6 with per-person grouping. See the test section | |
Prior art. How other systems hand off to humans was researched on Sep 28: Clara as a Coworker, section 9 (frameworks pause-and-ask vs. support vendors hand off whole conversations). A narrower pass on dedupe, digests and how a human reply resumes the agent is section 0a, just below.
What this means in one line. The engine ends repeats and silent drops: the 11-email night becomes 4 plus one roll-up, and the 45 dropped promises reach the team. This chain routes 9 of the 13 doors marked "fold" through the one entry point (forward tool, team note, after-reply backstop, tour status, tour alert email, two call-end checks, the call-end message); the rest are named below. Total team email does not go down, because the drops now arrive. The big volume lever sits upstream: 43 of the 69 emails had no real question or were answerable from the knowledge base (decision 5).
All numbers come from the engine's real decision code (commit e1d2cb7) and the chosen policy: one question per person per topic, at most one update a day, and a 30-minute window after a staff answer. Each property's handoff mode is as set in production today. In the first-now column, the Oct 7 row assumes the front door is autonomous, as today; with decision 3 it is 4 + 1 either way. Emails per active prospect-day counts both the emails sent right away and the roll-ups.
The rule. One entry point, involveTeam, and one lock: one open question per person per topic. A promise to check always opens or joins exactly one question, so a silent stall cannot happen. A repeat of a question still waiting for an answer never sends again; it becomes a line in that person's one evening roll-up. A staff answer closes the question and releases Clara's reply. Tour status notices (on my way, running late, arrived) are a separate kind of record with their own one-hour window, so they never swallow a question. The roll-up is part of the engine, not a separate sender.
33 doors: 13 keep (the escalation record, staff-reply release, the hold queue, live call transfer), 13 fold into the engine, 7 delete (no longer send anything, or duplicate another door). Deleting and folding removes an estimated 11,000 to 13,500 lines. The Oct 4 "one engine" covered host texts and team notices only; escalation emails never went through it, and no old path was deleted. That is why the doors kept multiplying.
| Door | What it is | Size (lines) | Since | Verdict |
|---|---|---|---|---|
| A | notifyHumanOnce ("already told a human?") | 1,166 | Aug 26 | fold into the engine |
| A2 | PM page + matter, "REGARDLESS" | 1,299 | Aug 26 | fold into the engine 2 emails/ask, inference |
| B | Matter + decision email (openMatterAndAsk), owner chain, outbox | 6,399 | Aug 14 | keep |
| C | Team note, one per person per day | 1,036 | Sep 6 | keep |
| D | After-reply backstop (mint + flush) | 535 | Sep 6 | fold into the engine |
| E1 | Team-notify engine (notifyTeam/routeTeamNotice) | 4,894 | Oct 4 | keep core |
| E2 | Team notices module door (sendOrHoldTeamNotice) | 222 | Oct 5 | keep |
| F | notify_leasing_team (tour updates) | 1,000 | Jun 7 | fold into the engine |
| G | Reply release / relay / teaching | 5,659 | Aug 14 | keep |
| H | Promise ledger staff outbound | 1,844 | Aug 26 | delete (no longer sends) retired #13801/#13912 |
| I | Backstop staff text | 94 | Sep 28 | delete (duplicate) |
| J | Tour alert fan-out (email + texts) | 1,324 | Sep 24 | fold into the engine |
| K | forward_to_property_manager tool | 2,319 | Jun | fold into the engine 3 doors from one tool |
| L | escalate_to_human tool | 514 | Aug | keep |
| M | Gated-inbound team notice | 767 | Aug 27 | fold into the engine |
| N | Held-first-email notice (internal) | 578 | Sep 25 | keep internal |
| O | Voice missed transfer + CALLNOTICE | 5,364 (slice) | Sep 23 | fold into the engine |
| P | Hunt chain + text-on-ring | 3,426 | Sep 19-20 | keep live transfer |
| Q | Voice callback dial | 363 | Sep | keep Willows only |
| R | Capture-unknown-caller note | 1,537 | Aug | fold into the engine |
| S | Hangup-message backstop | 688 | Sep | delete (no longer sends) yale-sandbox only |
| T | Callback-confirmed backstop | 263 | Oct 3 | fold into the engine |
| U | Unmatched-maintenance backstop | 181 | Oct 1 | fold into the engine |
| V | Tour check-in ask | 1,129 | Sep 25 | keep switch ON at Western Slope, prod read |
| W | Self-guided lockbox ask, alerts, host notes | 1,497 | Sep 29 | keep Fede 10-07 |
| X | Lost-slot, booking-blocked, unchecked-voice-tour | 397 | Oct 1 | fold into the engine 2 of 3 default off; prod unverified |
| Y | PM action reminder chase | 1,520 | Jul 21 | delete (no longer sends) chase retired #16102 |
| Z | Nag pass + stuck surface | 1,814 | Aug 14 | delete (no longer sends) sends (bookkeeping stays |
| AA | Stale-mute sweep | 662 | Sep | keep internal Slack |
| AB | Bake gate / held text queue | 4,804 | Aug 30 | keep |
| AC | Staff-loop judge / owner routing | 244 | Sep 28 | delete (no longer sends) no prod caller |
| AD | pm-page team-note fallthrough | 263 | Sep | delete (no longer sends) yale-sandbox only |
| AE | Missed-call note | 2,164 | Jun 16 | fold into the engine |
Extends the Sep 28 research on Clara as a Coworker, section 9. Only the three questions that research did not answer. Property-management competitors publish no escalation mechanics; "not documented" means exactly that.
Three Specifics — Lock, Digest, Resume
Scope: Answers the prior-art doc does not address: (1) one open question per person per topic + dedupe; (2) digest/roll-up batching; (3) how human reply closes and resumes.
---
1. One Open Question Per Person Per Topic + Dedupe
| System | Mechanism | URL |
|---|---|---|
| Temporal | Signal handler validates `pending_request_id`; duplicates ignored. | Human-in-Loop |
| PagerDuty | Dedup key: repeats update existing incident, not create new. Ack stops repeats. | Incident Dedup |
| LangGraph | No dedupe; each interrupt is discrete. Second interrupt lacks event. | Double Texting |
| Freshdesk | No lock; reminders time-based, escalate after SLA. | SLA Reminders |
| Others (OpenAI, CrewAI, Sierra, Ada, Zendesk, Decagon, Intercom, Funnel, EliseAI, Realm-X) | Not documented. | — |
Finding: Only Temporal and PagerDuty document dedupe. Neither enforces "one per person per topic" with fold-in. Your design is novel.
---
2. Digest/Roll-Up Batching
| System | Batching? | Urgency Rule | URL |
|---|---|---|---|
| Freshdesk | Yes (optional); consolidated digest. | Time-based (after SLA breach). | Escalation |
| PagerDuty | No; immediate. | Real-time by problem type. | Policies |
| All others | No digest; point-in-time. | — | — |
Finding: Only Freshdesk (time-triggered). Evening roll-up batching not documented.
---
3. Human Reply Closes & Resumes
| System | Close Mechanism | Idempotent | Late Reply | URL |
|---|---|---|---|---|
| Temporal | Signal transitions state; timer expires. Duplicates rejected. | Yes | Timer fires; late signal rejected. | Signals |
| Freshdesk | Status "Pending" → "Open" on reply (automation). | Not stated | Escalation fires first; reply closes. | Auto-Reopen |
| PagerDuty | Ack stops repeats; resolve closes. Second updates same. | Yes | After timeout, escalates; reply still resolves. | Escalation |
| LangGraph | Resume re-runs node. Second interrupt not clear. | No | Not documented. | Interrupts |
| Others | Not documented. | — | — | — |
Finding: Temporal, Freshdesk, PagerDuty document idempotent loops. None document roll-up + fold-in + one-evening-answer.
---
Five Takeaways
Tested with every property run as coworker, where the settings matter most. "One per person per topic" is the only setting that never left a genuinely new topic waiting until evening at a coworker property. One update a day and a 24-hour window cost nothing extra. Total team email barely moves across rows; what changes is how much is sent at once and how much waits for the roll-up.
| What counts as "the same question" | Updates a day on an open question | Team emails per active prospect-day (sent now + roll-up) | Prod's 69: sent now / to roll-up | New topics that only reached the team at day's end | Stalls |
|---|---|---|---|---|---|
| one question per person | 0 | 0.106 (0.081 + 0.025) | 40 / 29 | 9 | 0 |
| one question per person | 1 | 0.102 (0.084 + 0.018) | 43 / 26 | 7 | 0 |
| one question per person | 2 | 0.102 (0.084 + 0.018) | 43 / 26 | 7 | 0 |
| one per person per topic | 0 | 0.112 (0.09 + 0.022) | 49 / 20 | 0 | 0 |
| one per person per topic chosen | 1 | 0.108 (0.092 + 0.016) | 51 / 18 | 0 | 0 |
| one per person per topic | 2 | 0.108 (0.092 + 0.016) | 51 / 18 | 0 | 0 |
| Case (from this night) | Expected | Today | Engine, per-topic (chosen) | Engine, per-person |
|---|---|---|---|---|
| Staff answered half a two-part ask; Clara's relay says "I'll check with the team" about the rest | no new email on the relay | 2 emails | 1 | 1 |
| After-reply backstop and forward fire on one ask, 22 seconds apart | one email | 2 | 1 same turn; 2 across two turns | 1 |
| Three address corrections in 3 minutes | one email at most | 3 | 2 | 1 |
| Kat's 28: no re-page on the relay turn (trc-01) | 0 on relay | fail | pass | pass |
| Kat's 28: one ask, one email (trc-03) | 1 | fail | pass | pass |
| Kat's 28: a refined address is one escalation (sec-07) | 1 | fail (prod opened 4) | fail (2) | pass |
What the two failures mean. Per-topic grouping guesses the topic from keywords. A follow-up worded differently ("unit", then "rent") lands in a different topic and opens a second email. Per-person grouping passes all six, but in the replay it left 7 genuinely new topics waiting until evening at coworker properties. Per-topic left none. The fix is to make a follow-up within minutes stick to the person's open question whatever its keywords. That is a small core change to make before merge (decision 1).
The per-topic column was re-scored at 05:00Z on the redesigned core (lock 151cad3750), by the real engine code with an in-memory store, not a live agent loop. The per-person column is the 03:30Z score, before the redesign. Mutation control: an engine that sends on every join turns the scored rows red. Of the other cases, 2 wait on adapters (a guest-card ask asked twice; a page with no question in it) and 33 belong to other lanes (wording, the catch-all filing, the "thank you" read as an answer, lockbox codes). They are listed with owners in #17723; that re-score is pushed at ef7f0ed8f7 with the failing per-topic cases marked as known defects pending decision 1.
Bottom up: the decision, the lock, then the evening roll-up, and only then the adapters. An adapter must never merge before the roll-up, or the asks it hands over go nowhere. Every PR is labeled held for Fede, nothing is armed to merge, and nothing has a switch.
| # | PR | What it does | Review | Status |
|---|---|---|---|---|
| 1 | #17715 | The decision: open a new question, join the open one, or nothing for a repeat | bot approved | held for Fede |
| 2 | #17719 | The one lock and the entry point; a staff answer releases it; a store failure alerts engineering and still tells the team once | bot approved | held for Fede |
| 3 | #17736 | Evening roll-up 1/4: the store and the one-email composer | bot approved | held for Fede |
| 4 | #17740 | Evening roll-up 2/4: the roll-up email kind in the team sender | in review | held for Fede |
| 5 | #17747 | Evening roll-up 3/4: the 6 PM property-local timer | in review | held for Fede |
| 6 | #17757 | Evening roll-up 4/4: a joined ask starts the timer; an ask after 6 PM goes to tomorrow | bot approved | held for Fede |
| 7 | #17726 | Live wiring: questions are the existing escalation records | bot approved | held for Fede |
| 8 | #17780 | Live wiring for the roll-up: a queued ask starts that property's 6 PM timer | in review | held for Fede |
| 9 | #17743 | Doors A, C, K: the forward tool and the team note go through the engine | bot asked changes | held for Fede |
| 10 | #17762 | Door D: the after-reply backstop goes through the engine | bot approved | held for Fede |
| 11 | #17725 | Door F: tour status texts and emails to the host go through the engine | bot approved | held for Fede |
| 12 | #17737 | Voice: the call-end message goes through the engine | bot approved | held for Fede |
| 13 | #17756 | Door J: the tour alert email keys on the tour, so it never repeats the host notice | bot approved | held for Fede |
| 14 | #17760 | Doors T and U: the call-end backstops ask the engine before opening a question | in review | held for Fede |
| 15 | #17745 | [Decision 4] Delete the engine switch: turns it on at Situs and ConAm | bot approved | held for Fede |
| 16 | #17723 | Test cases from this night, scored before and after (tests only) | in review | held for Fede |
| 17 | #17734 | Replay variant, tuning sweep, model-tier sample (scripts only) | in review | held for Fede |
The review caught real drops. Tonight the code review flagged 9 blocking defects across 6 PRs. Six of them would have silently dropped or delayed a team message: a tour status swallowing a question, a second tour status muted for a day, a forward absorbed by a stale lock, a second reschedule never told to the host, an ask after 6 PM never sent, and a staff text lost. 8 are fixed; 1 is held (#17743, below). Not in the chain yet, each with an issue or owner: delete the duplicate backstop text (door I, #17766); the PM page and missed-call note (A2, AE) and the gated-inbound notice (M); tour status rows still use a small private ledger, not the engine lock (named in #17725). Honest follow-ups after the chain: missed transfer, capture-unknown-caller, lost-slot alerts, the tour check-in ask, voice callback, self-guided host notes.
Held with an open blocking finding, not patched again: #17743 (the forward tool and the team note). It was blocked twice for the same reason: a caller tells Clara "the team has it" when the ask was only queued for the evening roll-up. The fix is one typed answer from the engine (sent now / queued for the roll-up / already told), mapped once to both the tool result and Clara's wording. It is written up in #17773 and is the first thing to build after your yes on item 1.
Replay. 1,353 real conversations (944 active prospect-days) go through the engine's real decision function, not a copy. The control reproduces prod's sends. A planted mutation (send on every join, disable stall marking) turns 6 replay tests red. 180 policy runs were made: 90 settings, each at production handoff modes and at "coworker everywhere". The redacted corpus stays off the repo (PII).
Model tier. The pilot's recorded model replies (two samples of 45 turns, plus the 28 mirrored cases) are routed through the engine; the model was not re-run. Through the engine, the model's own sends on sample A go from 20 to 4 now plus 17 roll-up lines. Every promise opens or joins a question (5 of 5 across samples).
Where the earlier "11 → 4, 61 of 69 kept" came from. The earlier one-sender replay (section 8) had no roll-up and no autonomous mode, so a moved email counted as "not sent". Here it is delivered in the roll-up. This is lane C's reading; the earlier run was not repeated.
Not proven yet.
Review history of the core (#17715).
| Door | Added | The incident it fixed | Its own lock |
|---|---|---|---|
| F notify_leasing_team (host texts) | Jun 8, #2018 | Prospects updated a tour (late, arriving) and nobody told the host. | none, then the engine below |
| G Matter + reply release | Aug 14, #5738 | Ask a human and keep working; match the answer back to the question. | reuse an open matter |
| A notifyHumanOnce | Aug 27, #6373 | Same-call retries paged the PM twice. | exact question tuple, fails open |
| C + D Team note and backstop | Sep 6, #7196 | Clara said "I'll ask the team" about a balcony question and called no tool. No email went out. | person + day + hash; "two doors, one key" |
| J Tour alert fan-out | Sep 24, #11901 | Tour senders reached the property inbox, not the subscribed staff. | tour + time |
| I Backstop staff text | Sep 28, #13366 | Text-lane deflections reached the team by email only. | conversation + hash of words |
| H Promise ledger | Oct 1, #13912 | Promises Clara made were paged to the PM, never to the caller. | human_notify + team-note lock |
| E Team-notices engine | Oct 4–5, #16356 + #16651 | "The one door": Hold, Supervised, Live gating for team notices. Wired into host texts and tour updates. | its own claims |
| B Owner chain | Oct 7, #17389 | A forward with nobody on file went nowhere; now it walks owner → company email → engineering. | same as G |
"I thought we had simplified into one." We did, for one class. The Oct 4–5 engine became the single door for team notices: the texts to a host about a tour. The escalation emails to the PM (A, B, C, D) were never routed through it, and the comment in the team-note sender still says a backstop email followed by a forward about the same ask is an "accepted, documented duplicate". 152 PRs since Aug 1 touched this area; none removed a door.
Architecture map for tomorrow: /a/front-door-architecture-2026-10
Western Slope's public line rings a stand-in building called the "front door" (approved Sep 12 as a stopgap, because the rent-roll import was incomplete). It is a nameplate with no units. What happens next depends on the channel.
| Record | On the front door | On real buildings |
|---|---|---|
| Conversations | 123 (115 calls, 4 texts, 4 emails) | 91 |
| Leads | 20 | about 190 |
| Tours | 1 (Thomas) | 24 (3 with an empty unit) |
| Escalation matters | 15 (11 are Thomas's) | 17 |
| Escalation emails saying "Property: not identified yet" since Sep 24 | 11 of 23; 10 are Thomas's | |
| Front-door conversations where a real home was named and still not re-filed | about 11 of 123 (9%); the rest never named a home: maintenance, vendors, police, spam | |
| Conversations re-filed by hand | 6, all by Fede on Oct 2, note "voice conversation pinned to front-door by bindHome design" | |
The sharpest fact. Thomas never named the address; Clara did, at 19:23:56, from the listing. The voice booking tool ran during the call with listing e4a0f0d3, which belongs to Northridge unit 565-102, and still wrote the lead and the tour on the front door with the address in the unit-number field. The right building was in hand and not used. Both confirmations (his text, the team email) said "565 Redcliff Cir" and the company name, never "Northridge". No code re-files a tour off the front door; the only re-filing in the history is Fede's six by hand.
The three other failed application sends since Oct 3 are not on the front door (buildings 392 and 163). Their failure reads "AppFolio could not be asked" and the last successful AppFolio application send for Western Slope was Oct 2; that is being checked separately.
Short answer. Only Western Slope has a front door. Three causes, one a live bug: (1) a phone call is pinned to the dialled line when it rings and the conversation is never allowed to move, about 90% of what sits there, by design; (2) a first-time caller who books by phone had the tour filed on the placeholder because the booking tool was handed the line's own row and had no step to file under the unit's building (fixed for new calls by #17573, four hours after Thomas; unproven on a real call); (3) nothing moves anything afterward: Gera's daily tripwire was never built and the one-time move (step 5a) is still a dry run.
WRITE PATH PROPERTY COMES FROM GUARD Conversation, phone (call ends) the dialled line, at ring ✗ call-ended/route.ts:831-854 Conversation, text (stored row) thread's line; bindHome refuses moves ✗ conversation.ts:475-524 Conversation, text (per turn) re-resolved every turn, in memory ✓ inbound-dispatcher.ts:2626-2656 Inquiry, save_prospect turn home > person's home > line ✓~ tools-leasing.ts:4229 Inquiry + tour, phone booking listing's building (since Oct 7 23:36) ✓~ tools-leasing.ts:9118 Inquiry, email lead re-files if exactly one home matches ✓~ front-door-unit-home.ts:129-175 Tour, applyTourIntent (6 callers) whatever the caller passes ✗ apply-tour-intent.ts:926 Tour, after-call request placeholder has no units, refuses ✓ postcall-tour-request.ts:215-270 PMS write (card, showing) intent property, else listing building ✓~ drain.ts:989 Application send caller's property ✗ guest-card-send-application-intent.ts:104 Matter (escalation, team note) inherits the conversation's property ✗ escalation-matter.ts:142 Later re-file / reconciler none scheduled; hand scripts only ✗ ✓~ = guarded only when one home is already known
How much, and is it rising. Western Slope, front door / real home, per week since Sep 12 (Oct 5 = Mon-Wed).
| Week of | Conversations | Inquiries | Tours | Matters | AppFolio writes |
|---|---|---|---|---|---|
| Sep 14 | 10 / 6 | 7 / 38 | 0 / 1 | 0 / 0 | 0 / 5 |
| Sep 21 | 53 / 24 | 6 / 26 | 0 / 8 | 0 / 1 | 0 / 59 |
| Sep 28 | 35 / 46 | 5 / 24 | 0 / 11 | 2 / 14 | 0 / 117 |
| Oct 5 | 28 / 35 | 5 / 23 | 1 / 3 | 13 / 3 | 2 / 48 |
| Front door total | 126 | 23 | 1 | 15 | 2 |
Gera's design vs code today. D9 "accept the placeholder with a tripwire, a daily count of rows born under it" (Sep 10, "this week"): not built, no cron references the placeholder. Re-key script "once, after step 5a": enumerator, revert, census and dry-run plan exist (#7622, #7754, #14218, #17376), no apply. `home_unknown` / refusal states: none in code.
Options. (A) recommended, a hard rule at write time: no tour, AppFolio write or application may be created on a placeholder; it is created on the listing's building, as #17573 now does for phone bookings, extended to the other five tour callers and to application send; when no home is known, refuse and ask "which home?" instead of filing on the line; build Gera's daily tripwire as the alarm. Fix, no switch, about 1 to 2 days as 2 small PRs, plus a one-time cleanup of 23 cards, 1 tour and 2 failed intents with the existing preview/apply scripts (Fede's go on prod data). (B) nightly re-filer: contradicts #9579 (moving rows broke the tour-to-card join) and D9; only 3 of 20 sampled were resolvable anyway. (C) delete the placeholder, conversations belong to the company: that is step 5a, Gera's lane; doing it early breaks conversation keys and call-ended creation and orphans the line's knowledge, settings and voice cache, which live on the placeholder row.
Method: read-only prod scans by property id and by org id (both gave 123 live front-door conversations, 23 cards); tours and matters scanned across the 30 Western Slope property ids; transcript sample seed 20261007. Full report in the session scratchpad.
Today. The line a call rings on becomes the conversation's property. Every tool then reads "the property" off the conversation and passes it down. Leasing records inherit it. There is no separate idea of "the home this prospect is talking about" that a record must carry.
TODAY (phone) TARGET (any channel)
office line rings office line rings
└─ conversation.property = line (never moves) └─ conversation.property = line (fine, can stay)
└─ tool gets conversation.property └─ HOME resolved from what the prospect says
├─ inquiry.property = line ✗ (unit, listing, address), per turn, every channel
├─ tour.property = line ✗ ├─ inquiry.home = real home (required)
├─ PMS write = line ✗ fails ├─ tour.home = real home (required)
└─ application = line ✗ fails ├─ PMS write = real home
└─ application = real home
Text today already does the "HOME resolved per turn" half in memory; voice does not; nothing requires it on the record.
| Option | Where the rule lives | What it catches | Cost / risk |
|---|---|---|---|
| L1 Guard at each creator | A check inside the 4 places that write inquiries, tours, PMS writes, applications | Every known creator today | Small (2 PRs). Distributed: the 5th creator someone adds next month has no guard. Patches the symptom at each door, the pattern this retro is about. |
| L2 Fix the input: resolve the home per turn on every channel | The turn resolver. Voice tools receive the resolved home like text does; call end re-resolves once more | The voice gap that produced Thomas | Medium. Fixes the cause for phone, but still trusts callers; a tool that passes the line through is still allowed to write it. |
| L3 Data layer refuses: leasing records require a real home | The save functions for inquiry, tour, PMS intent, application intent reject a placeholder, hard fail, like the isolation floor | Every creator, present and future, every channel, including scripts | Medium. One rule, one place, impossible to bypass. Needs L2's resolution so voice has a home to give; otherwise phone bookings fail loudly until it lands. This is Gera's "tours are born on the home" made enforceable. |
| L4 Reconciler after the fact | A nightly job re-files placeholder records whose transcript resolves | Some of today's 23 | Rejected by the inspection: moving rows broke the tour-to-card join before (#9579); only 3 of 20 were resolvable. |
Recommendation: L3 with L2 as its enabler, in that order of design and the reverse order of shipping. Decide that a leasing record requires a real home at the data layer (L3). Ship L2 first so voice can supply one, then L3 so nothing can ever write the line again, then the daily zero-count as the alarm. L1 alone is one more patch at one more door. This touches Gera's portfolio model (home vs line), so the decision is shared with him; building is the leasing lane's.
Gera's lane (Smith, bridge, Oct 8 02:58Z): no conflict with the portfolio design; three conditions on any build. (1) Detect the placeholder with the existing company-line umbrella check, not a placeholder stamp: the live front-door row carries none on purpose, so a stamp-based guard would be dead on the one line it exists for. (2) The daily count leaves conversations out and says so in the PR body; they legitimately live on the line. (3) The refusal surfaces as a named "home unknown" result that makes Clara ask first, never a silent skip. His design's interim ("tours land on the umbrella calendar") is superseded by the ruling and gets one line updated in the same PR.
Decision asks: (1) L3+L2 as the rule, or L1 now? (2) cleanup of the 23 existing cards and Thomas's tour: with the preview/apply scripts, or leave until step 5a?
The portfolio design (portfolio-architecture-how, Oct 2) already rules on both halves:
So the catch-all is not a surprise to the architecture; it is a known interim that has now paged a customer. The fix direction is already decided there. What is not decided is when, and whether voice gets the same resolver as text before step 5a.
| Option | What changes | Oct 7 would have been | Risk |
|---|---|---|---|
| N1. One sender, one lock (recommended) | Every staff contact from a prospect turn goes through one sender keyed on person + open matter + the prospect's ask. The backstop shrinks to a yes/no "was a human contacted this turn"; if yes it is silent, if no it sends once. Corrections to an open ask edit the matter instead of emailing. Doors C and D delete; A and B become callers of the one sender. | 3 emails: upstairs 2-bed, lockbox key, application + co-signer. | Largest diff; needs the year-to-date replay as proof. Four PRs: sender, callers, delete C/D, delete the old locks. |
| N2. Shared turn-level claim | Keep the doors; add one claim per turn that every door checks before sending. | About 6 emails: kills the A+D duplicates, keeps the wording-triggered ones. | Fifth lock. Fede: "we keep making this mistake." |
| N3. Delete the backstop only | Remove D. Accept that a reply saying "I'll check with the team" with no tool call stalls silently; add a harness check that fails the reply instead. | 3 emails, but the Sep 6 balcony case regresses unless the reply check ships with it. | Cheapest. Silent stalls are the other failure mode Fede named. |
| Option | What changes | Thomas would have | Risk |
|---|---|---|---|
| P1. Tours are born on the home, every channel (recommended) | One invariant where every booking lands (apply-tour-intent): a tour needs a real building and a unit, or booking asks for the address. Voice gets the same per-turn home resolver text already has, so the home is known before the booking tool runs. Matches Gera's "born on the home" and "home_unknown refused". | Tour on Northridge 565-102; application sent after the tour; escalations named the property. | Voice resolver is new code in the call path. Gera's lane; needs his yes on sequencing with step 5a. |
| P2. Reconcile at call end | Keep the ring-time home; after the call, re-file the conversation, lead and tour to the home named during the call. | Same outcome, 10 minutes later. | Two states to keep in sync; mid-call tools still see the front door. |
| P3. Keep the umbrella until 5a | Status quo plus the tripwire count on the go-live page. | Repaired by hand each time. | Every Western Slope caller is Thomas until 5a. |
Fede: "include all of this in the test harness." Each case is a named fixture with the expected behavior; cases main still fails are marked known-gap so the check turns red the day they pass by accident.
| # | Case (from the Oct 7 trace, UTC) | Expected | Where |
|---|---|---|---|
| 1 | PM answers 00:57:50; relay turn says "I'll check with the team" about the unanswered half; backstop re-pages 00:58:04 | zero staff emails on a relay turn | coworker harness, PR pending |
| 2 | "You can rent it to me" (no question) pages the PM | no staff contact | coworker harness |
| 3 | backstop 23:51:20 + forward 23:51:42 on one ask | one email | coworker harness |
| 4 | three address corrections in 3 minutes, three emails | one or none | coworker harness; #17636 pins the count |
| 5 | tour booked on the catch-all with empty unit and address in the unit field (voice) | tour lands on the real building + unit; application runs in AppFolio mode; header names the property | tour harness, new |
| 6 | "That sounds perfect, thank you!" + signature read as the answer | not a decision | #17642 (it.fails), issue #17641 |
| 7 | PM writes "you can identify the property internally as Northridge" | conversation re-filed to that property | coworker harness |
| 8 | self-guided tour gets a host debrief | none for self-guided | #17648 |
| 9 | "call me tomorrow" after the tour → three "I'll check with the team" | host texted once, one promise sentence | #17648 |
| 10 | lockbox code asked for before arrival | sent at once, host told once | #17618, #17647 |
| 11 | self-guided email offers "between 12:00 AM and 11:30 PM" | no time range; book what they ask | PR pending |
| 12 | staff reply with a cell number saved to the public knowledge base | fact saved, number dropped | #17645 |
| 13 | PM's full answer ends with a question for the prospect; Clara emails the PM "here is what I'll pass along" and relays nothing; "That sounds perfect, thank you" then relayed as the answer | draft-then-go: Clara drafts, asks the PM "want me to send that?", sends the stored draft word for word on his yes | #17671 (held for Fede, design option D); replay of the real exchange 5/5 |
| 14 | Clara offers Thomas a tour of #204 while he is standing in #102 on a tour | never offer a tour to someone touring now or today | #17670 (fix, armed) |
Structural tier, final (no model; 1,311 real conversations, 666 people, Mar 30 to Oct 8). The control reproduces 66 of the 69 escalation emails prod actually sent and adds none; staff-reply release and matter state agree 18 of 18. So the test bed is trustworthy for "what would each rule have done to the emails we really sent".
| Variant | Of prod's 69 emails, still sent | Folded or attached instead | Matters avoided | Oct 7 prospect (prod: 11) |
|---|---|---|---|---|
| Control (today's rules) | 66 | 0 | 0 | 9 (the 2 missing are the backstop repeats, which share a key with nothing) |
| One thread per person per day | 44 | 24 | 11 | 1 sent, 10 folded |
| Attach to the open matter | 60 | 8 | 7 | 4 sent, 7 attached |
| Smarter staff-reply classifier | 66 | 0 | 0 | 9; every recorded staff answer already read as an answer, the "thank you" case is new |
Two replay runs, read them apart. The table above is the merged engine (#17667) with prod's backstop NOT modeled, so it reproduces 66 of 69. The overnight lead's run models the backstop and reproduces 69 of 69; it is the one that scores N1 (Oct 7: 11 → 4 emails; 61 of 69 kept, nothing new sent) and "N1 + one thread" (11 → 1). Its full per-rule table, dataset counts (1,353 conversations, 389 escalation points, 45 silent stalls) and the model-tier pilot live in leasing-engine-designs §54; this page stays the decision page.
Corrected Oct 8 02:40Z: the first run dropped staff answers on folded asks (review finding, fixed in #17667 before merge). "Attach to the open matter" moved from 57 to 60 kept, and the Oct 7 row from 1 to 4 sent; the one-thread row did not move. The 3 control misses are all prod sending the same forward twice within minutes. None of the variants sends anything prod suppressed. What this tier cannot see: emails a new rule would ADD, and the words Clara would say. That is the model tier, running now on a sample, scored for agreement with prod and with itself. Year-to-date pattern by customer: Camellia's failure is 36 silent stalls ("I'll check with the team", nothing opened); Western Slope's is noise (202 decision points, 274 staff replies in 229 conversations). Only 15 staff-answer relays exist in the whole history. Replay tooling: PRs #17665, #17666, #17667 (test and tooling only). The redacted dataset stays out of the repo until it passes a strict independent PII read.
Done (Oct 8, 03:45-04:01Z, on Fede's "repair, make sure the application is sent"). Nothing was sent to Thomas, Jay or Kat; the conversation was not split or moved.
| Record | Before (Oct 7 evening) | After (Oct 8, 04:01Z) | Proof |
|---|---|---|---|
| Inquiry + tour | Filed on the front-door placeholder | Filed on Northridge, unit 565-102 | tour row propertyId appfolio-403 |
| AppFolio guest card | Failed 10 times ("account could not be resolved for western-slope-front-door") | Card 2997 on Northridge, source Phone Call, created 03:45Z | outbox row sent, attempt 11; mirror shows the card under his name |
| Call note | Parked behind the failed card, would never have sent | Booking-call summary posted to card 2997 at 04:00:49Z | outbox row sent |
| Application link | Marked "no link configured", never attempted | Mark cleared; walker reached the send step and held it for quiet hours (Colorado night). Goes out on its own after 8 AM MT; a watcher confirms. | walker log: send_application_deferred_quiet_hours |
| Past showing | Failed | Left failed on purpose (would create a past appointment) | - |
Two outbox bugs this exposed, fixed in PR #17753 (bug fix, no switch; issues #17748, #17749; remaining raw-id paths tracked in #17755): (1) the card sender swaps a front-door booking to the real building for the property check but still sends the front-door id when it creates and looks up the card, so every front-door booking's card fails; (2) a call note linked to a card request never sends after the card later succeeds. Both needed hand scripts tonight.
Not in AppFolio: the "painter has the key / very interested" note from his text thread was never queued by the system, so there is nothing to release; it is a product gap, not a stuck row. Scripts live in the thomas-refile worktree; every write was ledgered.
| What | Fact |
|---|---|
| Start | Oct 7, 3:38 PM MT (robot heartbeat; Sentry issue 7779768172, 129 events by 7:54 PM) |
| Scope | Western Slope only. Camellia/JP&Co: no failed probe in 14 days, application link sent 11:30 AM MT. Situs: nothing in flight. |
| Error | "session probe was ambiguous": after login the robot loads one property page to prove the session; AppFolio now answers with a redirect back to that same page. The robot does not recognize that answer and stops rather than act while possibly signed out. A human browser follows the redirect and sees nothing wrong. |
| Cause | Most likely an AppFolio-side change. Probe code unchanged since Sep 9-10; nothing deployed near the start time; not linked to the Oct 6 PMS engine split. |
| Cost | 9 Western Slope guest-card writes failed for good, 3 still retrying. No showings and no application sends sat in that queue. Thomas's repair steps 2-4 blocked. |
Why no alert reached anyone.
Fixes. Ours, shipping as a bug fix with no switch: failed queue rows retry on their own once the session is healthy, up to 72 h, instead of waiting for a person to run the redrive script. Asked of the robot's lane (Smith, bridge thread): accept the self-redirect or fall back to a real re-login; page on repeated failed probes per account; health check reads the probe result, not the dashboard; the outage gets its own Sentry fingerprint.
Nothing on this page is built. The harness cases ship regardless as test-only PRs.