Chapter 144 · Interface and adoption
Designing the failure state
Six ways an answer does not arrive, each needing different words. The three-part structure that works, why 'I don't know' loses a user, and the measurement that made failure screens a priority.
8 min read
Failure states get designed last and seen most. Chapter 111 found 20.3% of Atlas's answers are refusals or near-refusals, and chapter 135 adds the degraded ones. A fifth of all interactions land on a screen that was designed in ten minutes.
The measurement that reorders the priority: Atlas's users who stopped using it after a poor experience had hit a refusal, not a wrong answer. A wrong answer is a problem someone reports. A bad refusal is a user concluding the system cannot help them, quietly, once.
Six failures, six screens
// Collapsing these into one "I can't help with that" is the default
// and it is the thing that loses people.
type Failure =
| 'not-in-corpus' // nothing relevant exists
| 'not-permitted' // it exists, they cannot see it
| 'not-authoritative' // another system owns the answer
| 'not-authorised' // the action is beyond their rights
| 'ambiguous' // several readings
| 'system' // degraded, timed out, erroredThe first five are chapter 111's refusal reasons, which exist as an enum precisely so the interface can say something specific. The sixth is operational and the only one that is genuinely the system's fault.
The three-part structure
// Every failure screen. The third part is the one most implementations
// omit, and it is the one that decides whether the person comes back.
<Failure>
<What>What happened, in one sentence, specific.</What>
<Why>Why, when it helps. Omitted when it does not.</Why>
<Next>What to do now. ⚠️ Never omitted.</Next>
</Failure>// not-in-corpus
<What>I could not find anything about Q3 freight surcharges in the
documents I have access to.</What>
<Why>I searched policies, contracts and supplier agreements.</Why>
<Next>Operations keeps surcharge schedules in the finance drive, which
I do not index. <Link>Ask the operations team ›</Link></Next>// not-permitted — the hardest to word, because it must not feel like
// an accusation and must not pretend the thing does not exist.
<What>There is an answer to this, and it is in a document I cannot show
you.</What>
<Why>The supplier margin schedule is restricted to Finance.</Why>
<Next>Your manager or Finance can share it.
<Link>Request access ›</Link></Next>// ambiguous — a question, not a failure, and the interface should not
// look like one.
<What>Two customers match "Northline".</What>
<Choices>
<Choice>Northline Supply — Ottawa, acct 4471</Choice>
<Choice>Northline Industrial — Calgary, acct 8820</Choice>
</Choices>Atlas measured the three-part structure against a single-sentence refusal:
| One sentence | Three parts | |
|---|---|---|
| User rephrased and retried | 52% | 23% |
| User took the suggested next step | — | 61% |
| Rated the interaction acceptable | 34% | 77% |
Halving the rephrase rate is the efficiency win. The 77% is the one that matters: a user who was declined and still found the interaction acceptable comes back.
Why "I don't know" loses a user
// The honest-sounding refusal that performs worst of all six wordings
// Atlas tested.
// ❌ "I don't have enough information to answer that."
//
// It is true, it is appropriately humble, and it tells the user
// nothing about whether to try a different phrasing, a different
// system, or a different person. They guess, guess wrong, and stop.// The repair is always the same: convert the system's internal state
// into the user's next action. The system knows which of the five
// reasons applies — chapter 111 made it an enum — and each reason maps
// to a different action.
const NEXT_STEP: Record<RefusalReason, (ctx: Ctx) => NextStep> = {
'not-in-corpus': (c) => ({ text: 'Try the finance drive', href: driveFor(c) }),
'not-permitted': (c) => ({ text: 'Request access', href: accessRequest(c) }),
'not-authoritative': (c) => ({ text: `Open ${c.owner}`, href: systemFor(c) }),
'not-authorised': (c) => ({ text: `Ask ${c.approver}`, href: mailto(c.approver) }),
ambiguous: (c) => ({ text: 'Pick one', choices: c.candidates }),
}The system failure, which is different
// The one case where the system is at fault, and the words should say so.
<What>Something went wrong on our side and I could not finish that.</What>
<Next>
<Button onClick={retry}>Try again</Button>
<Link>Search the documents directly ›</Link> {/* chapter 135 */}
</Next>
<RunId>Reference: 8f3a-2201</RunId>Three details:
// 1. Own it. "Something went wrong on our side" rather than "an error
// occurred", which reads as the user's fault or nobody's.
//
// 2. Offer the degraded path, not just a retry. Chapter 135 built
// retrieval-only, and a failure screen is exactly where it belongs.
//
// 3. Show the run id. Chapter 128 argued for it; this is where the
// user actually sees it, and a complaint arriving with it turns a
// twenty-minute search into a lookup.What never appears
const NEVER = [
'A stack trace, an error code, or a provider name.',
'An apology longer than one clause. It reads as evasion.',
'A suggestion to "try rephrasing" with no indication of how.',
'A retry button for a failure that will deterministically recur.',
'An empty state with no action at all.',
]The fourth is worth singling out. A retry button on a not-permitted refusal invites a user to click it repeatedly against a wall, and it is dishonest about what kind of failure it is. Retry belongs on system and nowhere else.
The empty first screen
// Not a failure, and it fails the same way: a new user opens the
// assistant and sees nothing. Chapter 141's starters live here, and
// this is the screen that decides whether they ever type anything.
<Empty>
<h2>Ask about orders, stock, policies and contracts.</h2>
<Starters />
<Scope>I can see: the policy library, supplier contracts, the orders
system, inventory. I cannot see: HR records, financial
reporting, anything in personal drives.</Scope>
</Empty>The Scope line is the highest-value sentence in the whole interface. It sets expectations before the first question rather than after the first refusal — which is chapter 147's entire subject, delivered in one paragraph on a screen everyone sees.
Every failure screen is a fork: the user tries something else, or they stop. The only variable you control is whether they can see what the something else is.
Key points
- A fifth of Atlas's interactions land on a failure or degraded screen. Those screens get designed last and seen most.
- Users who stopped using it had hit a refusal, not a wrong answer. A wrong answer gets reported; a bad refusal is a silent conclusion.
- Six failures need six screens. Collapsing them into "I can't help with that" is what loses people.
- Use three parts: what happened, why when it helps, and what to do now. The third is never omitted.
- The three-part structure halved the rephrase rate and took acceptability from 34% to 77%.
- "I don't have enough information" is true, humble and useless. It gives no basis for choosing what to try next.
- Map each refusal reason to a concrete next action. The system already knows which reason applies.
- For a system failure, own it in the words, offer the degraded path as well as a retry, and show the run id.
- Never show a retry button on a failure that will deterministically recur.
- Put the scope statement on the empty first screen. It sets expectations before the first question rather than after the first refusal.
Next: feedback: capturing it and actually using it — the loop that makes every earlier chapter improve.