Video Clara coworker

Video is Clara's fourth way in, next to phone, text and email. Same Clara, same tools, same data.

Proposed — pending review  Architecture added 2026-10-01 night; voice-agent framing added 2026-10-05. Not turned on for any customer.

What it is

Staff talk to Clara on video, either from our website or by inviting her into their own Zoom, Meet or Teams meeting. In a meeting she mostly listens. After the meeting, what the team said about each tenant goes to the renewals engine as notes for staff to approve. The near goal: Clara sits in on a client's renewals meeting next week.

WHO REACHES HER CARRIERS STAYS ON PROPFLOW Phone Text Email Video, our website Video, their meeting ElevenLabs Twilio Mail providers Tavus face, voice, call we upload nothing Front door company and person decided once One Clara engine Same instructions plus a spoken style Same capabilities by who and which way Same tools reads now, actions later Staff desk same as staff texts PropFlow data one company only AppFolio through our adapter Renewals engine meeting notes in
Tavus is the video equivalent of Twilio: it carries the call and renders the face. We upload nothing to Tavus. It hears what Clara says on the call; recording and memory are off, and each call is deleted afterwards. Phone is today's one exception to "one engine": ElevenLabs runs its own model and calls our tools.

Recommended: video is a fourth channel into Clara's one engine. It isn't free: that engine waits for the whole answer before speaking and loads a huge prompt for staff. Five changes fix that; dev measurements (Sonnet 4.6, two runs each) put her first word at 0.7–2.9 s.

Group meetings: the interruption problem (Oct 7)

Proposed — pending review. Fede, 2026-10-07, after the 3:55 PM client meeting: "Clara just couldn't talk at all… I don't think the current design is going to work for a meeting with a lot of interruptions."

What happened. One-on-one Zooms worked. The group meeting did not: Tavus heard the whole room as one speaker and never found a gap.

Zoom test todayLengthSpeaker turnsClara turns
2:08 PM, one person6 min2019
2:16 PM, one person9 min2420
3:55 PM, client group, people talking over each other8 min11, cut off after "Hey, I'm"

Why. Three things stack up. The live persona is set to high interruptibility, so any sound stops her. The wake phrase the design called for was never set on the persona, so she tried to answer everything. And Tavus gives us no per-speaker audio in a meeting, so our engine cannot tell who is talking or whether it was addressed; it only sees one merged "user" turn. (Sources: persona p3f51613c91e read 2026-10-07; Tavus conversational-flow and meetings docs.)

What Tavus offers. Interruptibility low / medium / high ("low: keeps talking through minor interruptions"), turn-taking patience, the sparrow-2 turn model ("fewer interruptions in noisy settings"), and wake / sleep phrases. There is no "never interrupt" or "finish the sentence" mode, and nothing documented about several speakers.

OptionWhat changesCostKeeps the face?Bet
A. Tune Tavus and retest (recommended first, today)Interruptibility low, patience high, wake phrase "Hey Clara" / sleep "Thanks Clara", prompt: finish the answer, keep it to two sentences. Retest in a 3-person Zoom with deliberate crosstalk.1 hourYesMight be enough for "mostly listens, answers when asked". Will not fix "who is talking".
B. Different carrier for meetings (if A fails)A meeting bot that hands us each speaker's audio (Recall.ai, or Zoom's own real-time media stream). Our engine decides when to talk, holds the floor until the answer is done, speaks through the bot. The website one-on-one call stays on Tavus.1–2 weeksNo face in meetings (voice only), unless Recall's GPU output video proves outSolves crosstalk and "was I addressed" for real. Recall is about $0.50 an hour plus $1 an hour for audio out.
C. Answer in text in group meetings (cheap floor, pairs with A or B)Clara listens, posts answers to a live notes page or the Zoom chat, and speaks only when addressed by name. Nobody gets talked over.2–3 daysYesMatches the original "listener" design and the renewals-notes goal.

Applied 2026-10-07 18:35 MT on Fede's go ("go with low", "go with all the best for group settings"): live persona p3f51613c91e now has interruptibility low (was high) and turn-taking patience high (was medium). Wake phrase not set yet: the persona is shared with the website one-on-one call, so a wake phrase there would silence Clara until "Hey Clara" on every call. Next retest: a 3-person Zoom with crosstalk.

Recommendation. Do A today as the go/no-go, with the retest recorded here. If she still cannot finish a sentence in a 3-person crosstalk test, go to B for meetings and keep Tavus for the website call. Build C either way: it is the shape that works in a room full of people.

Other bugs from today's practice calls (2:08 and 2:16 PM one-on-one Zooms, from the transcripts):

  • Refused to add up delinquent balances until pushed ("Are you sure you can't just add them up?").
  • Described the person on camera wrongly ("a woman with brown hair"), then read a name tile as "Cera Garza". Vision is on in the persona although the design says off for client meetings.
  • Reported a black screen with a Zoom notice until the testers rejoined; our code sets nothing about vision, so this came from the Zoom side.
  • Could not say whether a tour was self-guided, where work orders came from, or collections status.
  • No way to remove her from the meeting from the website. Fixed and live (#17579).
Can she join a client call next week?

Possible, but tight. A staff-only meeting where Clara listens and answers basic questions needs 6 of the 9 engine PRs, most of a seventh, and the meeting join itself, all proven at Fairhaven first.

StepNeeded for the demo?
1. Staff questions stop loading the leasing promptYes, for speed
2. Staff write-list consistency cleanupCan wait (no exposure)
3. Video as a channelYes
4. Staff person from the call bindingYes
5. Portfolio reads as one capabilityYes
6. Streaming answers from the engineYes
7. The answer line Tavus calls, with its read-only gateYes
8. Session-start snapshot and warm-upMostly: the snapshot yes, the AppFolio part can wait
9. End-of-call cleanupCan wait
Meeting join: link, wake phrase, 15-minute rejoinYes. It's on the critical path, outside the 9, and untested on a real Zoom

Fallback if it slips: Clara joins and only listens. She answers "I'll follow up after the meeting," and the transcript still feeds renewal notes. That needs just the binding, the meeting join and the transcript callback. Proposal.

v1 meetings are the client's own staff only. Tavus docs don't say what attendee or invite details Clara sees, so she can't detect outsiders yet.

Clara in Zoom: scope (Oct 3)

Program plan: the Western Slope pilot (notes → brain → renewals) lives on the company brain page.

Proposed — pending review. Fede asked 2026-10-03: "can i test on zoom?" → "scope it".

How she joins: Tavus can send Clara into a Zoom, Google Meet or Teams link. We pass the link as meeting_url when we create the call, and her face and voice join as a normal attendee. No Zoom app review is needed for that path. (Tavus API, Tavus FAQ)

Already built for the website call and reused: creating the Tavus call, the company binding, Clara's answer line, the company snapshot, the end-of-call callback, Sonnet 5.5, and the reply timer.

StepWhatSize
0. Test call (gate)One 15-minute call into our own Zoom with the made-up "Pinecrest" company. Needs a Tavus conferencing address set up on the PAL. Answers the five unknowns below.1 hour, Fede's go
1. Invite box"Invite Clara to a meeting" field on the video page: paste a Zoom link. Creates the meeting session, owned by the signed-in person's company.1 small PR
2. Join and listenCreate the Tavus call with meeting_url. Clara speaks only when addressed ("Hey Clara").1 small PR
3. RejoinOnly if the test shows the 15-minute cap applies in meetings.1 small PR
4. Renewal notesAfter the meeting, the transcript becomes renewal notes per tenant.1 small PR

Five unknowns the Tavus docs don't answer (all say "not stated"):

  1. Does our own answer line (Sonnet 5.5, our data) still drive Clara inside a Zoom? Blocker if no: Fede ruled no Tavus-hosted brain with customer data.
  2. Does she get each speaker's name?
  3. Is the call still capped at 15 minutes on our plan?
  4. Do the wake and sleep phrases work in a meeting?
  5. Do waiting rooms or passcodes stop her? Zoom has required approved tokens for bots in other companies' meetings since March 2, 2026 (Zoom); whether that touches Tavus's attendee is untested.

If unknown 1 fails: a meeting-bot service carries the audio and our own engine answers. The candidates are Recall.ai (about $0.50 an hour; pricing) and the open-source Attendee (GitHub). That path is bigger, and Clara probably has no face in the meeting.

How video plugs in

Video is a fourth channel into Clara's one engine, the same loop that answers texts and emails. Each spoken turn enters the engine directly, the way staff clarifying questions already do. The speaker is the signed-in staff person from the call binding, not a phone number. Ask Clara's 23 portfolio reads become one capability of that engine, STAFF_PORTFOLIO. In v1 a video turn can only read: a new gate allows read tools only. AppFolio data goes into a snapshot built when the call starts, under the per-account request budget, never as a live AppFolio call on each question.

The honest cost. Fede's direction is right, but it isn't "nothing to rebuild." Today the engine doesn't stream, a staff question loads about 57k tokens of leasing prompt, and there's a long setup before the model is called. Five changes make it fast enough to talk to:

  1. Stream the answer as it's written.
  2. Staff questions stop loading the prospect leasing prompt.
  3. Build a snapshot and warm the cache when the call starts.
  4. Use the voice model tier.
  5. Cap each turn at 2 lookups with a deadline of about 6 s.

A sixth change, skipping the resident and prospect setup steps, is needed only if a bench trace shows that setup is slow.

Dev measurements, first spoken wordAnswer already in snapshotAfter one lookup
Sonnet 4.6 (voice tier)0.7–0.8 s2.2–2.9 s
Haiku 4.50.4–0.9 s, but still reached for a lookup1.3–1.7 s, but picked tools worse
Sonnet 5 (email and Ask Clara tier)1.2–1.8 s4.6–5.1 s, too slow

Dev measurements: Fairhaven test company, N=2 per cell, warm cache, with the changes above in place. Lookups on a one-property dev company took under 60 ms, so a Western Slope-sized company will be slower. Without the changes, the estimate is 4–6 s to the first word on a question that needs a lookup (inference).

Engineering detail

Seam choice (engine-seam findings, origin/main a3a839f1a0):

A. One agent loopB. Ask Clara loopC. Tavus runs the model, calls our tools
Streams todayNo: handleIncomingMessage returns Promise<string>; every call is messages.createYesYes (vendor)
Staff tools and write gateYes: desk, metrics, mass comms, stripStaffTurnWriteToolsNo: 23 reads, its own promptRedeclared to Tavus
ActorPhone or email today; needs a person keySigned-in userPhone
VerdictChosenSecond brainSecond prompt family; staff data to the vendor's model
  • Entry: a per-turn ChannelAdapter, not SessionChannelAdapter, because we run the model on every turn. parseInbound checks the bearer and conversation-id, loads the #15301 binding (fail closed), takes only the last user message, and emits channel:'video', carrier:'tavus', sender video:<tavusId>. It calls handleIncomingMessage directly, like clarifyForStaff (reply-release.ts:2233), with senderRole:'pm', staffTiers, pmAuthorId, lineOrganizationId, onTextDelta, abortSignal and readOnly. Delivery is the response stream; sendOutbound skips. A new turn aborts the one in flight. Dedupe on (conversation id, message count, last-text hash).
  • v1 read-only gate: the existing staff write list (stripStaffTurnWriteTools) does not make a video turn read-only. It only guards staff questions inside a resident's escalation thread. A video staff turn carries staffTiers, so it gets the desk write tools. v1 adds its own gate for channel:'video': an allowlist of read tools, checked at composition and again at dispatch, with a mutation control.
  • Actor: new resolveStaffActorByPerson(personId, orgId), because resolveActor only takes a phone or email. Refuse the turn if the person holds no active staff role in that org.
  • STAFF_PORTFOLIO: dashboardChatTools executed by the existing handleDashboardChatTool, with ctx from a new toolContextForStaffActor. For an SMS staff sender with no login, a reviewer must check canViewProperty, which relies on user.
  • Streaming (reopened 2026-10-02; corrected: still needs rulings on the typed-tool-call, first-turn leasing and availability-claim retries, plus the strip/PII steps, and a streaming tracedClaudeCall): the whole-reply steps are gated off for senderRole:'pm' (weekday guard, hallucination swap, anomaly gate, same-turn escalation hold reason:'pm_sender', policy gate, fee guard, turnover guards); outbound safety runs but all three policies skip for audience:'pm'. Still running: strips and PII (per sentence safe; the internal-prompt strip is per paragraph) and draft salvage (needs final + pre-tool draft, so video releases text only after the last tool round). The handler today splits a finished answer with deltasOf; the engine must emit text after tool completion via onTextDelta. Wasted pre-model reads for staff: getToursByPersonId(staffPersonId) at conversation-manager.ts ~10445 and getOrgOperatingModel('') at ~10859.
  • Model tier: resolveDraftParams(channel) gives email and SMS Sonnet 5. Video takes the voice branch (AGENT_MODEL, Sonnet 4.6, effort low).
  • Storage: one conversation row per call at company level (turnScope:'pm_query'), not merged into the person's texting thread. Our rows are the transcript.
  • Channel: add 'video' and 'tavus' plus VIDEO_CAPABILITIES (spoken, sync); widen 19 files. Delete the dead 'dashboard' member (envelope.ts:43). Its adapter ran through this same loop until #3994.
  • Lane 10 fit: intake adapter → core loop → delivery stream, with no side effects in the adapter.
  • Open checks: whether Tavus's ~10 s abort counts keepalive bytes; the staff setup time on a real bench trace; cold start of a Vercel route importing the loop (3.7–12.3 s under tsx, not a real cold start); proporg-index missing on dev, which breaks the tours board.

What the answer line must handle (seen in the local proof's traffic):

SeenHandling
conversation-id header on every turn (72 of 72). Not in Tavus docs, so it could changeNo id or unknown id means refuse
A setup-change test call with id tavus-openai-compat-testShort canned answer with no data; never a 401
Tavus adds 3 system messages per turnIgnore them; our engine builds the prompt
A perception block <user_audio_analysis> inside the user messageStrip it
Speaker prefix such as [fede]Keep it as the speaker name for meeting notes; never use it for identity
Speculative starts; read timeout 30 s (10 s on an older client)Stop when Tavus drops the request; keepalive every 2–3 s
A/B: Tavus-hosted brain vs ours (Oct 5)

Proposed — pending review. Fede asked for a version that runs entirely on Tavus (their recommended model, tavus-gemma-4, plus their knowledge base), to see if our own brain is the ceiling. Same face, same voice (Belle). The Tavus version reads a made-up company; ours reads Western Slope. Measured live in Tavus's own room: time from question to her first word (typed question, so microphone pickup isn't included for either).

Tavus-hostedOurs
First word, median1.1 s4.5 s
First word, slowest (p90)1.2 s5.9 s
Answer length9 words18 words
Correct on fact questions1 of 6 (counts wrong: said 8 when 16, said 12 when 4; named a tenant and a "leaking pipe at 1242 Maple Street" that don't exist)December count right (8); others look right, not yet checked line by line
Didn't make up what it lacked (prices, vacancies)Yes, 2 of 2n/a (has live data)
Questions run105

What it shows: the speed gap is the brain, not Tavus. Tavus's own model starts talking in about 1 second. But its knowledge base looks up passages; it can't count or list, so it guesses, and it guessed wrong on 5 of 6 facts. It also has none of our live lookups, note-taking or privacy checks.

Found while testing: Tavus's fastest lookup mode ("speed") makes the hosted brain go silent; "balanced" works. That goes on the Tavus question list.

OptionSpeedCorrect
A. Keep ours, keep cutting time4.5 s todayYes
B. Tavus brain talks, asks our server for every number (one tool call)~1 s for chat, slower for factsYes, if it always uses the toolRecommended next test
C. Tavus brain only1.1 sNo (5 of 6 facts wrong)

Built and off everywhere: company setting clara.videoBrain (default ours). Tavus-hosted test persona p2c6a736c5c4, made-up data only.

Data access audit: what video Clara can see (Oct 6)

Proposed — pending review. Fede asked Oct 6: "an audit ... with the tools it has access to and what's missing. I want the entire PropFlow platform included ... Long term I'd like it to be able to access every single module's data and all entities." Read from the code on main, Oct 6.

  Call start ──► summary of "what's happening now" (built once, free on every turn)
  Each turn ───► 22 lookup tools (read-only) + live AppFolio reports
                 each lookup adds ~2–6 s to that answer
AreaRecord typesCoveredPartlyCan't see
Leasing8143
Residents12336
Money11172 (+1 unverified)
Maintenance9243
Communications10028
Properties and company7205
Integrations and analytics8503
Total65142030

What it shows: every existing Ask Clara lookup is already on video, so new reach means new lookups, not switching old ones on. Biggest blind spots: the knowledge base (pets, parking, policies), households, renewal offer details, payments received, the calendar and waitlist, and inspections. She can already pull live AppFolio reports (delinquency with late counts, move-out reasons, ledgers), but slowly and only for AppFolio companies.

Constraint: her call-start summary takes 19–31 s to build against a 30 s limit, so most new data has to be lookups, not more summary.

Decided Oct 6 (Fede): narrower than the list below. Build: move-out reason, who's behind on rent, lease terms. Then all the basics for leasing (prospects, tours, application status), maintenance (how many open, any ticket's details) and renewals: counts in her call-start summary, details through the lookups we already have. Not now: turnovers, building knowledge, the rest of the platform.

Where to expand first

#AddHowWhy
1Move-out reasonKeep the reason from the AppFolio notice email (today it's read and thrown away); show it next to each noticeFede asked in the meeting; "why are people leaving"
2Who's behind on rentA past-due lookup (names and how late, from the balance history we already store)Most common money question
3Rent and lease terms per tenantNothing to build: a prompt line telling her to use the lease lookups she already hasCheapest win
4Turnover pipelineUnit, stage and ready date in the summaryThe follow-up to every move-out question
5Building knowledge (pets, parking, hours, policies)A knowledge-base lookupShe can't answer policy questions at all
6Household membersA household lookup (no phone or email)Notices and renewals often name co-tenants
7Renewal offer detail and change requestsA lookup on the renewal reviews"What did we offer, what did they ask for"
8Collections and payments receivedLookups on collection reviews and payment historyFollows #2
9Vacant and notice-given unitsA tiny summary block (units and ready dates only)Leasing and move-out planning
10Calendar, waitlist, held emails, escalationsRead lookups per moduleCompletes leasing and inbox

Long-term shape (toward "every module"): a thin summary of what's happening now, plus one read-only lookup per module, each checked as read-only before it's allowed on video. Kept out on purpose: login credentials, phone-line setup, staff accounts and sync internals.

Option
A. Build #1–#5 now, then one lookup per module in the order aboveRecommended
B. Build only #1–#3 (the meeting gaps) and stop
C. Build a single "read any record" lookup across all modulesFastest coverage; harder to keep read-only and quick
Full inventory: all 65 record types

Leasing

EntitySnapshotToolGapDifficulty
Prospects, applications (stage)yes (active, names, stage, source, move-in)list_prospects, get_prospect, find_guest_cardnone; closed/lost prospects only by tool; no phone/email by designeasy
Guest cards / inquiriesnofind_guest_card (one at a time)partial: no list, no inquiry historymedium
Toursyes (+/-14 d)list_tours, check_tour_status, get_tour_notespartial: tour outcomes/no-show history only per toureasy
Tour availability / calendar slotsnononenone at all (calendar module, tour-day policies)medium
Waitlistnononenone at allmedium
Listings, public feeds, unit prices, concessionsno (by design)list_units (rent), get_market_contextpartial: no listing text, price history, concessionsmedium
Leasing settings (sources, hours, intake mode)nononenone at allmedium
Leasing conversationsnolist_conversations, get_conversationpartial: fine via tooleasy

Residents

EntitySnapshotToolGapDifficulty
Tenants / occupancypartial (only names in lease-end and move-out lines)list_tenantspartial: no roster, no household memberseasy
Households, people, rolesnononenone at all (person spine, household, merge audit)medium
Leasespartial (end date only)list_leases, list_tenantspartial: no rent/term in snapshot (by design)easy
Lease end counts, month-to-monthyeslist_leases, list_renewalsnoneeasy
Renewals (state)yes (90 d + counts of all rows)list_renewalsnone; offer terms only by tooleasy
Renewal offers, change reviews, packages (new-lease)nononenone at all (renewal/new-lease change reviews, package reviews)medium
Notices / NTV (date)yeslist_tenants, appfolio_report (tenant_tickler)none for dateseasy
Move-out REASONnoappfolio_report tenant_tickler only (live, AppFolio only)not stored in PropFlow: NTV email parser reads "Move Out Reason" and the ingester does not keep it; AppFolio sync maps it only for an admin preview. Snapshot says "our records hold no reason for leaving"hard to store; easy-ish live via appfolio_report
Move-outs (completed) / turnoverspartial (notices only)list_turnoverspartial: no list of units turning over in snapshoteasy
Condition reports (move-in/out)nononenone at allmedium
Key pickup bookingsnononenone at all (NV)medium
Tenant confirmationsnononenone at allmedium

Money (Collections / Financials)

EntitySnapshotToolGapDifficulty
Balancesno (by design)list_tenants, get_tenant_balance_historypartial: aged buckets and last payment exist per snapshoteasy
Late-payment flagnolist_tenants with name (balance over 30 days, last payment); appfolio_report delinquency (late count, plan, collections)no stored "paid late" flag or late-count history in PropFlow; only aged snapshotshard to store; workable live
Delinquency / collections listnoappfolio_report delinquencypartial: AppFolio companies only, slow (live paged read)easy (live) / medium (stored)
Collections reviews (dun), cash-assistance inquiriesnononenone at allmedium
Payments received (monthly receipts, tender mix)nononenone at all (payment history stored)medium
Charges, tenant ledgernoappfolio_report tenant_ledgerpartial: live onlyeasy (live)
Financial periods, NOInolist_financial_periodsnone (tool)easy
Unit revenue periods, rent snapshots (loss-to-lease)noget_portfolio_insights (indirect)partialmedium
BudgetsNVnoneNV: no budget type seen in the type listhard / NV
Bills (payables), GLnoappfolio_report bills, general_ledger, income_statementpartial: live onlyeasy (live)
Purchase orders, vendor quotes/invoicesnoget_work_order (quotes)partial: no PO listmedium

Maintenance

EntitySnapshotToolGapDifficulty
Work orders (open, recent closed)yeslist_work_orders, get_work_ordernoneeasy
Work order notes, photos, timeline, line itemsnoget_work_orderpartial: one at a timeeasy
Vendorspartial (vendor name per work order)list_vendorsnone for roster; no vendor performance, preferred listsmedium
Vendor calls, scheduled visits, contact pointsnononenone at allmedium
Inspections / condition reportsnononenone at allmedium
Turnovers (tasks, charges, timeline)nolist_turnoverspartial: task detail missingmedium
Appliancesnolist_appliances (one unit)partial: no building-wide vieweasy
Maintenance manuals / routine tasksnolist_maintenance_manualsnoneeasy
Maintenance settingsnononenone at allmedium

Communications

EntitySnapshotToolGapDifficulty
Conversations, messagesnolist_conversations, get_conversationpartial: tenant-by-person lookup not offeredeasy
Held emails / review queuenononenone at all (review page)medium
Email ingestion recordsnononenone at allmedium
Calls (recordings, analysis)noget_conversation (transcript)partial: call analysis NVmedium
Notes (staff / unknown-caller notes)noget_tour_notes onlynone for general notesmedium
Knowledge base (property knowledge)nononenone at all (getPropertyKnowledge exists)medium
Playbooksnononenone at allmedium
Mass sends / drafts and outcomesnononenone at allmedium
SMS status, consents, suppressionnononenone at all (compliance lookups)medium
Escalations (PM escalation actions), activity lognononenone at allmedium

Properties and company

EntitySnapshotToolGapDifficulty
Propertiespartial (names only)list_propertiesnoneeasy
Unitsno (by design)list_unitsnoneeasy
Property knowledge, preferred vendors, settingsnononenone at allmedium
Organization, settings, enabled modulesnononenone at allmedium
Users / staff / rolesnononenone at allmedium
Onboarding record, go-live statenononenone at allmedium
Portfolio registry (phone lines, mailboxes, calendars, PMS credentials, settings, vendor rosters, schedules by org/group/property)nononenone at allmedium (and sensitive: credentials must stay out)

Integrations, analytics

EntitySnapshotToolGapDifficulty
AppFolio sync health, sync logsnononenone at allmedium
AppFolio live reports (15 endpoints)noappfolio_reportcovered, sloweasy
Portfolio insightsnoget_portfolio_insightsnoneeasy
Conversation themesnoget_conversation_themesnoneeasy
Metrics (catalog, history)noget_property_metrics, get_portfolio_statsnoneeasy
Market datanoget_market_contextnoneeasy
Agent traces, grading, evals, cost, spendnononenone at all (internal, low value for a PM)medium
Reports / report datanononenone at allmedium
Bench: hybrid vs ours (Oct 6, 3:30 AM MT)

Decided Oct 6 (Fede, after a live meeting test): "let's make this the permanent version and remove the feature flag and the old version... this becomes a long-term architecture." Our own brain is the only video brain. The hybrid and Tavus-hosted versions, and the company switch between them, are being deleted. In that meeting Clara answered in a median 1.8 s on our server across 27 turns.

Being fixed now, from the same meeting: (1) she said "Sorry, I couldn't get that" three times when Fede was talking to himself; she should stay quiet. (2) She listed October offers when asked about November; she should answer only what was asked. (3) Basic renewal and tenant questions: who gave notice and why, the renewal increase, how many renewals are open, a tenant's balance and late-payment history.

Proposed — pending review. Same 10 questions, same test company (a copy of Western Slope's buildings, no real customer on the call), same face and voice. "Hybrid" = Tavus's model talks and asks our server for every number (option B above). Answers scored against the real data, read with the row cap off.

Ours, before (Oct 5)Ours, after tonight's fixesHybrid
First word, median4.5 s4.8 s2.2 s
First word, slowest (p90)5.9 s8.3 s3.4 s
Answer length18 words23 words11 words
Questions answered5 of 59 of 10 (no answer on the last one)10 of 10
Facts right (8 checkable)not scored6 of 85 of 8
Stays quiet on team chatterNo (answered everything)Yes (live check: 0 words, turn ran clean)Not built yet
QuestionTrue answerOursHybrid
Leases ending in December8✅ 8❌ 4 (read a cut-off list)
Who gave notice11 tenants❌ "no one"❌ named 1 of 11
Renewals waiting on a decision75❌ 59❌ "none"
Open work orders · vacant units · 30+ days late · occupancy · cheapest vacant0 · 54 · 0 · 77% · $1,250 (checked on 200 of 234 units)✅ all five✅ all five

What it shows: the hybrid starts talking twice as fast and is about as accurate. Why the misses happened: the hybrid's December miss was a cut-off list (verified: 27 KB of leases cut to 8 KB). Its renewals miss was a lookup that came back empty, likely a status-name mismatch (every renewal is marked "ready"). Our misses go through a different path; cause not checked yet.

Hybrid lookups: 8 calls in 10 questions, all succeeded. Our server took 0.3–2.0 s each (median 0.8 s), over the 0.3 s target; the slow ones are the big lists. Hybrid isn't yet told to stay quiet on chatter; that's its own small change. It still runs the instructions it was created with on Oct 5, not today's video instructions.

Ours, after: tonight's fixes were silence, screen share and counts, not speed. It is no faster, and the slowest answers were slower (8.3 s vs 5.9 s), on a different data set with more questions. "Before" was on the real Western Slope data, "after" on the test copy: compare speed, not facts.

Next
A. Count tools for lists (leases ending in a month, notices, renewals by status)Recommended: fixes the December-style miss on both; renewals also needs the status names fixed
B. Hybrid stays quiet unless askedNeeded before hybrid can go into a meeting
Retro: the 3:30 PM team call went badly (Oct 5)

Proposed — pending review. Five minutes, Western Slope Clara in Fede's team Zoom. Sources: the Tavus transcript, our server logs for the call, and a read of the live renewals data.

What went wrongWhyFix
Wrong voice (not Belle)We set Belle on ElevenLabs' fastest engine, which Tavus doesn't list as supportedDone: moved to the engine Tavus documents, same Belle voice. Prove it on the next join.
Said "December has 6 renewals"; the true number is 8She had all 8 rows, but only an 18-lease total for the next 90 days. She counted the rows by hand and got it wrong.Add a count per month to the facts she gets at call start
Then said Fede's 8 units "match what I have"She agreed with the list read to her instead of noticing it contradicted herSame fix; with real counts there is nothing to guess
Spoke up about room chatter ("none of that was for me, I'll stay quiet")Tavus sends every bit of room chatter as a turn, and she announced she was staying silentWhen she isn't addressed, she says nothing at all
"I only see a Zoom notification" while Fede shared the Renewals screenTavus's own screen check saw the Renewals page; her answer did not use itLook at what reaches our server from Tavus's screen check
Replies cut short ("Yes," / "three oh")About 40 turns from Tavus in 5 minutes for about 10 real questions; people talking over her cancelled 4 of themTavus turn settings: reply only when addressed by name in a group
Slow first reply (3.5 s), long answersJoined from a script on Fede's Mac, which skips the warm-up. The shorter-answer fix is still in the merge queue.Join through the normal invite path; merge the two shorter-answer PRs

Speed on this call: first word after she was asked: 1.6–3.6 s on most turns, 4.2–5.6 s on 4 (cold start, a fresh server, one data lookup). No errors, no timeouts.

Video is a voice agent with a face: the long-term shape (Oct 5)

Proposed — pending review. Fede's framing, Oct 5: the face mostly mirrors what is said, so video Clara is a voice agent. We already know how to run voice agents on ElevenLabs. Treat Tavus as one more provider that adds a face. The only real difference is the audience: phone Clara talks to people outside (tenants, prospects, vendors), video Clara talks to people inside (coworkers).

The flaw today: phone and video are two different machines. Phone runs on ElevenLabs with rules written for calls. Video runs on Tavus with our email and text rules plus one line asking her to be short. That is why she read seven renewals aloud and spoke her private notes on the Oct 5 Zoom test.

PHONE · OUTSIDE PEOPLE VIDEO · COWORKERS Tenant, prospect or vendor callsTwilio phone line ElevenLabs runs the whole call Hears + decides you're doneturn v3, eager Claude Sonnet 4.6 (hosted by ElevenLabs)call rules: 2–3 sentences, one price, no listsfacts loaded at call start; our tools by webhook Speaks as the words arriveClara's voice 12 specialists: leasing, maintenance, renewal, triage… Caller hears Clarameasured 1.8–2.3 s (median) Coworker speaks in ZoomTavus bot in the meeting Tavus hears + decides you're done Our server: Clara's text-and-email engineClaude Sonnet 5.5 · email and text rules+ a one-line "be short" video layerspeaks its private notes; reads lists aloud Tavus waits for the whole reply (likely) Tavus voice + face back into Zoomaudio choppy Oct 5 Coworker hears Clarameasured 3.6 s (median) Two stacks · two sets of speaking rules · two voices · two sets of turn-taking settings
Figure 1. Today. Green = ElevenLabs runs it, blue = our server, amber = Tavus, red dashed = the problem. Phone numbers: latency audit and v4 turbo trial. Video numbers: two test calls Oct 4–5 (section below).
HOW SHE ARRIVES · swappable Phone lineTwilio · today Meeting with a faceTavus draws the face only ("Echo Mode") Meeting, voice onlymeeting bot (Recall.ai) · no face ONE VOICE AGENT · ElevenLabs Shared call rules + settings (one copy, both audiences)2–3 sentences · lead with the fact · one price, one date · no lists · fillers name the thing · turn-taking · Clara's voice Outside specialiststenants · prospects · vendorsleasing, maintenance, renewal, triage, billing…facts about one home or one leaserunning today Inside specialist: coworkeryour team, in meetingscompany snapshot loaded at call startstaff reads; count first, then offer the listtake a note → company brain CLARA'S BRAIN · every channel Our servercompany + property facts · tools (lookups, notes) · safety, privacy, fair housing · company brain · same as text and email email and text keep their own writing rules on top; voice never reads them Estimate: video lands near phone's 1.8–2.3 s plus the face (not yet measured). Today 3.6 s.
Figure 2. Target. One voice agent, two audiences, the face as an optional skin. The face drawing and the meeting path are the two unproven pieces (see the probes below). Sources: Tavus Echo Mode, Recall.ai audio output, ElevenLabs + HeyGen avatar, and the same layering in LiveKit and Pipecat, where the face is a plug-in on an existing voice pipeline.

Three long-term paths

A. Keep Tavus running the callB. ElevenLabs runs every voice, Tavus only draws the faceC. Build our own voice pipeline
What changesVideo takes the shared call rules (in flight now)Add a coworker specialist on ElevenLabs; send its audio to Tavus Echo Mode, or to a voice-only meeting botLiveKit or Pipecat on our servers; face as a plug-in
SpeedStuck at ~3.6 s until Tavus speaks sentence by sentencePhone is 1.8–2.3 s today; video inherits that plus the face (estimate)Best possible; most work
One set of learningsNo: two turn-taking setups, two voicesYes: every phone fix helps meetings tooYes, but we leave ElevenLabs
RiskLowEcho Mode in Zoom is unproven; staff data goes to ElevenLabs (tenant data already does)High; months

Recommendation: B, in three steps.

  1. Now (in flight, no customer effect): video takes the shared call rules, stops speaking private notes and the "one sec" filler, and gets a test bench that checks answers without a video call. All of this carries over to B unchanged.
  2. Two cheap probes (~5 Tavus minutes, needs Fede's go): (1) Tavus Echo Mode inside a Zoom meeting, fed audio from an ElevenLabs test agent; (2) ask Tavus whether a custom model's reply is spoken sentence by sentence, and about the choppy audio on conversation c67ab293976cc446. If Echo can't join Zoom, the fallback is a voice-only meeting bot.
  3. Then: the coworker specialist on ElevenLabs, same settings and voice as phone, with Western Slope's company snapshot loaded at call start. The face becomes a per-meeting choice.

Unverified: whether Echo Mode works with a meeting link (docs show it in Tavus's own rooms); whether ElevenLabs agents can join meetings natively (none found; the documented route is a meeting bot). Phone's model runs at ElevenLabs today, so B also means company-level staff data reaching ElevenLabs.

Where the 3.6 seconds go, and the top 3 moves (Oct 5)

Proposed — pending review. Measured on two test calls the night of Oct 4–5; nothing below is being built until Fede picks.

Short answer: the model is the biggest single piece (1.2 s, measured). The likely surprise: on 12 of 12 turns, Clara's voice started 0.57–0.73 s after our whole reply had arrived, never during it. That points to Tavus waiting for the full reply (~0.7 s per answer), but it is not proven: every reply was about the same length (~100 tokens), so a slow voice engine would look the same. The 3-second pause test in move 1 settles it.

SUPPORTING ONE SPOKEN TURN, TODAY SECONDS SINCE YOU STOPPED DynamoDBcall, snapshot, notes Prompt cacheAnthropic · 1 hourprimed at call start Company brainmeeting notes saved here You stop talkingin Zoom or the browser Tavus decides you're donespeech recognition + turn detection (patience: medium) Internet hop, Tavus to usnetwork, counted as ours Our server accepts the turnVercel, Virginia · checks the call in the database Claude Sonnet 5.5 writes its first words~19k cached tokens: instructions, 26 tools, company data Rest of the reply streams to TavusTavus waits for all of it · ~100 tokens, ~6–7 ms each Our reply ends, then Tavus starts the voicevoice engine · 566–725 ms after our stream ends Tavus sends face + audio backinto the meeting You hear Claramedian 3.6 s · first answer 3.3 s · slow (p90) 4.8 s 0.56 s 0.07 s 0.10 s 1.19 s · biggest ~0.70 s ~0.6 s 0.17 s ≈ 3.6 s median 0 s1 s2 s3 s Tavus ~1.3 s fixed Tavus waiting for our full reply ~0.7 s Ours ~1.36 s (incl. the hop) Dashed blue outline = our side. Supporting pieces on the left are what our server reads and writes.
Figure 1. Today: one question, 3.6 s until Clara makes a sound. Each bar starts where the step above ended. Bars are per-step medians, so they add to ~3.4 s; the whole-turn median is 3.6 s. Releasing our first phrase early buys nothing today, because Tavus waits for the end anyway. The Tavus stages the next section lists as "not measured" are measured here.

What we already did (Oct 4–5)

MeasureBeforeAfter
First answer of the call4.9 s3.3 s
Typical answer3.7 s3.6 s
Slow answers (p90)5.6 s4.8 s
  • A dedicated fast route for video answers.
  • Thinking turned off.
  • Send at the first phrase, not the full sentence (no gain until move 1 below).
  • Company data loaded and cached before anyone speaks.
  • The conversation is reused turn to turn. Proven: the cached part grows each turn, and the new part stays at ~130–270 tokens.
  • Meeting-first turn-taking: wake word "Clara", interruption sensitivity high.

Tavus test minutes used: 6.04.

Top 3 moves

#MoveWhat it hitsSaves (estimate)
1Make Tavus speak as soon as the first sentence arrives. Ask Tavus, and run one cheap test: a reply that sends one sentence, pauses 3 s, then sends a second, once with emotion control on and once off.The ~0.7 s Tavus spends waiting for our full reply~0.7 s
2Shorter video answers (~40 tokens instead of ~100). A wording change, so Fede decides.The same wait, while Tavus still waits for the full reply~0.35 s
3Tavus settings: turn-taking patience low, plus their faster voice engine.Turn detection and voice start~0.4–0.6 s

Instant opener (~1.9 s off the first sound) only works once move 1 is fixed; today Tavus would hold the opener until the whole answer finished.

ONE SPOKEN TURN, AFTER MOVES 1 + 3 SECONDS (ESTIMATE) You stop talkingsame as today Tavus decides you're donepatience low (move 3) · today 0.56 s Hop + our server accepts the turnunchanged Claude Sonnet 5.5 writes its first wordsunchanged Voice starts on the first sentenceno wait for the full reply (move 1) + faster voice (move 3) Tavus sends face + audio backunchanged You hear Clara≈ 2.2 s (estimate) · today 3.6 s 0.3 s 0.17 s 1.19 s ~0.35 s 0.17 s ≈ 2.2 s today: 3.6 s ADD THE INSTANT OPENER (ONLY AFTER MOVE 1) "Sure," the moment the question arrivesfirst sound, with the real answer right behind it ≈ 1.1–1.5 s to first sound 0 s1 s2 s3 s
Figure 2. The same turn after moves 1 and 3, same colors and scale as Figure 1. Every number here is an estimate. On Sonnet the model's first words take ~1.0–1.2 s regardless of trimming.

Ruled out

  • Haiku or any smaller model. Fede, Oct 5: no.
  • Moving company data onto Tavus's own AI. Customer data can't go there.
  • Shrinking the prompt. Measured: the first word takes ~1.0 s with a warm cache whether the prompt is 5k or 33k tokens.

Decisions for Fede

QuestionOptionsRecommendation
(a) Ask Tavus, and run the 2-turn sentence-pause test (~3 Tavus minutes)?Yes / HoldYes
(b) Shorter video answers (~40 tokens)?Yes / NoYes
(c) Tavus patience low + faster voice, one change at a time?Yes / HoldYes
(d) Instant opener?Decide after move 1Wait for move 1

Risk on (c): low patience may cut people off mid-sentence in meetings. The wake word "Clara" softens it, because she only answers when addressed.

Why video feels slower than the phone, and the plan

Short answer: data isn't the delay. On the test link all of Western Slope's data is already loaded into her instructions when the call starts, so a normal question reads nothing (0 ms), and her words stream to Tavus as they're written. That's the same "everything in the prompt" approach the phone agent uses, with the same model. The extra time is on the video side: Tavus deciding you've stopped talking, an extra internet hop to our server, and rendering the face.

PHONE (ELEVENLABS) Caller stopstalking Turn end + transcriptElevenLabs Model, context in promptcalled by ElevenLabs directly Voiceaudio only VIDEO (TAVUS), TEST LINK Speaker stopstalking Turn end + transcriptTavus · not measured Our server → model, data in promptfirst word 0.7 s typical · 2.5 s slow Voice + faceTavus · not measured Video to browserWebRTC + internet hopto our server Measured on our server, last 200 real turns, 2026-10-02. Tavus's stages aren't visible to us; the control call below splits them out.
Blue is ours and measured. Amber is Tavus's and not yet measured.

What Fede saw on the 8:45 and 8:51 calls (2026-10-02), and what changed the same morning:

ProblemCauseFix on the test link
She repeated "two tours this weekend" to six unrelated remarksShe treated room chatter as a question and fell back to the last answerNew rule: answer only the newest thing said; side chatter gets a few words. Re-tested: "she's very robotic" now gets "ha, fair"
Three answers took 24–43 s, then "I hit a snag"The model call stalled and nothing cut it offNo first word in 4 s: cancel and try once more, then a short "give me a second"
First question of every call about 3–4 sHer loaded data isn't cached yet on the first questionThe data is warmed the moment the call starts
Slow to answer after you stopTavus waited longer for a pause, and its "start answering early" setting was offQuicker turn-taking, and early answering on. Tavus cancels an early answer if you keep talking, and we now stop it at once
Hard to cut her offInterruption sensitivity was lowRaised to medium
Lips lag the voiceNot ours to see. The face is already Tavus's newest model (Phoenix 4.5)Control call below

Where data should live, as companies grow:

LayerWhatSpeed
Loaded when the call startsA summary of the caller's own buildings: key numbers, listings, tours, prospects. Built in production by the call-start summary (#15487), limited to the caller's buildings (#15489). Western Slope is about 5k characters0 ms per question
Looked up mid-callRare deep questions, such as one tenant's ledger. Read-only lookups limited to the caller's buildingsOne extra model round, about 1–2 s
Document search (RAG)For long documents (leases, policy manuals). That's what ElevenLabs' knowledge base is. Not needed for numbers, and our knowledge base still fits in the promptLater, if documents get big

This is the scalable version of "load everything into the prompt": load everything about this caller's buildings, cached, and keep lookups for the long tail. A 10k-unit company still gives each manager a small summary because it's scoped to their buildings.

Proposed — pending review. In production, Clara writes the whole answer before Tavus starts speaking. Streaming it word by word is a chain of three changes, not a small one: (1) a streaming version of the shared model call that every Clara channel uses, (2) a decision on three safety re-checks that today re-write a finished answer (they can't re-write words already spoken), (3) a rule for the two steps that clean an answer before it goes out (removing internal instructions, and the personal-information check). Options:

  1. A. Speak the finished answer sentence by sentence after the cleaning steps. Small change, saves a few hundred milliseconds.
  2. B. The full three-change chain. Biggest gain, roughly matches the test link's speed.
  3. C. Leave as is.
Recommendation: do A now, then B. Also trim two wasted lookups that run before the model on every video turn (a small separate change).

Long-term setup (Proposed — pending review)

One Clara brain, on our side. Tavus (and later the phone vendor) only carries the face and voice. Speed comes from streaming, a warm prompt cache, running in the cloud next to Tavus, and trimming setup.

Ruling (Fede, 2026-10-02): no smaller (Haiku) model, and no Tavus-hosted brain with customer data. The Tavus-hosted model is used only as an A/B measurement, against a made-up company ("Pinecrest"), so no customer data goes to Tavus.

The two video modes
Website callMeeting listener
Who starts itA signed-in person clicks "Talk to Clara"A signed-in person schedules Clara into a meeting link
Company comes fromThat person's sign-inThe person who scheduled her, never the meeting itself
She speaksEvery turnOnly when addressed ("Hey Clara"), using Tavus's wake and sleep phrases
AfterwardsNothing extraThe transcript goes to renewals as notes waiting for approval

Staying on the $59 plan: Clara rejoins every ~15 minutes. Starter caps each Tavus call at 15 minutes, and a renewals meeting runs longer. Whether Starter can join meetings at all is unverified: the pricing page and the meetings doc don't say which plans can.

  • Our own meeting session is the parent. The binding and Clara's memory hang on it, not on a Tavus call.
  • Each Tavus call is a child of that session. Its company is copied from the session when we create it.
  • At about 14:30 our server ends the child at a quiet moment (nobody addressing Clara) and starts the next one.
  • Running notes carry forward on our side. Tavus memories are never used.
  • Gap risk: a few seconds of absence plus the join animation, and anything said in the gap goes unheard. We measure it from the end time of the last child to the first transcript line of the next, every rejoin, and report the median and worst case.
Engineering detail
  • The meeting session is VIDEOMEETING#<id> {organizationId, schedulerUserId, meetingUrl, startsAt, children[]}, and each child is a VIDEOCALL#<tavusId> binding with parentSessionId. Children are created only by our server.
  • Tavus create: meeting_url, max_call_duration about 870 s, and a per-session callback_url secret. Perception off and wake_phrase/sleep_phrase live on the setup (the PAL's conversational-flow layer), not on the call.
  • The quiet moment comes from the last utterance timestamp seen on our endpoint. If there is none by 14:50, we rejoin anyway.
  • Tavus docs do not say what ends a meeting conversation, how display names, waiting rooms or passcodes work, or how speakers are told apart. All of these must be tested on a real Zoom before the client call.
Keeping companies apart

This is the most dangerous part. One rule: only a signed-in person starting Clara from our app names the company. Every other signal can only make her say less.

SignalCan it name the company?Why
Signed-in start in our app, membership checkedYes, the only oneForging it means stealing a session
Meeting claimed by that company at startOnly because of the startWritten once; can't be claimed twice
The company's calendar got the inviteNoAnyone can invite a company's mailbox
Organizer emailCheck onlySpoofable without a passing email check
Attendee email domainsVeto onlyAnyone can add attendees; many managers use gmail
Meeting title, display namesNeverTyped by anyone; a place to plant instructions
CaseWhat Clara does
Attendees from another client, or unknown peopleCompany stays pinned; the whole call switches to no specifics (no names, balances, phones or emails) until they leave
A person who belongs to two companiesCompany comes from the start, never from the person
PropFlow staff start itMust pick one company, with a reason, audited; "all companies" refused; no "view as"
Invite forwarded, or link reusedNo new binding, so no data
"Do you also work with Situs?"One fixed line: "I can only speak to [Company]'s portfolio in this meeting."
No binding, expired, or brokenNo data, and she says "I'm not connected to a company for this meeting."

What Tavus sees: we upload nothing to Tavus. It hears what Clara says on the call. Recording and memory are off, and each conversation is deleted after the call.

v1 (next week): start from our app only; the client's own staff only, because Tavus docs don't say what attendee or invite details Clara sees, so she can't detect outsiders yet; read-only; the start page warns that Clara will share this company's data with everyone in the meeting. Later: joining from a calendar invite (with a verified organizer and the attendee veto), automatic no-specifics mode, actions, and owner meetings.

Threat list (15)
#ThreatDefense
1"Ignore previous instructions," spoken or typedCompany never comes from model text or the Tavus request; tools can't reach other companies; no write tools in v1
2Instructions hidden in data (an email, a work-order note)Same wall; no raw content read aloud
3An outsider invites a company's calendarv1 never starts from an invite
4Forwarded invite or reused linkNo new binding; unknown joiners mean no specifics
5Company picked from a person in several companiesNamed at start; no person id on the call's viewer
6Staff "all companies" bindingRefused for meetings
7Stolen call id (it's in the room link) plus the one shared Tavus keyBindings live ~18 minutes; rotate the key; per-call key if Tavus allows
8Notes written into the wrong company's renewalsWrites through the pinned company and its building list; only the starter approves
9Tavus hears the answersListed as a data processor; conversations hard-deleted after each call
10RecordingsTavus recording off; the client's own Zoom recording is outside our control, another reason for no specifics
11Tavus memory carrying one client's facts to anotherMemory and knowledge base off; Tavus history untrusted every turn
12Logs or error reports holding meeting speechIds only; check whether Sentry captures request bodies
13Asked about another client by nameOne fixed line; no other customer names in the prompt
14Answers changing mid-callBuildings fixed at start
15A broken binding falling back to wide scopeEvery failure means no data
Engineering detail
  • Pin: VIDEOCALL#<id>/binding via recordVideoCallBinding (#15301), write-once, read consistently, expiring. For meetings, refuse when getUserOrgScope(user) === null. Write it in the scope-row shape mintFromConversation reads (organizationId, scope key, propertyId, mapVersion), so moving to getRepository/checkPartition later is cheap.
  • Per turn: buildVideoToolContext (llm branch) builds a stand-in viewer with no person id, cut to the bound company's buildings. Never use requireUser, getUserOrganizationId or resolveInboundStaffMembership* here: without an org they return the first company found.
  • Don't reuse isForeignOrg/fenceIdentityToOrg: they fail open on unknown companies, on purpose, for texting. Meetings need fail-closed.
  • Tests T1–T8, each with a deliberate break: every tool against a seeded second company; a person in two companies; staff "all"; injection can't move scope; write-once and expiry; a static ban on the user-resolvers; prompt hygiene; the AppFolio login choice.
  • Voice today trusts the model's own property_id over the server's (/api/voice/tools/[tool] :428–433). Video must not copy that.

Source: isolation findings, 2026-10-01 night.

Renewal notes from meetings

Clara is the ears; the renewals team owns what happens to what she hears.

  • After the meeting, the transcript comes to us. The renewals lane's extractor pulls out, per tenant, what the team decided: lease length, price, rent, exceptions, and who on the lease it concerns.
  • Nothing applies until a person approves it, through the renewal approval step that already exists.
  • She also learns how a team walks through renewals ("October has three, then each tenant"). That comes from real meetings, not from us guessing.

Proposal from the renewals lane (to be decided with Fede after Western Slope's renewals turn on Monday; date unverified here): no separate store. The notes become a per-lease, per-renewal-cycle override that the renewal policy reads, keyed the same way as the renewal record (lease plus cycle).

Source of real meetings: we have no Camellia tenant-by-tenant renewal-meeting transcripts. Western Slope holds a weekly recorded renewals meeting and offered to invite us (unverified here).

Engineering detail
  • Facts: the renewal saga runs on Temporal, with the DynamoDB RenewalSaga as its read-model (ADR-0047, renewal-orchestration runbook). No notes or human-context field exists on Tenant, Lease, Renewal, RenewalSaga or RenewalSnapshot. The approve route is /api/leasing/renewals/approve (approve / reject / convert_mtm); HUMAN_REVIEW_PENDING maps to escalated.
  • Proposed shape, for the renewals lane to accept or change: {leaseId, cycle, termMonths?, proposedRent?, exception?, perPerson?: [{personId, note}], source: {meetingSessionId, quoteStartSec, quoteText}, status: proposed|approved|rejected, approvedBy?}.
  • Video supplies only the verified transcript, with speaker names and timestamps, scoped to the meeting session's company.
Actions on a call (phase 2)

"Send this renewal, but make this exception" goes through the same staff tools and the same write gate as a staff text. Clara repeats the action back and waits for a spoken yes before doing it.

Honest gap: staff texts today have no built-in "confirm before write" step. Desk writes run as soon as the model calls them, and the prompt only asks Clara to read back the result (staff-write-gate.ts, staff-leasing-desk-prompt.ts). We build that confirm step once, in the shared staff capabilities, for text and video together. A video-only version would be a parallel path.

Built and deployed the ElevenLabs way

Video Clara is run like the phone agents: her setup lives in our code, a merge deploys it, and a nightly check catches hand edits. The setup Tavus holds is thin and has no client data; what Tavus hears on a call is covered under Keeping companies apart. Everything about which company is on the call lives on our server.

PiecePhone today (fact)Video (proposal)
How many setupsOne shared set for every client (Fede, 2026-09-05/06; Western Slope row 2026-09-11)One thin Tavus setup per environment (dev and production), shared by every client
What's in itPrompt, tools and turn settings per agentStock face, Clara's ElevenLabs voice, speech engine, turn-taking, perception off, and our answer address and key. The prompt is a stub; the real one stays in our engine.
Source of truthAgent modules + config/elevenlabs-agents.json, 7 sync scriptsOne file (config/tavus-personas.json), one sync script, one comparator
DeployOn merge only, behind deployLaneRefusal(); "THERE IS NO BYPASS"Same lock. The PR shows a dry-run diff; the merge applies add/replace/remove changes, reads back, and restores the snapshot on mismatch
DriftLive check on PRs; nightly reportSame, report-only
Testing before mergeDrift tests, evals, fastlane real callA text runner through the one Clara engine against Fairhaven on every PR (no Tavus minutes), plus one real call nightly
StagingWillows clone fleet, same account; voice has no preview laneThe dev setup, pushable from a branch. Tavus has no publish step, so dev is the only buffer.
New clientNo new agent; a company record and a phone rowNo new setup; the company comes from the call binding

Two rules that come from how Tavus behaves:

  • Setup changes go in their own PR, never mixed with endpoint code. Tavus test-calls our answer address when a setup changes, and our code can reach production up to 30 minutes after merge.
  • Creating the first production setup is a third-party production write, so it needs Fede's go. After that, the merge lane updates it.

The Western Slope production snapshot used by the local proof never goes into git or CI. Tavus docs put the wake phrase on the setup, and create-conversation lists no per-call override, so meeting mode likely needs a second setup per environment (inference). Source: build-deploy findings, 2026-10-01, from origin/main e6be230d12.

Where the build stands (2026-10-02)

The code is done. Clara can take a video call, answer from the same brain as text, email and phone, look things up only inside the caller's buildings, and close the call cleanly. Nothing is on at any company yet.

PieceStatus
Staff questions stop loading the prospect leasing instructions (#15322)Live
Video is a channel of the one Clara brain, Tavus is its carrier (#15319, #15359)Live
Start a call and tie it to the signed-in person's company (#15301)Live, off until the company setting is on
Know who is on the call and what they may see (#15330)Live
Portfolio lookups for staff, on text too (#15338)Live everywhere, no switch (Fede's yes)
Video turns can only read, never act (#15342)Live
The endpoint Tavus calls for each answer (#15365)Live, needs the production secret
Lookups stay inside the call's buildings (#15489)Live
A summary of the caller's buildings ready when the call starts (#15487)Live
End of call closes the conversation; late answers are refused (#15484)Live
The page to start a video call (#15483)Live, off until the company setting is on
Admin company picker on the video call pageMerged. Platform admins choose the company and give a reason before a call
Ending a call, removing Clara from a meeting (#17579)Merged 2026-10-07 (main 45b6f592e). Found in Fede's Zoom test: "Done" and "End Call" only cleared the page and never ended the Tavus call. Now both end it; meeting mode gets a "Remove Clara from meeting" button. Not a fix: Clara saw a black feed with a Zoom notice until the testers rejoined; our code sets nothing about vision, so that came from the Zoom side
Meetings ClaraSet up for testing. Joins Zoom, stays quiet until "Hey Clara", goes quiet at "Thanks Clara"
A/B test pageReady. Same face and voice, our brain vs Tavus's own model, on a made-up company
Minute protectionDone. A call nobody joins ends after 1 minute; calls end 1 minute after the last person leaves the web call, 30 seconds after a Zoom empties
Plan175 video minutes a month, pay as you go after that, 3 meeting seats
Pronunciation list for videoNot yet. The phone has one; video needs its own

Streaming, reopened 2026-10-02. Most whole-reply checks are off for staff, but three re-checks that re-write a finished answer and the two cleaning steps still run, so true word-by-word streaming is a chain of three changes. The plan is in Why video feels slower than the phone.

Skipped for now: the full staff write list (no exposure: video can only read).

What's left, in order:

  1. Fede: yes to create the production Tavus persona and set its three secrets. (Tavus Starter bought 2026-10-02.)
  2. Turn video on for our own test company (Fairhaven / Willows) and run a 5-minute bench call: every answer traced to a real record, first-word time read from the logs, the end-of-call signal seen.
  3. Fede turns it on for Western Slope's website.
  4. Meeting join with the 15-minute rejoin, about a day after Starter.

Follow-ups (not blocking): a "Talk on video" link in the app, a company picker so admins can start a call, the outcome event for company-level calls, a race when the call ends mid-answer, and the eval proof for the call-start summary (#15488).

Decisions

Open, for Fede:

QuestionRecommendation
Buy Tavus Starter ($59 a month, 15-minute calls; rejoin covers long meetings)?Yes. A purchase, so Fede's go.
Apply to Recall.ai's startup program, for the long-term listen-mostly option?Yes
Should staff texting Clara get the same portfolio reads (read-only, staff-only, no switch)?Yes. Same tools on every modality.
Perception off for meetings? (A proof call described the caller's face.)Yes, off
Tavus's AI notice (disclosure_type) in client meetingsNeeds a legal check before any client meeting

Decided by Fede on 2026-10-01: Clara's phone voice (ElevenLabs), a Tavus stock face with no real person, Tavus account set up, answers only in v1, website first. Updated the same night: also join a client's renewals meeting next week. Video is another modality of the one Clara, not a wrapper around Ask Clara.

Found while investigating (not video work)
  • Staff questions load the prospect leasing prompt. A staff-only turn pulls in about 57k tokens of leasing prompt through the "unknown sender means leasing" fallback (leasingBodyWouldLoad, clara-unified.ts:85–111). This is likely live on today's staff texting desk too (inference; one desk trace will confirm). It's chain step 1, a fix with no switch.
  • Staff write list: a small consistency cleanup, no exposure.
Risks
  • Cross-client leak in a meeting. This is the top risk. See Keeping companies apart.
  • AI disclosure in client meetings. Attendees may not know she is AI, and EU rules on this apply from 2026-08-02. Flagged for Fede; not decided here.
  • Call length. Free plan calls stop at 5 minutes (our proof calls ended at the cap), and Starter calls stop at 15. Rejoin covers this, with a gap.
  • Which plans can join meetings: unverified. The pricing page and the meetings doc don't say. Check in the Tavus dashboard before buying.
  • No recording on meeting joins. Tavus records only its own rooms. We rely on the transcript.
  • Tavus analyzes faces. One proof call returned a description of the caller's appearance. We turn perception off for meetings.
  • Speed. The first-word times are dev measurements on a tiny test company. On Western Slope-sized data, lookups will be slower and she may feel laggy.
  • Tavus hears every answer. Zero-retention is Enterprise only. Recording and memory off; we hard-delete each call after storing the transcript.
If we drop the face
OptionCostGoodBad
Tavus audio-only (audio_only: true)Not published; assume the same per minuteNo code changeSame 15-minute cap assumed; no saving known
Meeting-bot service (Recall.ai) + our ElevenLabs voice$0.50 an hour + $0.15 an hour for transcription, about 1¢ a minuteZoom, Meet, Teams; speaker names; no length capMore for us to build
Phone Clara dials the meeting's dial-in numberExisting phone costsReuses the phone agentNeeds dial-in on their account; no speaker names; shows up as a phone number

Sources: Tavus create-conversation docs; recall.ai/pricing. Recommendation: Tavus with rejoin for next week; a meeting-bot service is the long-term option for meetings where Clara mostly listens.

Tavus: everything we learned

Research passes 1, 2 and 2b (2026-10-01), covering part of the ~230 doc pages. More research is coming and will be added to this section.

PieceWhat it is
PAL (was "persona")The setup: model address, voice, speech engine, perception, turn-taking
Face (was "replica")Rendered by Phoenix (4.5 since Sept); 50 stock faces
Raven-1Perception: sees the camera and screen share; sends what it sees to the model
Sparrow-2Turn-taking (default since 2026-09-08)
Custom modelOpenAI-style streaming endpoint; per-PAL headers and extra body; speculative starts before the speaker finishes
VoiceElevenLabs or Cartesia with our key and voice id
Speech-to-textTheirs only (auto, Soniox, Whisper, Deepgram); hotwords for hard names
SettingWhat it does
meeting_urlJoins Zoom, Meet or Teams (Teams since 2026-09-12)
wake_phrase / sleep_phraseSilent until addressed, keeps listening for context (set on the setup, not per call)
idle_engagementoff / patient / eager re-engagement when people go quiet
pal_interruptibility, turn_taking_patience, voice_isolationHow easily she is cut off, how long she waits, background-voice filtering
callback_urlEnd-of-call events, including the transcript with speaker names; no signature documented
audio_only, test_modeVoice-only call; a call where the face never joins (whether it skips the concurrency limit is unverified)
Tool delivery by APISigned with HMAC-SHA256 (X-Tavus-Signature)
PlanPriceMinutesPer callAt once
Free$0255 min1
Starter$59 a month10015 min3
Growth$397 a month1,25060 min10

Overage is $0.37 a minute ($0.32 on Growth), billed with a 30-second minimum and then in 6-second steps. Third-party comparisons put Anam at $0.11–0.16 a minute. Sources: tavus.io/pricing, docs.tavus.io (llm, meetings, conversational-flow, webhooks, create-conversation, tools), research file pass 2.

Not documented yet: the bot's display name, waiting rooms and passcodes, what ends a meeting call, how speakers are told apart, the attendee list, and API rate limits.

What she can answer first
  • Properties and units
  • Tenants and leases
  • Prospects and tours
  • Renewals
  • Work orders and turnovers
  • Conversations, including emails Clara was part of
  • AppFolio reports, through our adapter
  • Basic leasing questions

Not yet: taking actions such as booking a tour or adding a note. See Actions on a call.

Who sees it when
  1. Local test with Fairhaven test data.
  2. Built into the website behind a company setting that is off everywhere.
  3. Fede tries it with Western Slope data.
  4. A test meeting on our own Zoom, then Western Slope's renewals meeting, on Fede's go.
  5. Turned on at Western Slope only, on Fede's go.
Cost

See the plan table under Tavus: everything we learned. With rejoin, a one-hour meeting is four Starter calls, about 60 of Starter's 100 monthly minutes. Buying a plan is Fede's call.

Build plan
  1. The engine chain: done; see Where the build stands.
  2. Meeting listener: the meeting session, wake phrase, 15-minute rejoin, and transcript to renewals.
  3. The "Talk to Clara" button, with the video call right on the page.
  4. Deploy lane for the Tavus setup, built the ElevenLabs way.
Long-term vision: the company brain (not planned)

Vision — Fede, 2026-10-02. Not being built; written down so today's design choices don't block it.

Each customer gets a company brain that Clara draws on.

  • Base layer (parts exist today): knowledge base, policies, listings, fees, rules.
  • New layer, coworker notes: what Clara hears in meetings she joins on video or Zoom, transcripts and notes the team shares with her, and team decisions. It could be part of the knowledge base or a separate store next to it.

Clara checks this layer before acting. Example: renewals. Today the renewal flow follows basic rules. With the brain, Clara first checks whether a team meeting or decision changes what she should do.

It is an add-on, so not every customer will have it.

What it means for today's video work: meeting transcripts are worth keeping per customer rather than throwing away, and they never cross between customers.

Engineering details

Tracking issue: #15255. Sources for this page: origin/main dfeb59b5cc (inbound-router.ts, envelope.ts, conversation-manager.ts, capabilities/index.ts, dashboard/chat), ADR-0022, ADR-0054, architecture-source-of-truth (P5, P8, F7), portfolio-architecture-how (§02, §04, §05), agent-loop-decoupling-2026-09, voice-agent-architecture-decision, docs/runbooks/elevenlabs-voice-agents.md, docs/runbooks/renewal-orchestration.md, and the local proof log.

PropFlow Docs