Home knowledge: how Clara answers questions about a specific home

Clara answers three kinds of rental questions: company policy (pets, fees, screening), home facts (neighborhood, schools, parking), and listing facts (price, availability). This page tracks how well that works.

Updated 2026-09-15 · Western Slope's scattered homes, pattern applies portfolio-wide · nothing live at a customer

1The picture: three knowledge levels, three channels

3x3 grid: channel by knowledge level Company knowledge Home knowledge Listing card VOICE TEXT EMAIL Nothing no rows exist yet Only after hand-off triage can't read it Up front, rich unconditional Nothing Up front, every turn Only mid-conversation thin, conditional Nothing Up front, every turn Only mid-conversation thin, conditional
Figure 1. The phone answers listing questions best, company policy worst; text and email answer home questions well but show the listing card only partway through; nobody answers company policy yet.

2Where we are, 2026-09-15 evening

ChannelStatusProof
PhonePartlyBooks, reschedules and cancels. Quality broke rulings on 2 of 3 calls: budget asked twice, stacked questions, stated our home count, implied recognizing a first-time caller; one reschedule hit the wrong day. No bench script yet for pets or insurance by voice.
TextYesAll five steps pass as a prospect: right home, rent, size; property pet rule beats company rule; company insurance rule holds; tour booked, moved, cancelled, calendar cleared. Test tenant recognized by name and home; leak gets a photo request; rent question goes to the team, not invented.
EmailBlockedClara composes the right reply, but it never arrives: our Microsoft test tenant cannot send outside itself, so every reply bounces. Test-looking sender addresses are also dropped silently. Blocked until the restriction lifts.
KnowledgeYesCompany policy reaches every channel; property policy wins on the same topic; proven live by text and by a bench call.
CalendarYesEvents land on the agent's Outlook and clear on cancel; zero leftovers after the run.

Full step-by-step results with message and tour ids are in the appendix.

3Decisions for Fede

Open: one question. When a lead names a home's street or a company but not which unit, what should Clara do?

Recommendation: (a). Keeps Clara answering instead of stalling, and never guesses on a customer's behalf.

Decided already (4)
DecisionOptions consideredDecided
How we hear a shared company mailbox(a) live subscription · (b) poll it · (c) customer forwards to us2026-09-15, Fede: (a), flexible per client — a property with its own mailbox is subscribed and answered there; a shared mailbox is subscribed once and Clara replies from it
Which address a reply comes from(a) the customer's own leasing mailbox · (b) our address2026-09-15, Fede: (a) — always the mailbox the lead's message arrived in
What stays dark in week one(a) only new leads coming through the customer's portfolio · (b) the whole mailbox goes silent2026-09-13 ruling: (a) — anyone who emails us directly still gets a full reply; only new leads from the customer's own listings are held back until Fede turns them on
Company tour length in Settings(a) add the field to the company Settings screen next week (recommended) · (b) leave at the default 30 minutes until a customer needs a different length2026-09-15, Fede: the company tour length is set from the company Settings screen, not a script. Not built yet.

4Proposed: asset type and the first reply

Proposed, pending Fede's review. Every channel assumes an apartment building, wrong for Western Slope.

What their own staff actually write

9,875 real staff messages mined, 4,337 + 5,538:

WordWestern Slope (all houses)Situs Group
unit1,151776
property1,7962,159
home / house77 / 51474
apartment32150
showing vs tour1,831 : 592,258 : 658
leasing office01

Decided (Fede, 2026-09-15). Their messages set the facts: "unit"/"property" not "home," "showing" not "tour," one exact rent, never a range; not the voice. Fede on their robotic opener: "this is a robot text, i think we could do better." Ours: greet by name, answer what was asked, name the home with rent and a fact or two, then offer two times.

What staff write on the second and third follow-up message: appendix.

What changes, and how it ships

Kinds. Four kinds (community, single home, small plex, mixed) live on the home; a marker on the company flags "leasing runs from one central number." The kind is a fact about the home, not the ad. Western Slope's 49 homes are single homes; their shared front door becomes the company's phone line, not a home.

Wording and script. For a single home: "unit," "property," or "the house on street," never apartment or floor plan; quote that home's own rent, never a range; address not office; say showing, not tour. One shared script, kind picks vocabulary. Spreads to follow-up texts, reminders, team notes. Ships Thursday on every home; script merge and plex-as-kind later.

Open: Clara's turn 2 and 3

What does Clara do next?

Recommendation: (b), wording only for Thursday. Ships Thursday: one time, yes/no ask, beds/baths/rent once, link after a showing. Later: matching each message's language, noticing a no-show before an apology.

Conflicts with Gera's portfolio design

His design makes every home its own building, phone line on the company. We use a stand-in home record as front door; our test company's 14 homes are units in one record. Agree on substance; need to settle before it's written twice.

Evidence (engineering detail)

Verified against origin/main at 8ccfb886 on 2026-09-15; read-only.

5The target shape

Three inputs feed one builder that feeds three readers Company sections Home sections Listing card Knowledge block builder filters: retired, fair-housing Phone script Text / email prompt Mid-conversation lookup
Figure 2. Different code per channel reads company policy, home facts, and the listing card today, so they drift apart. Target: one shared builder feeding all three, fixing every channel at once.

This fixes the biggest gap: company policy today lives where only the front-door property can read it. Moving it up lets any home answer a policy question. Tracked on the go-live tracker.

6Appendix (engineering detail)

Source material: file paths, PR numbers, ids, call-by-call reports. Nothing here changes the picture, table, or decisions above; it proves them.

Full precedence proof: company policy vs. property policy (2026-09-15)

Fede's rule: company (org-level) policy is injected and respected everywhere, but a property-level policy on the same topic always wins. Built and proven today on Fairhaven Residential (TEST), org org_53047a06-2c49-4231-92c2-6f2a7009149a, property fairhaven-homes (bench line +17208071724). The company knowledge base is fabricated for this test per Fede's instruction — the real customer is still writing theirs.

BeforeAfter
Company-level policy storeDid not exist — no writer, no seeded rows for any org12 policy sections seeded for Fairhaven's company (application fee, screening, pets: "no pets allowed company-wide", deposits, office hours, lease terms, utilities, parking, smoking, renters insurance: required, $100k liability, holds, tours)
Property's own Pets section"Cats and dogs are allowed at nearly every home currently listed. Breed, size, deposit and pet-rent details are not published.""Two pets allowed per home, cats and dogs. A one-time pet fee of $300 per pet applies, due at move-in." — deliberately conflicts with the company's "no pets" line on the same title
Rendered knowledge block (voice, text, email — one shared builder)Company sections never reached the builder (no reader wired)Renders the property's Pets line, not the company's; the company-only Renters insurance line still shows through untouched
Live phone call asking "do you allow pets?"n/a — no company policy existed to conflict with"Yes, we do allow pets! We allow up to two cats or dogs per home, and there's a one-time pet fee of three hundred dollars per pet due at move-in." (property wins)
Same call, "do I need renters insurance?"n/a"Yes, renters insurance is required — you'll need a policy with at least one hundred thousand dollars in liability coverage, and you'll need to provide proof before move-in and keep it current throughout your lease." (company-only policy survives)

Voice and text/email are proven identical because they call the exact same renderer (buildLeasingKnowledgeBlock, fed by the same loadOrgKnowledgeSectionsSafe helper) — confirmed by rendering it directly against the live Fairhaven row and comparing to the live call transcript, both quoting the property's pet-fee language verbatim. Live call: EL conversation conv_8401m2k509v9efnb5n56yjz01d77, +17208071724, 2026-09-15. Shipped as two small PRs: #8633 (company knowledge store + seed script) and #8638 (wired into all four leasing readers). Both dark by default — a company with nothing seeded renders exactly as before.

The 10 graded guest-card replies (engineering detail)

Ten real guest-card emails were run through the actual reply-writing step (nothing sent, nothing saved) — five re-addressed to Fairhaven's real homes, five composed for the property they originally named, Camellia, so we could see today's real Camellia reply shape too. Every reply graded clean on facts (right home, right price/beds/baths/availability, no invented numbers, nobody's name given out); the single-family wording rule (no "apartment", "unit", "floor plan", "community", a price range once a home is named, or "leasing office") held on all five Fairhaven replies. The two soft misses below are both the same pattern: Clara promising an internal follow-up ("I'll let the property team know…") that nothing is wired to yet.

CardPropertyHome named?FactsWording
1FairhavenYes (1704 Emberly Way)Pass — 2bd/1bd, $1,450, cats-only, 10/1/26 all match the live unit recordPass
2FairhavenNo — whole-homes fallbackPass — quoted range ($1,395–$1,975, 980–1,210 sqft) matches only the homes available NOW, correctly excluding two homes not ready yetPass — no banned words; "our office is open…" for scheduling is a soft miss worth watching (not one of the named hard-fail phrases)
3FairhavenYes (118 Quillwood Lane)Pass — 3bd/2ba, $2,150, 1,480 sqft, both pets, all matchPass
4FairhavenYes (146 Quillwood Lane)Pass — prospect asked about a 1-bedroom; Clara correctly says none is available rather than inventing onePass — soft miss: "I'll let the property team know… so they can follow up" is a promise nothing acts on yet
5FairhavenYes (1265 Sparrowgate Road)Pass — 3bd/2.5ba, $2,395, 1,720 sqft, both pets, all matchPass
6CamelliaNamed address is not one of Camellia's ownPass — correctly says it can't find that address instead of guessingPass (apartment language is correct here)
7CamelliaNamed address is not one of Camellia's ownPass — falls back to Camellia's real $1,000–$1,200 / 386–585 sqft range, sourced from the live unit listPass
8CamelliaNamed address is not one of Camellia's ownPass — same real range; asks the prospect to confirm before booking anythingPass
9CamelliaFollow-up on card 7 (utilities question)Pass — utilities/Xcel/Conservice answer matches the property's own knowledge text word for wordPass
10CamelliaNamed community is not CamelliaPass, soft miss — invents no Camellia facts, but promises "the leasing team for [that community] will follow up" — nobody confirmed that will happenPass

Composed read-only against propflow-prod data through the merged compose stage (PR #8622) plus a same-shape scratch runner for the Camellia leg (Camellia is a real customer, so it cannot route through the bench credential the merged script uses — the scratch runner reuses the identical knowledge/prompt/read-only-tool code and bills only the capped eval-workspace key). Nothing sent, no row written.

What staff write on the second and third message (mined evidence)

699 threads where staff wrote twice or more. Most second messages chase silence rather than answer a question, and they come fast: a median 11–19 minutes against roughly two hours on turn one. Staff name one concrete time and ask yes or no. They volunteer the home's beds, baths and rent unprompted — "By the way, Pomona units are 3 bed, 2½ baths and rent is $2,395." They switch to Spanish message by message, hand a thread to a named colleague, and answer a no-show with an apology and a new time in one message, offering a video walk-through. The application link goes out on turn two or three. Nobody uses a lockbox or self-showing. About a third of threads die right after that second message, usually just after a time is proposed.

Situs GroupWestern Slope
Threads with 2+ staff turns390399
Turn 2 follows silence69%53%
Median turn-2 reply gap19 min11 min
Thread dies after turn 242%30%
Application link on turn 2 or 3~30%~10%
Why the listing is the wrong owner, and Gera's model applied

A listing is the for-rent sign in the yard — pull the sign when the unit rents, and the house, the neighborhood, and the driveway don't go anywhere.

Proves: registry keys.ts (knowledge block, D6-A "structured knowledge INHERITS"), store.ts getPropertyKnowledge, tour-arrival-info.ts, the model chapter's invariant "one node runs a building… nothing under PROP# ever moves," and the Western Slope go-live tracker's anchor-property row.

Code truth: the channel × level matrix and the seven gaps (G1–G7)
LevelVoiceSMSEmail
(1) Org / company sectionsNot at all — effective-sections.ts:215,278, zero importers repo-wide, darkness pinned by __tests__/effective-sections.test.ts:380,388; no rows written either (effective-sections.ts:41-45)Not at all — same module, zero importersNot at all
(2) Property sectionsUp front, but only after hand-off — renderPropertyPolicies at src/app/api/voice/personalization/route.ts:1154, emitted as property_policies at :3121, consumed by leasing.ts:301 / leasing-many-homes.ts:669 / lease-and-billing.ts:138; triage.ts declares no {{property_policies}}Up front, every turn — conversation-manager.ts:7000,7193clara-unified.ts:1163, no hand-off gateIdentical to SMS — one builder, clara-unified.ts:805 called from conversation-manager.ts:9105
(2b) Property pricing / feesUp front, subset — paymentFees, rentersInsurance, customFees are voice:false (property-knowledge-block.ts)Up front, full set — clara-unified.ts:1191Identical to SMS
(3) Listing cardUp front, unconditional, rich — formatPortfolioHomes at leasing-context-injection.ts:573; parking :670, "Around the home" :691Mid-conversation only, thinner — gated by tour classification or a single named apartment (inbound-router.ts:918,1010,1039); no deposit, pets, amenities, parking, or drive times unless a tool is calledIdentical to SMS — same router branch (inbound-router.ts:856)
(3b) Scattered-homes enumerationorgHomesForLine shares one source with mid-call tools (org-offerable-homes.ts:403)Up front preload reads the anchor property's fetchLeasingAvailability, which has no unit rows — emptySame

Gap table

#What a prospect experiencesSmallest fixWhat it folds into
G1Retired sections leak via get_property_details (tools-leasing.ts:2182 returns raw sections, no retiredAt filter; up-front block filters at property-policies.ts:31)Return screenedSectionsForVoice(...) / a shared retiredAt filter; reuse src/lib/domain/escalation/voice-knowledge-gate.tsOne knowledge-block builder — the filter runs once for all three readers
G2Triage has no property-knowledge reader — triage.ts declares no {{property_policies}}Closed, won't do (Fede, 2026-09-15): the greeter works like prod and transfers leasing questions to the leasing Clara, who has the one block. Not a gap.
G3The per-home block (aroundTheHomeLines) is voice-only; no text module imports itHave the text prompt render the same per-home lines from the same producerOne knowledge-block builder
G4Listing card up front on voice, conditional on text (inbound-router.ts:1010)Populate the text preload from orgHomesForLine/listingCardFacts on any leasing turn with a propertyOne knowledge-block builder
G5Scattered-homes text preload reads the anchor property's roster, which has no unit rowsSame fix as G4 — route the preload through orgHomesForLineMigration PR 1 (Property per home)
G6Org tier reaches nothing; rows don't exist yetWire resolveEffectiveSections into the one knowledge-block builder, behind the same filtersMigration PR 3 (company sections move to org)
G7Voice pricing subset — paymentFees, rentersInsurance, customFees are voice:false by designFlip the three flags — its own voice behavior change, needs its own PR and a live-call proofIts own PR, outside this migration

Source: knowledge-matrix.md, code-grounded audit with file:line citations, 2026-09-14.

Camellia email inspection — what today's parser assumes, and where it breaks
StepWhat happensCode
ArrivesMicrosoft notifies us about the property's mailbox; the owning property is found by scanning all properties for the subscription id. A second door matches recipient address.webhook-processors.ts:419, match-property.ts:98
RecognisedA card needs an appfolio.com sender and a "CONTACT INFO" body; eight listing-site domains are handled separately.parse-lead-source.ts:837, :55
Parsed, routedName, email, phone, bedrooms, move-in, budget, comments, source, raw unit label, then the tour lane.parse-lead-source.ts:922, process-inbound-message.ts:1420
Person, homeThe save-prospect tool creates the prospect, a deterministic pass fills what it dropped, and the raw unit label matched to that property's unit rows — no match leaves it unset.apply-parsed-lead-facts.ts:277, :171
KnowledgeOne prompt builder for all channels, given the property's policies.clara-unified.ts:805, property-policies.ts:27
ReplyFresh message to the renter, not to the relay, through the property's own mailbox.decide-email-send-mode.ts:114, property-graph-sender.ts:279
DarkSuppressed by a per-property switch, the leasing stage, or the pipeline allowlist.process-email-record.ts:47, capability-stage.ts:20, operating-mode.ts:50

Honest finding. No Camellia address, org id or property id is hard-coded in the parse-and-reply path. The shortcuts are structural: it assumes one mailbox per property — the assumption a many-home company breaks.

ShortcutWhy it breaks Western SlopeReplacement
Inbound wired only to Property.emailIntegrationoutlook-subscription-renewer/handler.ts:113Their mailbox belongs to the company, not to one of 15 homes; nothing subscribes to it.Subscribe on the company hook, ORG#/ATTACH#mailbox
Company mailbox send-only — company-mailbox.ts:36We can write from leasing@ but not hear it.The lifecycle the per-property door has
Mailbox → property by scanning every propertyOne mailbox, many homes: no right answer.Gera's doorbell, ADDR#email#<mailbox>/HEAD → one company, one node
Home is a unit row — apply-parsed-lead-facts.ts:171Scattered homes have no unit rows; the home never resolves.The home's own Property, via org-offerable-homes.ts:240
Listing scraping one hard-coded row — listings-sync.ts:24Only JP&Co's listings page is in the array.Each company's listings source on its record
Rent-roll source a hard-coded id set — process-inbound-email.ts:78In the email entry file; the next customer needs a code edit.A property field

The design

Ingress. A Microsoft change-notification subscription on the company mailbox, polling, or the customer forwarding AppFolio's notifications to us. Recommend the subscription — webhook, renewal job and token vault all exist; only the target list is per-property. Forwarding is the fallback if consent is blocked.

Home resolution. The mailbox gives the company, through the doorbell row. The card gives the home, through its address or its "Interested in" line, matched against that company's offerable homes — the home's own Property, never a unit row. An unknown address fails closed and logs; we never invent a property.

Identity. One Person per company, found or created by the in-company email claim first, phone second — the resolver a phone call already uses.

Reply. Sent as the customer from their leasing mailbox, to the renter not the relay, threaded on our conversation. Dark means one thing: no proactive answer to a card from the customer's portfolio until Fede turns it on. Anyone writing to the mailbox directly still gets a full reply.

Migration plan — the PR chain and what moves where
#One concern, darkSizeThursday
1Doorbell row: mailbox address → company and node~200yes
2Subscription, renewal, webhook lookup for a company hook~250yes
3Home resolution from a card, fail-closed~250yes
4Email turn reads the one knowledge block~200no
5Reply from the company mailbox, threading, the dark rule~150no
6Replay harness leg, Fairhaven fixtures~250no

Thursday's floor is hearing, resolving and recording — PRs 1, 2, 3, dark. Separately, the fact-ownership migration:

Proves: the standard rollout playbook (dark, ≤300 lines, one concern per PR) already governs every PropFlow PR chain; registry writable-at levels for knowledge.<fact> and knowledge.section.<id>.

Where the home comes from, and how we test it
ChannelHow we learn the homeWhat Clara knows before her first reply
Email guest cardThe listing site names the property and usually the exact home in the card.Company policy, property facts and the listing card, loaded before she reads the message. Behaves like Camellia: the home is known up front.
Text from a listing siteThe tracking number or link carries the home; the first text usually names it.Same as email once the home is matched; company policy only until then.
Phone call to the company lineOnly the company is known. The home comes out of the conversation, by street address or by criteria.Company policy only, then property and home facts once the caller names or picks a home.

Bench proofs, all run on Fairhaven Residential (TEST), our own bench company, no real prospect involved: (1) a home answers with its own knowledge on a bench call; (2) an org-level fee answer inherits down with no per-home copy of it; (3) re-listing a home keeps its facts and costs zero re-enrichment spend. Each proof is an artifact — a call, a database read, a log line — not a claim.

Dry-run passes 2, 3, and 4 — full call-by-call detail

Same script run repeatedly on Fairhaven Residential (TEST): ask a policy question, a home-fact question, a listing-fact question, then try to book a tour that lands on the leasing agent's own calendar.

Pass 2 (2026-09-14)

Phone: pet policy answered correctly pre hand-off; the neighborhood/distance-from-downtown question about 875 Kestrel Bend was asked twice and Clara stalled both times instead of answering or saying she didn't have it; listing facts matched the card. Booking failed twice — home_not_resolved on the named address, then unit_not_available_for_tour on a substituted home — so no tour exists. Text and email: not wired yet (no Fairhaven scenario existed in either harness). Report: scripts/voice-harness/dry-run/results/voice-reports/many-homes-2026-09-14T23-39-26-418Z.json.

Pass 3 (2026-09-15)

Confirmed live that the org's "book on people's calendars" setting was already on — a prior note calling it off was stale. Made no difference: the tour never got that far. Phone: the caller asked the pet-policy question before naming a home and Clara deflected to "each individual property" instead of answering from company knowledge — the known gap where triage hands every such question to leasing. Home and listing facts both failed the same way: caller named 875 Kestrel Bend, the lookup tool returned nothing, and Clara told the caller "that one isn't coming up in our system," then failed a second general search too. No tour attempt fired at all — a step back from pass 2. Sentry stayed clean because the failures returned empty answers instead of throwing errors, so nothing paged. Report: scripts/voice-harness/dry-run/results/voice-reports/many-homes-2026-09-15T08-27-29-464Z.json.

Pass 4 (2026-09-15, overnight)

Phone. Call conv_2601m2j55m46ejyaadp0w7wtxwfg, goal score 7/10. Pet policy yes/no answered but breed/size/deposit specifics deflected to leasing rather than saying "not published" — a real remaining fail. Home fact and listing fact for 875 Kestrel Bend matched the card exactly. Tour booked for Oct 15, 1:00pm, guest card name "Devin Ashford" (caller said "Devon" — an ASR spelling miss, not a defect); tour row 6586d0f9-1f4e-4398-949a-9888bfaf7420 read directly from the database, then cleanly cancelled by the harness's own cleanup. No team email fired (none was composed for this call); confirmation text couldn't be checked because texting is shadowed on this bench config.

Text. Now wired for Fairhaven. 12 of 13 checks passed over 6 turns — pets, bedrooms, rent, availability, garage question all correct. The one "failure" was the check tool giving up 7 seconds before the tour actually landed in the database (8183ddb1-0ff3-402f-a5f3-1bf4a30409c3) — a timing bug in the test tool, not a booking failure. Report: ~/.claude/sms-stress/reports/SMS-1789463642436.md.

Email. Now wired for Fairhaven and the inbound message was accepted, but Clara never replied inside the 8-minute test window. This overlapped a live rate-limit error ("This request would exceed your account's rate limit") on the shared background AI pool that also degraded the text run's automated grading. Filed as a hypothesis (the capped pool starving the actual reply call), not a diagnosed root cause. Report: ~/.claude/email-stress/reports/STRESS-1789463635325.md.

Reschedule/cancel by phone. A booked tour was "moved" by a live call — Clara said "you're all set" and the reschedule tool fired — but the tour record shows no change at all: same date, same time, no history entry. The caller was told a change happened that did not happen; this is a real, unfixed defect (src/lib/tools/leasing.ts:704-786, documented as "cancels the old tour and creates a new one," neither occurred). A follow-up cancel call on the same booking worked correctly. Sentry stayed clean because the no-op didn't throw an error.

Calendar discrepancy, unresolved. Every tour above is bound to the same calendar-agent person, whose channel record resolves to a specific Outlook mailbox — confirmed live. Querying that exact mailbox directly for the tour's event, both by id and by date range, returned zero matching events both times. Either the calendar write isn't reaching the mailbox Microsoft's own lookup shows, or the two point at different underlying accounts despite matching addresses. Not diagnosed further this pass; candidate files: src/lib/domain/calendar/sync-tour.ts:733, src/lib/domain/calendar/outlook-client.ts:506.

Guest-card replay — the 40 real prospect emails, parser detail before and after

Separate from the scripted dry run above: every real AppFolio guest-card notification held in our own mailbox, replayed through the production parser and resolver with sending disabled — nothing sent, nothing written, no model called. Corpus: 40 real guest cards from one scattered-homes company, deduped by message id. Harness: scripts/email-stress/guest-card-replay/.

StepBeforeAfterWhat changed
Renter's name read23/40 (57%)40/40 (100%)The second-visit notification ("New Interest on an existing guest card") now reaches the parser.
Home the card is about23/4040/40Same cause.
Move-in date0/40 (present in 30 emails)30/30The parser now knows the section the relay prints it under.
Unit label32/40 set, 25 wrong8/40, none wrongA field label ("Move In Date: 09") is no longer mistaken for a unit number — a wrong unit was worse than none, since it drove a real lookup downstream.
Renter's own words, clean of noise23/40, all polluted with template text38/40, cleanThe relay's own boilerplate is no longer appended to the prospect's message as if they'd said it.
Matched to the right home7 correct, 5 wrong, 28 none33 correct, 5 wrong, 2 noneThe lane now reads the home the card names in its own banner before guessing from the subject line. 95% resolve, up from 30% for the company match and 18% for the correct home.
Company resolved12/40 (30%)38/40 (95%)Derived from the home match.
Renter questions we could answer27/40 (67%)32/40 (80%)Most of the remaining gap is phantom questions disappearing — a field the relay itself prints was being miscounted as something the renter asked.
Company policy in the reply0/400/40 — unchanged on purposePolicy lives on one company record today and nothing reads it per home; that's the target-shape migration above, not a parser fix.
Listing facts in the reply43% at best43% at best — unchangedA listing fact only exists while the ad does; matching the home correctly doesn't conjure facts nobody stored.

Still wrong: 5 cards match the wrong home when two roster rows tie on the same street name; 2 name a home with no record at all; and this measurement covers only one of our two inbound email lanes.

Proves: harness PRs #8588, #8594, #8595, #8613; fixes #8603, #8604, #8607, #8610 — each number is printed by scripts/email-stress/guest-card-replay/run.ts over the real corpus, and each defect is pinned as a test that fails on the fix (passes on today's wrong behavior) so it turns red first.

Compose stage — 5-card graded proof

PR #8622 (merged) adds the actual reply-composing stage, through the exact functions the live email lane calls, pointed at our subscription bench credential rather than the metered production key. Two review rounds found and fixed real gaps before merge: read-only tool calls were silently refusing without a live conversation id, and each tool call was writing an audit row into the production table even though this stage claims to be read-only. The shared pool this stage's model calls ride was capped at merge time (same constraint as the email dry-run above); it reset and the 5-card proof ran clean below.

Fairhaven Residential (TEST) is a 14-home scattered-site portfolio, not one building: 7 addresses in Fairhaven, CO (80402/80403), 7 in Westmarch, CO (80517/80518) — a Westmarch address on a card is a real home in this portfolio, not a data error.

CardHome namedFacts rightPolicy lineNo invented factsNo apartment-complex wordingNo staff name
Jordan Q. — new lead118 Quillwood Lanepass — 3bd/2ba, 1480 sqft, $2,150, nowpass — $300/pet fee, 2 pets, from the Pets policy sectionpasspasspass
Jordan Q. — return visit, asked about utilities + a tour1265 Sparrowgate Roadpartial — beds/baths/sqft/pet policy right, rent never stated, utilities question dropped silently instead of answered or deferredfail — no policy line in this replypasspasspass
Alex Rivera — no home namednone (whole-homes fallback)pass — rent/sqft range correctly bucketed from live inventory; omits one qualifying home (avail. 12/1) without saying more existpass — office hourspasspasspass
Jayden Hust — asked a 1BR move-in date146 Quillwood Lane (an actual 2BR)correct that no 1BR exists anywhere in the portfolio; never mentions 146 Quillwood is a real, available 2BR — confusing rather than wrongfail — no policy linepasspasspass
Seth Perry — mentioned a pet, asked for an application1704 Emberly Waypass — 2bd/1ba, 900 sqft, $1,450, cats-only, avail. 10/1fail — doesn't state the pet fee or address the "Pet Type: Other" ambiguitypasspasspass

Net: 5/5 correctly named and priced the right home with no invented facts, no apartment-complex language, and no staff name — the harness's own safety bar. 2/5 dropped the org policy line; 2/5 left a piece of the renter's actual question unanswered instead of naming the gap. No card in this sample presented a policy conflict, so the property-wins-on-conflict rule is still unexercised here. Full replies and file:line for each gap: PR #8622's Proof section.

Full fact inventory — today's location vs. target owner, file:line
FactToday (key path)Target ownerWho writes itHow often
Neighborhood summaryORG#/LISTING#<id> listing.neighborhood.summary[]PROP#<home>/KNOWLEDGE knowledge.<fact>scripts/enrich-listing-neighborhoods.ts + cron drainonce per new address, re-run only on address change
SchoolsORG#/LISTING#<id> listing.neighborhood.schoolsPROP#<home>/KNOWLEDGE knowledge.<fact>same enrichment pathsame
Geo (lat/lng)ORG#/LISTING#<id> geo{lat,lng}PROP#<home>/KNOWLEDGE knowledge.<fact>same enrichment pathsame
Drive timesORG#/LISTING#<id> driveMinutesTo{<addressKey>: minutes}PROP#<home>/KNOWLEDGE knowledge.<fact>same enrichment pathsame
Parkingparsed live, on the call, from the listing descriptionPROP#<home>/KNOWLEDGE knowledge.section.<id>enrichment path (write-once, migrated off live parse)currently on every call; target: once, on address change
Company sections (contact, screening, fees, pets, snow, line limits)anchor PROP#western-slope-front-door/KNOWLEDGE knowledge.section.<id>ORG#<org>/KNOWLEDGE knowledge.section.<id>manual authoring, onboardingon policy change
Tour arrival (tour-arrival, tour-arrival-shared-building)anchor property, matched by shared-building address textPROP#<home>/KNOWLEDGE, matched by property idmanual authoring / queuedon tour-arrival policy change

Sources: registry keys.ts (knowledge block), src/lib/data/store.ts getPropertyKnowledge (~line 4080), src/lib/domain/leasing/tour-arrival-info.ts, scripts/enrich-listing-neighborhoods.ts, src/app/api/cron/neighborhood-enrichment-drain, and the Western Slope go-live tracker's anchor-property row.

A5. Basic prospect dry run, 2026-09-15 evening
ChannelStepResult
Email1. AvailabilityPartial fail — reply composed ("118 Quillwood Lane is available now... rent runs $2,150/mo... Thursday 1, 2, or 3 PM") but bounced, error 550 5.7.708. Gmail message id 1a0a6b8227d8981d; conversation PROP#fairhaven-homes/CONV#d585bafa.
Email2-5Blocked — never sent, same sending restriction.
Text1. AvailabilityPass — "118 Quillwood Lane is available now! 3bd/2ba, 1,480 sqft... $2,150/mo." Conversation 78ae3ee7-3c42-4987-9cb6-d33a9aaa7369.
Text2. PetsPass — property's $300 per pet rule beat the company's no-pets rule.
Text3. InsurancePass — $100,000 liability, correctly answered from the company-only rule.
Text4. BookBooked Thursday, September 17 at 2:00 PM. Tour TOUR#78ab76e2-c5ac-4f85-9c18-14893a8ebc8e.
Text5. Move and cancelMoved to Friday 2:00 PM, then cancelled. Clean history, no leftovers.
Phone1. AvailabilityPartial — the real unit and price were quoted correctly, but the call never played out the full scripted scenario. ElevenLabs conversation conv_0601m2kasavgeq7.
Phone2-3. Pets, insuranceNot run — no bench script covers these by voice yet.
Phone4. BookBooked 3:15 PM at 402 Thistledown Court, tour 146d47d5. Quality issues noted above (budget asked twice, stacked questions, stated home count, implied recognizing a first-time caller).
Phone5. Move and cancelReschedule landed on Wednesday instead of the requested day, and the confirmation sentence cut off mid-way. Cancel was clean and the calendar event was removed.
TextTenant recognitionPass — recognized Terry Testwell, a seeded test tenant at 146 Quillwood Lane. Conversation ecedc003. A rent question was correctly forwarded to the property manager rather than answered.
PhoneTenant recognitionNot attempted — needs a caller phone number set up first.
PropFlow Docs