If-Else or Boundaries? BotSailor's developer advocates a layered architecture for chatbot logic, separating AI-driven language interpretation from deterministic business rules and backend data. The approach warns against both excessive if-else chains and over-reliance on LLMs for deterministic decisions, citing OWASP's guidance on excessive agency. The recommended model uses AI for parsing user input, structured validation, backend APIs for truth, and deterministic conditions for predictable paths, with human fallback for edge cases. Series: From Visual Flows to AI-Orchestrated Automation · Part 03 A chatbot rarely becomes difficult because of its first if statement. The problem starts later. A customer wants to track an order. Another asks about a refund. Someone enters an invalid order ID. The API times out. Another user phrases the same question in a completely different way. Someone else just wants to talk to a human. Soon, what started as a simple decision tree is handling language, validation, business rules, external data, exceptions, and customer experience at the same time. In the previous part of this series, “Where Developer Hours Disappear in Messaging Automation,” I looked at why repetitive conversation logic can consume more engineering time than expected. Working around messaging automation at BotSailor has made the next question equally interesting to me: Where should chatbot logic actually live? The answer is not necessarily more conditions. And it is not “let AI handle everything” either. The first approach is familiar: if intent === "track order" { // track the order } else if intent === "refund" { // handle refund } else if intent === "support" { // contact support } else { // fallback } Nothing is inherently wrong with this. The problem appears when one decision tree gradually becomes responsible for too many things: identifying intent, validating data, calling APIs, enforcing business rules, handling failures, and choosing what the customer sees next. The opposite approach sounds more modern: send everything to an LLM and let the model decide what happens. That solves some language problems, but introduces another architectural problem. A probabilistic language model should not automatically become the source of truth for an order status, payment state, refund eligibility, or another deterministic business decision. OWASP's guidance on Excessive Agency https://genai.owasp.org/ highlights a related risk: giving LLM systems too much functionality, permission, or autonomy can allow unexpected model behavior to produce unintended actions. Instead of choosing between if-else everywhere and AI everywhere , I think the better question is: What responsibility belongs to each layer? For many conversational workflows, this model is more useful: User language ↓ Structured input ↓ Validation ↓ Backend/API truth ↓ Deterministic decision ↓ Conversation response ↓ Human fallback Each layer has a different job. AI can interpret uncertain language. Structured input can collect the exact values the system needs. The backend can remain responsible for business truth. Deterministic conditions can control predictable conversation paths. Humans can handle cases that still require judgment. That separation matters more than whether the orchestration is written in JavaScript or represented visually. Suppose a customer enters: BS-1042 The chatbot should not ask an AI model: “Do you think this order has shipped?” It should ask the system that actually owns the order data. A simplified JavaScript request might look like this: js async function getOrderStatus orderId { const response = await fetch https://api.example.com/orders/${orderId} , { headers: { Authorization: Bearer ${process.env.API TOKEN} } } ; if response.ok { throw new Error "Order lookup failed" ; } return response.json ; } The service might return: { "order id": "BS-1042", "status": "shipped", "tracking url": "https://example.com/track/BS-1042" } Now we have something more useful than an AI guess: structured information from the system of record. The next decision can remain deterministic: switch order.status { case "shipped": return "show tracking"; case "pending": return "show pending message"; default: return "human handoff"; } This is where visual automation becomes interesting. In BotSailor, the same pattern can be represented using a User Input Flow , an HTTP API call, response mapping into custom fields, a Condition , and a human fallback. Conceptually: Track Order ↓ Collect order id ↓ HTTP API ↓ Map order status ↓ Condition / | \ / | \ Shipped Pending Unknown ↓ ↓ ↓ Tracking Waiting Human message message handoff The API still owns the truth. The visual workflow owns the conversation around that truth. A Condition can route the conversation based on the mapped value, while User Input Flow can collect and validate customer information before the API request. AI Reply can then be used where language is genuinely uncertain instead of asking AI to replace deterministic business rules. This is not really a competition between code and no-code. It is an ownership decision . Consider this rule: if order.status === "shipped" { sendTrackingLink ; } Should it stay in backend code? Sometimes, absolutely. If a rule controls pricing, refunds, fraud detection, permissions, financial transactions, or audited policy, I would rather have the application enforce it. But a presentation decision such as: status = shipped ↓ show tracking message can reasonably live in the conversation orchestration layer, particularly when the underlying status still comes from an authoritative backend. A useful boundary might look like this: | Responsibility | Better Home | |---|---| | Interpret fuzzy language | AI | | Validate an order ID | Structured input | | Retrieve order status | API/backend | | Choose conversation path | Deterministic workflow | | Approve a refund | Business logic/backend | | Handle unusual exception | Human | The important question is not: “Can this be moved into a visual builder?” It is: “Should the conversation layer own this decision?” Now suppose the order API returns: 503 Service Unavailable The workflow should not silently ask AI to generate a plausible order status. It should fail predictably: API failure ↓ No verified order status ↓ Explain temporary problem ↓ Retry or human handoff Before calling this workflow production-ready, I would test at least: ✓ Valid order ID ✓ Invalid order ID ✓ Order not found ✓ API timeout ✓ Malformed response ✓ Unexpected status ✓ Human handoff The happy path proves that the demo works. Failure paths tell you whether the architecture works. Moving beyond if-else does not mean removing deterministic logic. It means stopping one layer from becoming responsible for everything. The mental model I keep coming back to is: Use AI to interpret language, APIs to provide truth, deterministic logic to control predictable paths, and humans to handle exceptions. Once those boundaries are clear, a chatbot becomes easier to reason about whether its orchestration lives in JavaScript, BotSailor, or another workflow system. And that creates the next engineering problem in this series. If APIs are going to provide the truth, how do we make the API layer reliable enough for a live conversation? That takes us into authentication, payload mapping, response handling, and reusable HTTP API wrappers. Where would you draw the boundary in your own stack? What stays in backend code, and what would you allow the conversation workflow to decide? AI Assistance Disclosure: AI was used to support the structure and editing of this article and to help prepare illustrative code examples. The ideas and practical perspective are informed by the author's work around BotSailor and messaging automation. The final article and examples were reviewed before publication.