0151 — What a resident's message IS comes before what trade it names: a question or an FYI never files a work order
- Status: Accepted (Gera, 2026-10-30, in chat: "we shouldn't claim we created work orders when we didn't … They weren't asking for a work order, they were asking for details. Just because it's rated pest control doesn't mean we need a work order. Sometimes they just want a question. The right response would have been to ask the team about the pest control scheduling." And: "I don't want a quick fix, I want durable practices … we have to think big picture: what is the right strategy? We don't just throw work orders and call it done.")
- Date: 2026-10-30
- Deciders: Gera
- Incident: a resident at a customer property texted, 2026-10-30 01:20Z, that she had found a few cockroaches, killed them and made sure there were no more, and asked whether the grounds get sprayed this season. The text-intake rung (
inbound-dispatcher.ts→classifyMaintenanceIntake→createWorkOrderFromIntake,createdTrigger: text_intake_sweep) classified itnew, filed a general work order whose own dispatch summary read "Requesting information on whether the property receives seasonal pest control spraying", auto-assigned the preferred vendor and synced it to the PMS — seventeen seconds before Clara's turn ran. Clara's draft ("the work order for the roaches is in with the vendor, and the number will come through by text") was therefore a TRUE statement about a work order that should never have existed, grounded by the intake'swork_order_createdentry and scripted by the prompt's own number-will-follow sentence. Eleven minutes later the PMS-number relay told her the order was in; two minutes after that she wrote back "We don't need a work order for cockroaches, I was just wondering if you spray around the property", and Clara closed the order with a vendor stand-down. Related: ADR-0054 (the intake rung), ADR-0149 (emergencies).
Context
The intake classifier had five verdicts — new, existing, troubleshoot, ask, reply — and four of them assert a repair. reply was defined as "conversational — thanks, confirmations, questions not about any work order, smalltalk", which reads as small talk, not as a question about pest control. So a message that named a trade and reported a sighting was new by default, whatever it asked for. The resolver, the act-rule (intakeCreateAction), the voice post-call sweep (planVoicePostCallSweep) and the Clara Bench door all inherited that default: the only way for nothing to be filed was for the classifier to call a pest question "smalltalk".
Two durable facts made this worse than one wrong ticket. First, on text Clara holds no create_work_order tool (ADR-0054 slice 4b-2), so the intake rung IS the filing decision; nothing downstream can un-file. Second, every honesty guard on Clara's reply grounds on the intake's own completed action, so once the rung has filed, "your work order is in with the vendor" is a fact and the guards correctly let it through. The decision has to be made where the filing is made.
Decision
A resident's message is classified by what it IS before by what trade it names. The intake rung answers that question first, on every channel it classifies (text, email, the voice post-call sweep) and in the Clara Bench door, with ONE classifier (intake-classify.ts) and ONE act-rule. A trade word — pests, a leak, heat, an outlet, a drain — is never the verdict on its own.
| What the message IS | How you tell | Intake verdict | Work order? | Clara's reply |
|---|---|---|---|---|
| Emergency | ADR-0149's definition; priority: emergency |
new (priority emergency) / the life-safety preempt |
Yes, in every mode | the emergency rules; never "question" or "fyi" |
| Repair request | an UNRESOLVED fault at the home or the common areas that the resident wants acted on, or a tech asked for — in any phrasing, "just so you know, the hallway light is out" included | new / existing / troubleshoot / ask |
Yes (after a troubleshoot step or one photo ask where those apply) | confirms what the record says, by the number, promises no time (RULE 7.8) |
| Question | asks HOW, WHEN or WHETHER the property handles a trade — a spraying schedule, who changes the filters, what is covered, when the gutters get done, a policy — and reports no unresolved fault | question → question_not_repair |
Never | answers from property knowledge if it is on file; otherwise forward_to_property_manager and nothing about it in her own words (the silent hand-off, #23508); never a work order, a vendor or a number |
| FYI | tells us something with no action asked and no unresolved fault — fixed it themselves, resolved on its own, a courtesy heads-up | fyi → fyi_not_repair |
Never | a brief acknowledgement; a forward only if the team should know |
Mixed messages resolve by the fault, not the trade: a fault the resident already resolved plus a question is a question (the incident); a fault still present plus a question is a repair (and the question is answered or forwarded on the same turn).
What this commits the code to:
questionandfyiare first-class targets ofIntakeClassification, in the prompt, the structured-output schema and the parser; the resolver passes them through asquestion_not_repair/fyi_not_repair, drops any order id the model typed, and never routes them through the open-order guards (they are neither a continuation to attach nor a new issue to de-duplicate).- The act-rule files only
new— unchanged, now pinned for the two new targets. The dispatcher hands Clara an entry that says in as many words that nothing was filed and none will be (intakeQuestionEntry,intakeFyiEntry), because the one thing that turn must not do is tell the resident a work order exists. - The voice post-call sweep and the Clara Bench door take the same verdicts to the same outcome: a call cannot file what a text would not.
- Neither is a "maintenance matter" (
isMaintenanceMatter): at a Maintenance-Off building a trade question is answered or forwarded like any other question, never met with "maintenance is not handled here". Neither carries a module on the reply (CLARA_REPLY_MODULE_BY_REASON): the module switch governs acts, and these turns perform none. - The dispatch decision record carries the verdict (
question_not_repair,fyi_not_repair, targetsquestion/fyi), so the rate of each is countable from real traffic.
A piece of code violates this ADR if any intake path files a work order from a verdict other than new; if a trade word, a category or a priority decides a filing without a fault to act on; if a question or an FYI is coerced into new, existing or ask by a resolver branch; or if Clara is handed a completed action for such a turn that lets her describe a filing.
Consequences
- The classifier's prompt gains two verdicts and one rule; the proof is
scripts/eval-maintenance-intake-create-subscription.ts(MESSAGE_KIND_CASES): question and FYI shapes across pest, HVAC, plumbing, electrical, appliance and general upkeep assert0orders and forbidnew_issue, and fail onorigin/main's classifier (the file swap named in that script's header); repair controls across the same trades still file. The rows and their sample counts ride in the PR body. - A resident who asks a question gets an answer or the team, not a vendor at the door. A resident who still has the fault still gets the order; the controls pin that the rule cannot be satisfied by filing less.
- Follow-ups outside this ADR: the honesty guard on Clara's filing claims runs only on renewal threads and misses the "is in with the vendor" shape (its own slice, under the guard-change gate); the intake create path does not point the conversation at the order it filed (
conversation.workOrderIdstayed null whileworkOrder.conversationIdwas set — its own small PR).
Alternatives considered
- Sharpen the
replydefinition instead of adding targets. Rejected: the decision would stay invisible — a question would be recorded asreply_passthrough, indistinguishable from "thanks", so nobody could count how often residents ask rather than report, and the voice sweep'sdefault:arm would still read it as a conversational call rather than a decision. - Make Clara's reply guard the fix (hold any "your work order is in" that nothing backs). Rejected as the fix, kept as a safety net: on this incident the claim WAS backed. The guard protects against a different failure (a claim with no filing) and never un-files an order.
- Decide by category (pest questions never file, pest reports always do). Rejected: that is the "just because it's rated pest control" reasoning in reverse. The kind of message decides, for every trade; the trade only triages a repair once it is one.