How Clara decides whether to reply, rebuilt so she stays quiet when unsure. For Gera, Fede and the team.
2026-10-29 · v3, the build plan · follows Classifier Plan v2 · reviewed by Fable · design only: nothing here is built yet beyond the PRs named as shipped
Clara replies only when she's sure the reply is allowed, helps, and is true. The page keeps its topic labels. The reply decision adds who's writing, the thread's state, the kind of message and risk, then an Opus check reads the draft itself. Every check must pass, and we count what each one blocks.
Shipped and live: #22879 (money owed holds when Collections is Off or Supervised), #22880 (its eval), #22972 (a general reply follows who is writing), #22995 (an unknown sender's reply waits under the first module that isn't Off), #23220, #23237, #23258 (the hold says "We don't know who this person is"), #22935 (Resident Services is a real module), #23380 (Resident Services is its own reply topic and sends only while Live), #23428 (silent hand-off, slice 1).
In flight: #23500 and #23508 (silent hand-off 2a and 2b: a reply that is only a hand-off or a hedge sends nothing), and the neighbor-complaint hold (being built, no PR yet).
Still true on main:
classify-reply-topic.ts) has five labels (renewal, maintenance, collections, resident services, other). It runs on Haiku 4.5 with a 6-second limit. On a timeout or error the renewal hold reads it as "other", and the Supervised hold falls back to the thread's module, so most replies still go.shouldSpeak returns "silent" only when a teammate already holds the thread, or from the silencer that runs before a draft. Never because of the kind of message, the topic or the risk.| Measured on 639 real moments (Sep 27 to Oct 27) | Today |
|---|---|
| Page topic agrees with the reviewers | 75.7% |
| Rent trouble labelled "billing" instead of collections | 74% |
| Old 3-label reply check: right action | 81.6% |
| Old reply check: "shouldn't reply" moments it called "other" (sends) | 75% |
| Email "shouldn't reply" moments Clara actually replied to | 24% (18/75) |
| Text "shouldn't reply" moments Clara actually replied to | 4/119 |
| Today's 5-label reply check on the same set | not measured yet: part A1a measures it |
344 of the 639 moments are texts and emails (179 + 165); the other 295 are voice, which this plan leaves alone. Every replay gate below uses the 344. A second, separate measure of page labels: the 10-27 audit hand-judged 248 messages at 84.7% strict and 92.3% counting defensible calls. This plan's page target uses the turn-study measure: 75.7% today, 90% target.
Reviewer agreement (Fleiss κ): who's writing 0.85, kind 0.70, topic 0.68 (0.82 when all three named one), action 0.67, any risk 0.39.
Fede's engine: gather context → shouldSpeak → writer → draft check → record → proof rail. Tenant comms is Gera's swarm. The core doors (shouldSpeak, the notice engine, the send door, the writer) are Fede's.
| Step | What it does | Model | Owner |
|---|---|---|---|
| 1 · Gather context | Who's writing, from identity records. Thread state, from the record: a teammate active, a person on the call, an open escalation, Clara asked last. The keyword risk floor. Each module's state at the building. Only what existed at that moment. | None | Interface: Fede. The parts that fill it in: swarm |
| 2 · shouldSpeak | Rules only. Silent when a teammate is active, a person took the call, the thread is escalated with an open matter, or the sender is automated. Silent plus a tracked ask when the keyword floor finds money trouble, legal or hardship. The emergency chain is unchanged. An unknown sender's reply is held under the first module that isn't Off (as today). Otherwise Clara drafts. | None | Fede |
| 3 · Writer | Opus. Writes a draft, or [[NO_REPLY]]. It also returns its own read: kind, topics, risk flags, the value it adds, the facts it used (tool results). | Opus | Fede (prompt), swarm (the shape of its read) |
| 4 · Draft check (new) | A separate Opus call with a different job. It doesn't see the writer's reasoning. It answers three questions: allowed, adds value, true. It also gives its own kind, topics and risks. | Opus | Swarm. Where it plugs into the send path is a change to Fede's contract, which Fede approves. |
| 5 · Combine and record | Every check must pass, decided by rules and not by a model. Passed everything and the module is Live: send. Passed everything and the module is Supervised: a person approves it. Anything failed, unsure, timed out or unreadable: silent, flagged for the team, and it goes into Fede's daily open-conversation digest. Engineering failures also post to #alerts and never reach the customer. One decision record per turn names every check that blocked. | None | Swarm (record), Fede (notice engine) |
| 6 · Proof rail | Answer key, arms, judge, nightly CI gate, shadow diff. | Opus judge | Swarm |
Value rule (Gera): reply only with real value. A greeting gets a greeting. Never "I'll let the team know". A hand-off is silent. This covers texts and email; voice and the tour offer keep their wording. The draft check's "adds value" question generalizes the silent hand-off work (#23428, #23500, #23508): those stay as the fixed rule underneath it, and the check catches the hedges they miss.
No topic: the reply follows who's writing. Prospect → Leasing, resident → Resident Services, vendor → Maintenance, unknown → held under the first module that isn't Off.
The whitelist: every topic in the message and in the draft has to resolve through one map. Live → can send. Supervised → a person approves. Off → silent. general or none → use who's writing (above). Truly unmapped → silent. Nothing sends by being left out.
One TurnAssessment per inbound text or email. Records are only ever added, never edited. The message's page topic and subtopic stay where they are.
| Dimension | Values | Comes from |
|---|---|---|
| Who's writing (actor) | resident · prospect · vendor · staff · owner · unknown · automated, plus the identity record it matched | Records, no model |
| Thread state | staffActive: the latest staff message is newer than Clara's latest, and under 24 hours old (with a backstop of the last 10 rows). humanOnCall: a transfer happened. escalated: an open matter exists. awaitingOurAnswer: Clara asked last. The exact constants are set by replaying the 344 rows. | Records, no model |
| Kind | greeting · thanks/bye · ok · real ask · answer to our question · FYI · automated · unclear | Writer and draft check |
| Topic + subtopic | The page's labels, one list, more than one allowed (9% of messages have two). Changes: collections is split out of billing; resident inquiry becomes Resident Services; a staff lookup is a kind of sender, not a topic. | Page labeller, writer, draft check |
| Risk | money trouble · hardship · legal/dispute · safety · privacy/identity · fair housing · staff decision pending · distress · confusion · wants a person · neighbor complaint, plus the keyword floor | Keyword floor, writer, draft check. A flag from any source counts. Never a vote. |
Each record also keeps what the labels were when the decision was made next to the current best guess. If a later relabel would have changed a past decision, it goes to review. It also stores the model, prompt, policy, keyword-list and taxonomy versions.
allowed (with topics and the module each maps to), adds_value (with why), true (with any unsupported claims), risk flags, kind. One call returns all three verdicts (Fable: three calls to the same model triple the cost and add no independence). A missing verdict counts as a fail.| Slice | Rows |
|---|---|
| Money-adjacent (hardship, promises to pay, disputes, late fees, eviction, the post-transfer half of a balance call): 150 for development, 300 locked for the final challenge | 450 |
| Random real traffic (to measure how common each case is) | 150 |
| Leasing, maintenance, renewal, Resident Services (100 each) | 400 |
| Thread state: teammate active, person on the call, escalated | 100 |
| Soft speech: hi, thanks, ok | 75 |
| Unknown, vendor, automated senders | 75 |
| Safety, fair housing, privacy | 50 |
| Adversarial: instructions hidden in a message, wrong identity | 50 |
| Total | ≈1,350 |
Primary: the unsafe-send rate among sends: sends whose right answer was "don't reply", or that fail allowed or true, divided by all sends. We report it with a one-sided 95% upper bound, overall and per module.
Always shown beside it, so "hold everything" can't win: missed holds on the 300 locked money rows (0 of 300 means under about 1%), and coverage, meaning the share of right-answer-is-reply rows that actually send.
Secondary: blocks per check; value violations (a hand-off or hedge that went out); page topic agreement (75.7% → 90%); Resident Services vs Collections mix-ups; time p50 and p90 and the timeout rate; how often a label flips across 5 repeats of the hard rows; cost per 1,000 turns; holds per customer per day; digest size.
| Release bar | On the key | In shadow |
|---|---|---|
| Money | 0 misses on the 300 locked rows | n/a |
| Unsafe sends | 95% upper bound ≤ 2% on sealed sends | 0 confirmed unsafe would-sends in ≥ 400 graded (bound ≈ 0.75%) |
| Coverage (reported, not a gate) | target ≥ 75% of right-answer-is-reply rows send; feeds the silence-budget call | silent and digest volume, reported against the silence budget (open call 2) |
| Time | within the budget in section 4 | timeout rate < 2% |
Honest sizing: the key is for design and catching regressions. It cannot show a 0.1% error rate; that needs about 3,000 graded sends and builds up after launch. It is never a launch gate.
Rollback: flip the setting back. The check can only block, so rolling back makes Clara louder and never opens a new way to send. A timeout never goes back to meaning "send".
One taxonomy module (the topic list, definitions, the topic + subtopic → module map, the risk flags) is read by both the page labeller and the draft check. The old reply-topic classifier is retired into the draft check. The page labeller stays separate and never decides a reply. When the draft check and the page disagree on the same message, that is logged as free test data.
| Page today (8) | Reply check today (5) | One list | Module |
|---|---|---|---|
| leasing | other | leasing | Leasing |
| renewal | renewal | renewal | Renewals |
| maintenance | maintenance | maintenance | Maintenance |
| turnover | other | turnover | proposed: Renewals (notice to move out) · Maintenance (turnover work) |
| billing · balance, due date, autopay, portal, deposit | resident_services | resident_services | Resident Services |
| billing · delinquency, late fee, payment plan, owed | collections | collections | Collections |
| resident_inquiry | resident_services | resident_services | Resident Services |
| vendor | other | vendor | Maintenance |
| none | other | general / none | who's writing decides |
Page chips: Haiku labels within the 20-second bar, then an Opus 5.5 pass relabels inside the existing stamp workflow through the one canonical writer (never a new classification caller, and a relabel never reverts to "Classifying"), on untagged and reconsidered rows only; the record keeps both. Fable's pick: the only option that keeps Opus as the standard and the 20-second bar.
Each one is ≤ 300 lines, branched from main, and works on its own. Order corrected by Fable: thread state is recorded before anything reads it, the combine step gets its own slice, and the block-count page lands before any customer is switched on.
| # | Slice | Owner | Needs | Done when |
|---|---|---|---|---|
| A1a | Every failure path of today's reply check (timeout, error, no verdict) holds. Time limit 6 s → 20 s. (An interim on today's check; in the new pipeline a timeout means silent + digest.) | Swarm drafts, Fede merges (send door) | none, now | A test that forces a timeout holds; the 344 rows replayed on main's prompt = the before-number |
| A1b | Reply check moves to Opus 5.5 low | Swarm | A1a | Replay again = the after-number; timeout rate < 2% |
| A2 | Keyword risk floor writes flags; the door reads them (silent + tracked ask) | Swarm (flags) + Fede (door), same week | none | 0 misses on the money development rows; false alarms on 150 random rows reported |
| C1 | Who's writing and thread state recorded on every turn, no behavior change | Swarm | none | Replay identical to today's |
| A3 | shouldSpeak goes silent on thread state | Fede | C1 | Constants chosen from the 344 replay; identical elsewhere |
| B1 | The taxonomy module; the reply check reads it | Swarm | none | Evals green |
| B1b | The whitelist in the reply check, with who's writing as the fallback | Swarm + Fede (door) | B1 | Block rate on the 344 replay reported before it enforces |
| B2 | Answer-key harness: the format, loader, upper bounds, counts per check, CI gate on the synthetic rows | Swarm | none | CI runs it nightly |
| B3 | Labelling: 100-row pilot, then the full round | Swarm + Gera and Fede | B2 | κ bars met |
| C2 | The writer returns its own read, recorded only | Fede | none | Replay identical to today's |
| D0 | The combine step and the decision record, recorded only | Swarm | C1 | Every turn has one record |
| D1 | Draft check in shadow, whole-pipeline shadow | Swarm; Fede approves where it plugs in | D0, C2, Preview records | Would-have decision on every text and email turn |
| D2 | Bench the arms on the key: today; today + A1 to A3 + B1b; + draft check | Swarm | B3, D1 | The release bars in section 6 |
| E1 | Block counts per check on a page for Gera; digest feed | Swarm | D0 | Live before D3 |
| D3 | Enforce at one customer | Swarm; a founder switches it on | D2, E1, shadow bars | A week of clean daily block reviews |
| D4 | Enforce everywhere; delete the old reply check and the rollout setting | Swarm | D3 | Two clean weeks |
| E2 | Page chips on the one list; collections split visible; kind and sender behind the (i); Opus relabel pass inside the stamp workflow | Swarm | B1 | Page agreement measured on the key |
Settled here, not open: an Off module stays silent and goes to the digest (Gera's value rule already decides it: "we don't handle that here" adds nothing). Collections is never Live until counsel answers; the action is sending counsel the per-customer question now. The seam for the draft check is an engineering sign-off for Fede on D1, not a product call.
Earlier pages: Classifier Plan v2 · Classifier: Fail Closed (audit and literature) · Clara Reply Scenarios (the 40 cases to vote on) · Fede's engine: Clara, the new hire. Evidence: the 10-27 code and production audit; Fable's and Astra's critiques of v1; the turn study (639 scrubbed moments, three blind labellers); the literature review (selective risk, conformal bounds, why models don't abstain, multi-intent). Code checked on main on Oct 29: classify-reply-topic.ts, supervised-reply-rule.ts, should-speak.ts, model-paths.ts.