The Honesty Layer — History, Bugs, Gaps & Redesign
A deep inspection of the layer that stops Clara from saying things that aren't true — how it came about, what happened the week of Aug 13–16, where it stands today, the bugs still in it, how it compares to the research and to competitors, and a proposed long-term design that fits "Clara is the system of work." Two agent fleets did the work on 2026-08-16: 17 agents read code, git history, PR threads and Slack; 14 did independent web research. Every code claim below was verified against the repo; the one verifier that disagreed was itself checked and discarded.
1. How it came about
2. The August saga
3. Where the layer stands today
4. Bugs found in the current code
5. Gaps vs. research, competitors, best practice
6. Where we're ahead
7. Independent research: confirms, corrects, adds
8. Proposed long-term design
9. Decisions for the founders
10. Migration path & proofs
Method & caveats
TL;DR
- What we did right: the idea (never assert an action or number without evidence), the pre-merge replay gate that came out of the RCA, the fail-open/fail-closed reasoning per failure mode, and the "make it true, don't block" pattern that quietly worked.
- What's structurally wrong: guards key on turn-scoped tool flags rather than real state; hard-block-or-silence with no "hold for a human" tier; six families each with their own snapshot of the same fact; English-only regexes; no coverage on voice or maintenance; the guard in scope can't be replay-tested; no CI gate — the merge rule is human-enforced.
- What to do: one checkpoint for every outbound message; one shared "what is true right now" ledger; a three-way verdict (pass / hold for review / block) with repair before refuse; a small trajectory-aware judge with a narrow trigger and a gold set behind it; voice reconciled after the call against the same ledger. Four increments, each with a replay-number proof. Four decisions (now H1–H4 on the architecture doc) need your call.
1. How it came about (April → August 2026)
The layer was born on 2026-04-27 after a live test in which Clara texted a tenant "Done — I'll flag this for PM" without firing the escalation tool — partly because, for most renewal states, she didn't even have that tool available. The founding framing, still in the file's header: "Lease renewals are legal contracts. A tenant who signs based on a number Clara invented exposes us to liability." Two checks shipped: every dollar amount and lease term in Clara's text must match the prepared offer, and every "I did X" claim must have a matching tool call in the same turn. On a trip, the reply is replaced with a safe escalation acknowledgement and the original is kept for audit.
| Date | Change | Why (the incident) | What it taught |
|---|---|---|---|
| Apr 27 | Born: offer-number check + action-claim check, hard-replace on trip | "Done — I'll flag this" with no tool fired | Right idea; scope far too wide (every message while a renewal was "active") |
| May 6 | Accept month-to-month rent + premium as a valid total | Real tenant test tripped on a legitimate MTM number | Numeric matching too literal |
| May 27 | The false-positive reckoning: gated to open renewals only; "all set / good to go / done" removed as claims; regression table seeded from real blocked messages | Sweep of 5,036 real traces: 34 blocks, ~79% false positives — a tenant who had already signed kept tripping it on "take care!" for 60 days | Guard scope must be as narrow as the risk, not as wide as the state machine |
| Jun 13 | Sibling: runaway ceiling (stop after 40 consecutive auto-replies) | Two AIs texting each other in a loop | Loop-shape failures need behavior detection, not lists |
| Jul 8 | Single canonical conversation writer (ADR-0079) — the plumbing every guard's audit row rides on | — | — |
| Jul 13–15 | Generalized to tour times; then cross-turn detection of a dropped reschedule (CAM-F-001) | Clara said "see you at 1:45" but never moved the tour; the reminder fired for the wrong time | Same-turn evidence isn't enough — promises span turns. Bot review caught renewal phrases false-blocking tour turns in the same PR |
| Jul 30 | Sibling: farewell-loop breaker. Same day: fix — the guard had been reading tool names, not results, and running outside the loop so the set was empty on normal turns | 11 rounds of "Take care! 👋" with a plumber's auto-text; a true claim blocked on ordinary turns | "Did the tool fire" ≠ "did the tool succeed"; the bug sat live April→July, and the fixer initially missed its twin one function below |
| Aug 13–16 | The P8 saga (next section): new booking-honesty guard → three real customers hurt → full revert → mandatory replay gate | Voice invented "confirmed for Thursday at 9:30"; email invented an "earliest available slot" | See §2 |
Five lessons the history teaches, in the fleet's words: every widening of scope produced a false-positive incident; "did the tool fire" hides the real question for months; the multi-round bot review inside single PRs repeatedly caught what solo authorship missed; a guard that blocks a true statement is the "worse than not guarding" failure and it happened more than once; and by August the blast radius outgrew hand-verification, which is why governance (a merge gate) finally replaced fix-and-move-on.
2. The August saga (Aug 13–16) — what actually happened
What P8 was. A new, separate guard ("never fabricate confirmations or slots"): pure regex over Clara's draft, plus a set of booking-tool names that returned success this turn. If the draft sounded like a confirmation ("you're all set for Thursday at 2") and no booking tool had succeeded this turn, the entire reply was replaced with one of four canned texts — the one real customers received: "I'm not able to book or hold a time from here, so nothing is scheduled yet…" It never looked at the actual tour record. It never ran on voice (ElevenLabs owns that loop). It shipped on the evidence of a 36-row synthetic test that its own PR body said was "not fail-before evidence."
| When (MT) | What happened | Root cause |
|---|---|---|
| Aug 13, 10:22 PM | P8 ships. Its exemption for "caller already has a tour" is dead on arrival — the code reads a field the tool never produces (top-level vs. nested). | Unit tests hand-built the guard's inputs instead of using real tool-result shapes |
| Aug 14, 9:01 AM | Real customer #1: a Camellia prospect asks about her real virtual tour an hour away, is told "nothing is scheduled yet." (She toured and applied anyway.) | The dead exemption above |
| Aug 14, 1:52 PM | A separate PR puts a TOUR STATE block into SMS/email prompts (so Clara restates real tours) — but nobody wires it into the guard's evidence, so it becomes a wider source of over-blocking for 15 hours. | Two features shipped independently on the same topic; the guard's own docblock had predicted this interaction |
| Aug 14, 8:23 PM | Fix lands for both known bugs; blast radius so far: 42 blocks, 41 harness traffic, 1 real caller. | — |
| Aug 14, 11:00 PM | Grounded-claims ledger + escalation-claims guard ships ("make it true, don't block": if Clara says "I've passed this to the team" with no record behind it, fire the real escalation and let the sentence stand). Its own pre-merge replay of real history: 0/166 false positives on real people. | — |
| Aug 15, 10:59 & 11:19 AM | Real customers #2 and #3: a correct pricing/availability answer, and "I have to cancel my appointment today" from someone with a real 2 PM tour — both replaced with "nothing is scheduled yet." | The availability/slots tools' real results carry no ok/success field, so the guard could never mark them succeeded even when they ran — a truthful answer naming times reads as an ungrounded slot claim. Never fixed in-tree; the family was reverted first. |
| Aug 15, ~11:35 AM | Fede orders the rollback. 1:24 PM: the ledger reverted (most recent change, suspected). 2:31 PM: the whole P8 family reverted. The revert PR's evidence: replay of 177 real Camellia turns since Jul 15 — 28 would-block (15.8%), 0 fabrications; Camellia since launch: 1,107 turns, 93 would-block (8.4%), 0 true catches. | — |
| Aug 16, 3:19 PM | Guard-change merge gate lands: any PR touching a guard that replaces customer text must replay that guard over ≥30 days of real conversations and hand-label every real-person would-block; unit tests may not hand-build guard inputs. Journey harness now records guard events; synthetic traffic no longer pages humans. | — |
What the team concluded (from the PR threads): the idea wasn't wrong; the process was — a guard that can replace a customer's reply shipped on synthetic evidence. And the ledger was collateral: its own replay was clean, it was reverted because it was the most recent change to shared plumbing. As of today production has no structural guard against Clara fabricating a booking confirmation beyond the prompt rule and the older tool-keyed guards.
3. Where the layer stands today (main, 2026-08-16)
- Voice (ElevenLabs) is outside this entirely — PropFlow supplies a prompt and tool webhooks; there is no pre-send seam. Only prompt rules apply.
- Everything is deterministic regex/state comparison — no LLM judge in the runtime path. The one trajectory-aware LLM reviewer in the codebase (daily reviewer + adversarial verifier, fed tool ground truth) is dormant with zero callers. The real-time grader is a text-only loop detector.
- Verdicts are binary: silent or replace. No "hold for a human" tier;
needsApprovalappears nowhere in the codebase. - Six families, six snapshots. Each guard is threaded its own view of the same fact (offer snapshot, tour snapshot, staged charges…) — the drift that bit P8 for 15 hours is structural.
- Measurement: the replay harness explicitly skips the hallucination guard and all three turnover guards (their inputs need live DB snapshots a trace doesn't carry). None of the eval/replay tooling is a required CI check (required checks are Build / Type Check / Unit Tests / review). The Grading Playground is live and is the right gold-set primitive, but nothing calibrates a judge against it yet.
4. Bugs found in the current code (adversarial review, verified)
| Sev | Finding | Concrete failure |
|---|---|---|
| P0 | Real phone calls never pass through any of these guards | On a call, Clara states a wrong tour time or rent; nothing checks it. The docblock's "runs before voice TTS" is true only for the typed-mode test harness. Confirmed by my own grep: the only caller is the SMS/email loop. |
| P0 | No honesty guard on ordinary maintenance conversations | Tenant: "My AC stopped working." Clara (tool never fired): "I've dispatched a technician — Tuesday 2–4 PM." Nothing inspects it. Guards run only for renewals, active tours, or pending turnover approvals — not the majority of traffic. |
| P1 | Word-form or bare-digit numbers bypass the numeric check | "about twenty-four hundred a month for a year" against a 6-month $2,400 offer → no finding. The regex needs a literal $ and 4–6 digits (the comment says 3). |
| P1 | Legitimate counter-offer echo gets blocked | Tenant: "would you do $2,350?" Clara: "Got it — I'll flag $2,350 to my PM." Not within $5 of the offer → blocked and replaced with canned text; escalation fires twice. |
| P1 | Every phrase catalog is English-only | "Le avisé a mi equipo" / "Nos vemos a las 2pm" match nothing. We demonstrably serve Spanish-speaking tenants (a Spanish out-of-office fix merged this week). |
| P2 | Guard's tour snapshot keeps a cross-channel blindness the prompt fix removed | Prompt sees the tour; guard doesn't → false block on a truthful restatement. |
| P2 | Tour-time check compares time-of-day, never the date (self-documented) | Wrong day, right time ships clean. |
| P2 | "You're all set"/"confirmed" can capture an unrelated time in the same clause; dead emergency-valve env var | Untested false-positive shape; hygiene. |
Also verified: three test gaps (renewal-context invariant untested at the boundary; no stale-snapshot + grounded-prompt test; farewell list includes generic "ok/great/perfect"). Ruled out after checking: several suspected ordering bugs are fine.
5. Gaps vs. the research, competitors, and best practice
Compared against the Compass research brief, our own independent research (§7), and what Decagon, Sierra, Intercom Fin, Harvey and the property-management vendors publish.
| Practice | Us | Gap |
|---|---|---|
| Confidence-gated verdicts; hold-for-review instead of hard-block on any flag (Decagon "revises or escalates"; Sierra "subtle corrective action") | Missing | Every guard is silent-or-replace. Direct cause of P8's harm and the May 79%-FP sweep. |
| Blocking CI gate on guard/prompt changes with real-history replay | Written rule | The ≥30-day replay gate exists but is human-enforced; can't run in CI as written (needs prod DB creds). Second-order cause of P8. |
| The guard in scope is replay-testable | Missing | Replay harness skips the hallucination guard and the three turnover guards. |
| Voice output has an honesty backstop | Missing | Zero. And no vendor (ElevenLabs, Cekura, Hamming, Coval, Retell) publishes a method for checking a spoken confirmation against backend state — build, not buy. |
| One centralized, ID-addressable policy layer with telemetry per fire | Seed | A well-shaped registry exists with one caller and one policy (fair housing). 72 guard files around it. Telemetry is ad hoc per family. |
| Trajectory-aware judge; atomic claims vs. the complete record; not English-only | Missing | No LLM judge in the runtime path; the trajectory-aware reviewer is dormant; regexes English-only. |
| Real-time grader sees tool calls | Missing | Text-only loop detector. |
| Stale-data detection ("confidently wrong from an old PMS snapshot" ≠ invented — the Intercom Fin lesson) | Missing | Sync-age exists for ops dashboards only; nothing on the quoting path. |
| Tool-boundary validation (guard next to the side effect; needsApproval for high stakes) | Have (SMS/email) | Strongest thing in the stack on SMS. No general "high-stakes tool → hold" primitive; PMS writes get one narrow guard; tour-mutating tools none at the tool. |
| Human-labeled gold set + judge calibration | Primitive exists | Grading Playground is live and right; no judge on the other end yet; only conversations are fed, not renewal/maintenance judgments. |
| Scripted, pre-approved replies for fair-housing/legal topics (EliseAI practice; Funnel's trigger list) | Partial | Fair-housing policy exists as a rewrite/block rule on leasing text; not a scripted-reply lane. Worth checking against Funnel's list: lawyer, mold, safety, accommodation, angry sentiment, application denial, anything outside approved data. |
6. Where we're ahead of the brief
- Fail-open vs. fail-closed reasoned per failure mode — the gate fails safe when the gate itself breaks; a tour-state read fails open on a DB hiccup but fails closed on "definitively no tour." The brief doesn't address this at all.
- The merge gate methodology — every guard-touching PR replays ≥30 days of real conversations with every real-person would-block hand-labeled, and the harness refuses to pass with an unlabeled row. Stronger anti-gaming than a static gold set. (Gap: not CI-enforced.)
- "Make it true, don't block" — the escalation-claims pattern is the brief's own recommendation, built independently, and it worked (0/166). Nobody in the research has a documented production implementation of it — we do, in a reverted PR.
- The turnover edit-honesty guard's negation/exemption engine — closer to Harvey's atomic-claim-vs-complete-record model than anything else in the stack, hardened over four incident-driven review rounds.
- Guard cost/sustainability designed in (no-model-call alert paths; weekly-not-nightly for expensive checks); Sentry issue-collapsing by constant message; the multi-round bot review that repeatedly caught regressions before merge.
7. Independent research — what it confirms, corrects, and adds
Confirms: the layered, deterministic-first / judge-second architecture is real convergence (Decagon, Sierra, OpenAI Agents SDK all say it in primary sources); trajectory-aware judging is the documented fix for context-starved false positives; atomic claims must be checked against the complete record or you manufacture false positives; tool-boundary guards and a human-approval pause are distinct, documented mechanisms.
Corrects the Compass brief: EliseAI's ~$2.2B is Aug 2025, not 2026; Braintrust's $800M valuation is press-reported, not confirmed by Braintrust/ICONIQ; ChainPoll's "23% improvement" couldn't be located in the paper (cite AUROC 0.781 vs 0.673 instead); the cited PPI++ paper wasn't fetchable — the verified one is arXiv:2601.05420. Most important: "give the judge more context" is not monotonic — Cleanlab found detection quality can fall as raw context grows; pass structured, labeled, complete context, not maximal. And "Catching One in Five" is more skeptical than summarized: automated judging is "a regression floor, not a substitute for human review."
Adds what the brief missed:
- Fair-housing exposure is live legal precedent for exactly Clara's use case — HUD's May 2024 guidance (technology-agnostic; providers remain liable for third-party AI), Open Communities v. Harbor Group (a chatbot auto-replied that vouchers weren't accepted at 100+ properties), Louis v. SafeRent ($2.275M), DOJ v. RealPage. Nonprofit testers now run automated protected-class inquiries against leasing bots at scale. Peer practice: EliseAI routes compliance answers to pre-approved scripted replies; Zuma recommends quarterly disparate-impact review.
- Voice: the only concrete latency budget found is Hamming's (P50 <1.3s / P95 <3.5s time-to-first-audio; tool calls 500–1500ms). No vendor verifies spoken confirmations against system state — a build item and a differentiator.
- Online detectors: Google Vertex Check Grounding is engineered for <500ms per turn; Galileo Luna-2 ~152ms on GPU; HHEM-2.1 ~1.5s/2K tokens CPU; AWS/Azure publish no latency. Only 3 of 7 systems publish any.
- Incident lessons: Air Canada held liable for a policy its bot invented (the "separate legal entity" defense rejected); Cursor's fix was labeling AI replies as AI; Klarna reversed an all-AI rollout. Cross-cutting: fabricated commitments are visible and litigable; over-blocking is invisible and just causes churn — track a false-refusal rate on known-benign traffic with the same seriousness as catch rate.
- Confidence-gated automation has numbers now: arXiv:2604.23855 — 45% full automation with 39% less human handling at fixed precision.
Open in the evidence: no controlled regenerate-vs-block cost/latency study anywhere; no third-party hallucination or fair-housing benchmark for any PM vendor — publishing a real one would be a first in the vertical.
8. Proposed long-term design
Two independent design passes (one "policy engine first", one "supervisor first") converged on the same skeleton; the synthesis re-verified every load-bearing repo claim. This is a redesign that fits the vision — one brain, PMS as system of record, Clara as system of work, policy that compounds — not a patch on P8.
Voice is architecturally different, not exempt: post-call (or near-real-time async) reconciliation of the transcript against the same ledger, corrected via the existing callback/SMS-follow-up path — never inline blocking of a spoken sentence (pending Decision 3's spike).
Settled points: keep the registry shape that already exists for fair housing (don't rewrite it); one ledger, not six snapshots; binary per-claim verdicts; repair before refuse, refuse before silence; thresholds as data (Arms-tab pattern), logic as code — no DSL; every guard trip becomes a policy-learning event feeding the Playground so a human's true/false label tunes thresholds and promotes recurring patterns into the cheap deterministic layer; two-key arms and consent gates stay orthogonal and untouched.
Keep unchanged: the offer-number math (incl. MTM+premium), the turnover edit-honesty exemption engine, farewell-loop and runaway ceiling (pre-generation, outside the registry), the replay-and-label merge-gate methodology (extended to judge changes), the blocked-draft marker, the two-key arms. Retire: P8's specific design (already gone), per-guard snapshot threading, the standalone per-guard telemetry file, fair-housing-only framing of the registry, the dead env valve, hard-block-as-only-verdict.
9. Decisions for the founders
Decision 1 — How often does the judge run? (now H1)
A. Residual only — the judge fires only when a deterministic policy is unsure. B. Every claim-bearing turn — also when the deterministic layer passes but the turn is in a claim-bearing capability; much higher cost and calibration burden. C. Start at A, widen to B once the gold set proves precision RECOMMENDED.
Decision 2 — Does the gate cover tool execution, or text only? (now H2)
A. Text only — gate the composed reply before send. B. Text + tool execution, scoped into increment 2 RECOMMENDED — a checkToolCall() entry point gates tools flagged "requires grounding" before they run. C. Text now, tool gating as a later increment.
Decision 3 — How hard do we chase mid-call voice correction? (now H3)
A. Post-call only, commit now. B. Time-box a spike against the live ElevenLabs agent first RECOMMENDED — check whether EL exposes a server-side hook for mid-call correction. C. Defer voice past increments 1–4.
Decision 4 — Build the credentialed nightly replay job now? (now H4)
A. Yes, inside increment 4. B. No — a fifth project after 1–4 RECOMMENDED — it's a multi-week infra project (prod-DB credentials are the structural blocker). C. Never — the written rule is enough.
10. Migration path — four increments, each with a proof
Addendum 2026-08-19 (owner direction, from the application-link incident): Increment 0 — the outbound-composition contract. The PolicyGate inspects "the composed reply", but the agent loop today has no contract for what the composed reply IS: text the model writes in a round where it also calls a tool is silently discarded (only the final round ships; the stash is used only when the final round is empty). A prospect lost a complete, correct answer to this on Aug 19 — the gate never saw it. Before (or with) increment 1: define reply assembly as one deterministic, unit-pinned seam for all channels — every customer-visible text block across rounds is part of the draft unless marked internal; the trace records what each round contributed; the model's prompt states the same contract. Owner decisions on the incident page: the interim is a prompt rule only (D2-B, shipping); no new standalone guards (D3-A) — claims-vs-reality checking belongs HERE, in the one checkpoint. Proof: the red repro test on fede/loop-text-drop-repro green, plus the same ≥30-day replay + golden-eval gate as increment 1.
- Re-land the shared ledger + registry skeleton, zero behavior change. The grounded-claims ledger must be re-authored (it was reverted as collateral). Migrate the five live guards onto the existing registry shape with check logic byte-identical. Register all five in the replay harness (today it skips four of them). Proof: ≥30-day Camellia replay pre- and post-migration shows 0 verdict drift.
- Three-way verdict + tool-execution gate (Decision 2). Add pass / hold-for-review / block with a confidence field; wire "hold" to the existing anomaly-review hold; move thresholds to config rows. Proof: replay the literal 177-turn P8 corpus against a reconstructed booking policy on the new shape with the field-shape bugs fixed: 0 of the 3 real incidents hard-block; any genuine fabrication in history still blocks.
- The judge, narrow trigger (Decision 1), and the gold set. ≥200 cases from Playground verdicts tagged "never invent a fact" + the 177- and 166-turn known corpora; subscription-powered meta-eval as a CI-blocking check on judge PRs. Proof: ≥80% catch on the two named blind spots (word-form numbers, Spanish claims) at <5% false positives.
- Voice (Decision 3) + retire the scatter. Spike, then build mid-call or post-call reconciliation against the ledger; widen the live-effect entity map to tours/renewals; wrap the gate so future Temporal freeform sends inherit it. Proof: a Willows-bench call with an injected wrong tour time is caught and corrected within budget (today: 0% coverage), plus a repo report showing 100% of customer-facing send sites resolve to the one gate.
Risks named honestly: re-landing the ledger must re-derive correctness, not assume the old review covers new call sites; the judge is a new probabilistic component in a system whose worst incidents came from false confidence in deterministic-feeling checks — hence narrow-trigger-first; voice stays the least-covered channel through increment 3; tool-execution gating touches shared infrastructure; config-as-data needs an audit log on threshold writes; PMS-agnosticism is aspirational until the adapter layer is real.
Method & caveats
Code fleet: 17 agents (history reader, architecture reader, saga reconstructor with Slack + PR threads, adversarial bug hunter, evals/telemetry mapper, gap analyst, two designers, synthesizer, six adversarial verifiers) at repo HEAD 9fa94067a4, plus a separate stack-wide guardrail verifier. Research fleet: 10 topic researchers + 3 fact-checkers + 1 synthesizer, primary sources preferred, vendor-reported vs. independent labeled. The RCA doc and the Option-B decision doc on this site returned 403 to the agents (Cloudflare Access), so every RCA number here is as quoted in PR bodies. Verifier #1's "file doesn't exist" verdict was a bad search — the guard's existence and its single caller were re-checked directly. Related pages: Vision v4 vs. Reality, Clara as a Coworker.