Merged into the PropFlow architecture source of truth (2026-08-19) — that page now carries the distilled state of this investigation (§4.5, "The honesty layer") and the four open decisions below live there instead. This page remains the full inspection record. Read it there →
Full inspection record · merged 2026-08-19

The Honesty Layer — History, Bugs, Gaps & Redesign

Update 2026-08-17: the gate was built and measured on 30 days of real traffic overnight — numbers for every decision below are at Honesty Gate — Overnight Numbers. Decision 3 (voice) is resolved: post-call only, zero added call latency.

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.

In one paragraph. We built a real safety net in April because Clara once told a tenant "Done — I'll flag this for the PM" without actually doing it. It has grown into six guard families over four months and has caught real problems. But it has a structural weak spot: it can only ask "did a tool fire this turn?", never "is this actually true right now?" — and when it's unsure it doesn't hesitate, it replaces Clara's whole reply with canned text. That combination is what turned three correct answers to real Camellia customers into "nothing is scheduled yet" on Aug 14–15 and forced the rollback. Today, phone calls have zero output-side protection, ordinary maintenance conversations have none, and the guard we're inspecting can't be replay-tested at all. The industry has converged on the fix — one checkpoint, deterministic checks first, a judge that sees the whole picture, and repair or hold, don't hard-block — and our own week is the cleanest evidence for it: the guard that repaired ("make it true") had 0 false positives on 166 real people; the guard that blocked had 0 true catches out of 28.
TL;DR
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

6guard families live today (renewal numbers, tour times, 3× turnover, loop breakers)
0output checks on real phone calls
0 / 28true catches of the reverted booking guard on real people (28 false positives)
0 / 166false positives of the "make it true" escalation guard on real people
72guard/gate files; one registry, one caller, one policy

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.

DateChangeWhy (the incident)What it taught
Apr 27Born: offer-number check + action-claim check, hard-replace on trip"Done — I'll flag this" with no tool firedRight idea; scope far too wide (every message while a renewal was "active")
May 6Accept month-to-month rent + premium as a valid totalReal tenant test tripped on a legitimate MTM numberNumeric matching too literal
May 27The false-positive reckoning: gated to open renewals only; "all set / good to go / done" removed as claims; regression table seeded from real blocked messagesSweep of 5,036 real traces: 34 blocks, ~79% false positives — a tenant who had already signed kept tripping it on "take care!" for 60 daysGuard scope must be as narrow as the risk, not as wide as the state machine
Jun 13Sibling: runaway ceiling (stop after 40 consecutive auto-replies)Two AIs texting each other in a loopLoop-shape failures need behavior detection, not lists
Jul 8Single canonical conversation writer (ADR-0079) — the plumbing every guard's audit row rides on
Jul 13–15Generalized 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 timeSame-turn evidence isn't enough — promises span turns. Bot review caught renewal phrases false-blocking tour turns in the same PR
Jul 30Sibling: 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 turns11 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–16The P8 saga (next section): new booking-honesty guard → three real customers hurt → full revert → mandatory replay gateVoice 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 happenedRoot cause
Aug 13, 10:22 PMP8 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 AMReal 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 PMA 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 PMFix lands for both known bugs; blast radius so far: 42 blocks, 41 harness traffic, 1 real caller.
Aug 14, 11:00 PMGrounded-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 AMReal 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 AMFede 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 PMGuard-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.

The natural experiment. Same team, same week, same incident family, two designs. The blocking guard: 28 real fires, 0 true positives. The repairing guard: 166 real people, 0 false positives. That is exactly what the research brief predicts (context-starved judge + hard-block-on-flag + no pre-ship gold set = customer-visible harm), and it's ours, not a vendor's.

3. Where the layer stands today (main, 2026-08-16)

Clara drafts a replySMS / email / dashboard loop only
PII strip
Guardshallucination guard (offer $ + term, action claims, tour time) — only if an open renewal or active tour is in scope · 3 turnover guards — only if a turnover approval is pending · fair-housing policy (leasing text) · anomaly review gate
On triphard-replace or block; original kept as a marked "blocked draft"; Sentry breadcrumb
Send toolSMS: 8 ordered checks incl. TCPA/STOP + rate limit · email: suppression

4. Bugs found in the current code (adversarial review, verified)

SevFindingConcrete failure
P0Real phone calls never pass through any of these guardsOn 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.
P0No honesty guard on ordinary maintenance conversationsTenant: "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.
P1Word-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).
P1Legitimate counter-offer echo gets blockedTenant: "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.
P1Every 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).
P2Guard's tour snapshot keeps a cross-channel blindness the prompt fix removedPrompt sees the tour; guard doesn't → false block on a truthful restatement.
P2Tour-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 varUntested 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.

PracticeUsGap
Confidence-gated verdicts; hold-for-review instead of hard-block on any flag (Decagon "revises or escalates"; Sierra "subtle corrective action")MissingEvery 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 replayWritten ruleThe ≥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-testableMissingReplay harness skips the hallucination guard and the three turnover guards.
Voice output has an honesty backstopMissingZero. 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 fireSeedA 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-onlyMissingNo LLM judge in the runtime path; the trajectory-aware reviewer is dormant; regexes English-only.
Real-time grader sees tool callsMissingText-only loop detector.
Stale-data detection ("confidently wrong from an old PMS snapshot" ≠ invented — the Intercom Fin lesson)MissingSync-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 calibrationPrimitive existsGrading 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)PartialFair-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

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:

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.

Any outbound textSMS · email · dashboard · future Temporal freeform
PolicyGate — the one checkpoint① deterministic policy registry (today's checks rehoused, logic unchanged, IDs + config-as-data + telemetry per fire) ② one shared grounding ledger read fresh through the PMS adapter ("what is true right now") ③ a small trajectory judge: atomic claims vs. the complete record, binary verdict, fast model, narrow trigger
Three-way verdictpass · repair (fire the tool that makes it true; 1 attempt) · hold for review (Grading Playground inbox, never silently replace) · block (only pre-tuned high-confidence deterministic hits → honest decline)
Send toolconsent/TCPA/rate-limit gates unchanged, run after honesty settles the text

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

These decisions moved. The four founder decisions that lived on this page (judge cadence, tool-execution gating, mid-call voice correction, the nightly replay job) are now answered as H1–H4 on the architecture source of truth, so there is one canonical answer store instead of two pages drifting out of sync. Vote or read the current state there → The option text below is preserved for context only; it is no longer where an answer is recorded.

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.

  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.
  2. 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.
  3. 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.
  4. 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.

PropFlow Docs