Your WhatsApp AI Agent Works in a Demo. Here's What Breaks in Production. A developer at WAPGROWAI detailed the production-hardening work behind the company's WhatsApp AI agent, separating the cheap classification call that decides whether to answer from the model that writes replies. The agent labels each incoming message as in-scope, out-of-scope, or wants-human via a forced tool call at temperature 0, answers from a single business profile in the system prompt rather than a retrieval index, and rebuilds conversation history on every call. An AI agent on WhatsApp demos beautifully. You paste in a business description, ask "what time do you open?", and it answers in one friendly sentence. Then real customers arrive. They ask about a competitor's product, send voice notes, type "HUMAN " in capitals, fire three messages in five seconds, and reply to a three-day-old thread. The model provider has a bad afternoon. A customer asks the bot to ignore its instructions. This post goes through what we had to build around the model to run the AI agent in WAPGROWAI https://www.wpautoai.com/ 's WhatsApp inbox, and the questions we think every team should settle before turning one on. The TypeScript snippets are small, simplified versions of the patterns, and each was run against the cases it describes. The most important design decision: the model that writes answers should not be the one deciding whether to answer. Every incoming message first goes to a separate, cheap classification call that returns one of three labels: answer it, decline it, or hand it to a person. The labels come from a forced tool call with an enum, at temperature 0, so the result is never free text you have to parse: // scope.ts export type Scope = "in scope" | "out of scope" | "wants human"; // The model must call this tool; free text is never used as the label. export const LABEL TOOL = { type: "function", function: { name: "label message", parameters: { type: "object", properties: { scope: { type: "string", enum: "in scope", "out of scope", "wants human" } }, required: "scope" , }, }, }; type CallTool = system: string, user: string, tool: typeof LABEL TOOL = Promise<{ scope?: string } ; export const classifyScope = async message: string, businessName: string, callTool: CallTool : Promise