Skip to content
KONIGI Patterns

AI Assistants / Limits and errors / Refusal

‹ 2 of 3 ›

Refusal

People need to understand why a request cannot be fulfilled and what they can do next.

Updated September 12, 2026

Problem

When an assistant declines a request, people need to know whether it lacks access, lacks information, or cannot fulfill the request under the product’s rules.

Solution

State the limit plainly and explain what the person can do next. For missing access or an unsupported task, name the capability that is unavailable. For missing knowledge, say what information is needed or offer a search if available.

When a product rule prevents an answer, give a brief explanation without lecturing. Complete the parts of the request that can be handled, and make any omissions clear.

Offer a way to report a refusal that seems mistaken. Keep alternatives relevant to the original request.

Use when

The assistant cannot fulfill a request because of a capability, knowledge, or policy limit.

Don’t use when

The request is answerable with a caveat. Reaching for a refusal where a hedged answer would serve is the habit that trains people to take their work somewhere with fewer rules.

Trade-offs

A brief message may leave questions unanswered, while a long explanation can feel patronizing. Alternatives can help but may not meet the original need. Refusal messages need to stay consistent with the product’s actual capabilities and rules.

Checklist

  • Can the viewer tell whether this is a capability, policy, or knowledge limit?
  • Is there an offer of the nearest thing the assistant can do?
  • How long is the message, and does it moralise?
  • Is partial compliance possible instead of a full decline?
  • Is there a way to report a wrong refusal, and does a human see it?
  • Is the false-positive rate measured at all?
  • Does the refusal contradict what the first-run state or the prompt starters promised?
  • Does it read the same way on the tenth occurrence as the first?
  • Is a policy decline visually distinguishable from an error?
  • Can the viewer tell whether rephrasing would help?

Compare

Claude tends toward explaining the boundary and offering an adjacent version of the request. The turn stays productive, and it runs long. ChatGPT favours shorter declines with a suggested reframing, so the next move is usually visible in the message itself. Gemini more often redirects to search for knowledge-limited cases, converting a decline into a handoff rather than an ending. Microsoft Copilot in an enterprise tenant has a fourth case the consumer products lack, where the refusal is about the viewer’s permissions rather than the model’s policy, and saying which one it is matters more there than anywhere else.

Generation error indicates a technical failure. Knowledge cutoff notice flags potentially outdated information. First-run state explains limits before people begin.

Prompt starters set expectations about supported requests. Usage meter handles limits on how much the assistant can be used.

Refusal anatomy Three declines separated by cause: a capability limit, a policy decision and a knowledge gap, each with a different sentence and a different offer, alongside a long moralising version shown as the failure case and a report control for wrong refusals. Cannot, will not, and does not know Cannot no access to that system. Paste it in? Will not outside what this product allows. Does not know past the cutoff. Search for it? length is the tell wrong refusal? people who hit one stop asking, so it never shows up in the logs 1 2 3 4 1 NAME THE CATEGORY Capability, policy and knowledge call for three different next moves. 2 OFFER THE NEAREST THING The offer is what turns a decline into a usable turn instead of an ending. 3 DO NOT MORALISE Restating the request back as sinister damages more trust than the decline does. 4 FALSE POSITIVES The most under-measured problem here. A report control is the cheap way to see it. Partial compliance beats a blanket decline. Answer the safe part and name what was withheld.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Cannot, will not, and does not know are three different sentences with three different offers. Length is the tell for the moralising version, and people who hit a wrong refusal stop asking, so it never shows up in the logs.

shadcn
npx shadcn@latest add button
Tokens
--card--card-foreground--muted-foreground--border--primary

Cannot

no access to that system.

Will not

outside what this product allows.

Does not know

past the cutoff.

Refusal.tsxA closed set of three causes, an offer that is required for two of them, a length guard, and a report control on every one.

import { Button } from "@/components/ui/button";

/**
 * Three declines with three causes, and the cause is the first thing the
 * viewer needs. A capability limit, a policy decision and a knowledge gap
 * call for different next moves, so the set is closed and the label is
 * derived from it rather than typed each time.
 */
export type RefusalKind = "cannot" | "will-not" | "does-not-know";

const KIND_LABEL: Record<RefusalKind, string> = {
  "cannot": "Cannot",
  "will-not": "Will not",
  "does-not-know": "Does not know",
};

/** The nearest thing the assistant can do instead. Paste it in, search for it. */
export type Offer = { label: string; onSelect: () => void };

/**
 * Length is the tell. A decline that runs past a sentence or two is almost
 * always a defensive one, so anything longer than this is flagged in
 * development rather than shipped as a paragraph.
 */
const MAX_REASON = 120;

type Props = {
  /** One plain sentence. No apology, no restating the request as sinister. */
  reason: string;
  /** Wrong refusals never show up in the logs, because people stop asking.
   *  A report control a human reads is the only cheap way to see them. */
  onReport: () => void;
} & (
  /** A capability or knowledge limit always has a nearest thing to offer. */
  | { kind: "cannot" | "does-not-know"; offer: Offer }
  /** A policy decline may have nothing to offer, and says so in one line. */
  | { kind: "will-not"; offer?: Offer }
);

export function Refusal({ kind, reason, offer, onReport }: Props) {
  if (reason.length > MAX_REASON) {
    console.warn(`Refusal: "${reason.slice(0, 40)}…" is ${reason.length} characters. Cut it to one sentence.`);
  }
  return (
    <div className="rounded-lg border bg-card p-3 text-card-foreground">
      <p className="text-[11px] uppercase tracking-wide text-muted-foreground">{KIND_LABEL[kind]}</p>
      <p className="mt-1.5 text-sm">
        {reason}
        {offer && (
          <>
            {" "}
            <Button variant="link" size="sm" className="h-auto p-0 text-sm" onClick={offer.onSelect}>{offer.label}</Button>
          </>
        )}
      </p>
      <Button variant="outline" size="sm" className="mt-3 h-6 text-[11px] font-normal text-muted-foreground" onClick={onReport}>
        wrong refusal?
      </Button>
    </div>
  );
}

demo.tsxHow it is called: one of each kind, with the offers and the report handler a real app would supply.

import { Refusal } from "./Refusal";

/**
 * The closed set, one of each. Cannot and does-not-know carry an offer;
 * the policy decline is one line and stops there. Every one has the report
 * control, because the wrong ones are the ones nobody measures.
 */
export default function Demo() {
  const report = (kind: string) => () => console.log("reported a wrong refusal:", kind);
  return (
    <div className="flex w-[320px] flex-col gap-3">
      <Refusal
        kind="cannot"
        reason="no access to that system."
        offer={{ label: "Paste it in?", onSelect: () => {} }}
        onReport={report("cannot")}
      />
      <Refusal
        kind="will-not"
        reason="outside what this product allows."
        onReport={report("will-not")}
      />
      <Refusal
        kind="does-not-know"
        reason="past the cutoff."
        offer={{ label: "Search for it?", onSelect: () => {} }}
        onReport={report("does-not-know")}
      />
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No screenshots yet for this pattern. Browse Products to see other examples.