A plain-English map of everything the system does, from a message arriving to a reply going out, and everything that keeps it honest along the way. Written for understanding the shape of the system, not for building against it.
Three doors in, one Clara behind all of them
Residents and prospects reach Clara three ways, and each has its own front door:
Whichever door a message comes through, it ends up in the same place: one conversation, one person, one property, described next.
Not every email should go to Clara. This layer decides.
Email is the noisiest channel — team CCs, spam, automated notices, forwarded threads — so before anything reaches Clara, a decision layer classifies the email (lease question, maintenance, tour request, spam, needs a human, etc.) and decides one of: answer it, extract useful information from it silently, or set it aside.
Two protections sit on top of that classifier:
Every decision this layer makes is written to a durable row — what came in, what was decided, and why — so the whole history is queryable, not just observable in logs that roll off.
Every person at a property gets exactly one conversation, no matter how they reach out
Once a message is headed to Clara, it lands in that person's one conversation at that property — reused for life, across email, text, and voice. This matters: a manager who answers a resident's escalation over the phone and a resident who texts a follow-up an hour later are talking about the same thread, not two.
From there Clara runs a turn: she reads the conversation history plus the property's knowledge, decides what to say and what tools to call (schedule a tour, forward to the property team, create a work order, update a renewal), and composes a reply. The tools she has access to depend on who she's talking to and what stage the conversation is in — a prospect gets leasing tools, a current resident gets maintenance and renewal tools, a property manager on a turnover gets an entirely different toolset.
The last checks before anything reaches a real person
Because a wrong number on a lease renewal is a legal problem, not just an annoyance, every reply passes through checks before it's sent:
Every time a safety net blocks a reply, the reply that was withheld is preserved internally with a marker naming which guard fired — nothing is silently lost, and the record answers "what would Clara have said, and why didn't she?"
Clara doesn't guess outside her lane. She asks, and the answer sticks.
When Clara can't or shouldn't answer alone, she forwards the conversation to the property's team by email, flips the conversation's status to "escalated," and — if the property has named an escalation owner — opens a tracked question: a specific, single question sent to that person, chased with reminders until it's answered (three reminders today, matching every other reminder ladder in the system, per today's ruling).
This system already exists in the product but is dormant almost everywhere — it only activates for a property once someone sets that property's escalation-owner address. Turning it on for a property changes nothing else: the existing team-inbox forward, the status flip, and the general reminder cadence all keep working exactly as before: this is additive, not a replacement.
Ruled, build pending (Decisions 1 and 7, today): today, once a team member answers a tracked question, Clara relays their reply close to word-for-word — which risks leaking internal asides ("she's been late twice, don't budge"). The fix: Clara treats the answer as policy and re-answers the resident's original question in her own words, never narrating "the team said." And when the answer is a concession or a non-standard term (a discount, a special lease length), Clara will explicitly ask the decider whether this is a one-off exception or the property's policy going forward — so a special-circumstance decision never silently becomes a blanket rule for everyone else.
Facts live in DynamoDB, organized by property, and feed straight into what Clara says
Every property has a knowledge record in the database — lease terms, pricing, pet policy, parking, amenities, and so on — assembled at onboarding from the lease and the property's public listing. That knowledge is rendered into Clara's prompt every turn, so she answers from what's actually on file rather than guessing.
On top of the onboarding facts, there's a teaching loop: when a property team member answers something Clara didn't know, that answer gets folded into the property's knowledge so the same question is never asked twice. Ruled, build pending (Decision 8, today): today every team answer gets saved as if it were a general rule — including one-time, person-specific facts ("this phone number left apartment 608," "this specific tenant can renew at $1,100"), which pollutes the knowledge base with advice that's wrong for the next person. The fix: classify each answer as a reusable rule (save as property policy) or a one-off fact (attach it to that specific person/matter only) before saving.
Voice calls read from a cached, pre-rendered version of the same knowledge (kept in sync whenever the underlying knowledge changes) so a live phone call isn't waiting on a database read mid-conversation.
The pieces you'd never see, but that keep everything moving on time
Nothing ships on a hunch; changes are proven against real history before they go live
flowchart TD
A["Resident / prospect / team member"] -->|email| E["Email front door
classify + decide"]
A -->|text| L["Agent loop
one thread per person"]
A -->|call| V["Voice (ElevenLabs)"]
E -->|"answer it"| L
E -->|"quietly note it, no reply"| Q["Silent — audit row only"]
E -->|"active thread override"| L
V --> L
L --> S["Safety nets
hallucination guard · policy gate · runaway brake"]
S -->|"passes"| OUT["Reply sent"]
S -->|"blocked"| H["Escalate to the property team
+ tracked question, reminders"]
L <--> K["Property knowledge
(DynamoDB)"]
H -->|"team answers"| K
H --> OUT2["Answer relayed to resident
(build pending: Clara's own words)"]