Live Nov 18 · Cohort 3Nov 18, 19:00 MSK — free hands-on AI agents live workshop · Live Cohort 3 starts Nov 23Join Nov 18 Live →Cohort 3 (Nov 23)
ReleaseAPI / Product · ActionableHabr AI→ original

Two conversation limits in the amoCRM bot had been broken since launch: AND instead of OR and a faulty branch were to blame

The Octery bot for amoCRM had two limits in its admin panel: a maximum number of replies and a nonsense threshold. Neither had worked from the start. The manager handoff condition used AND instead of OR, while the handoff branch wrote values to fields with incorrect names, causing subsequent steps to read the model's old response.

AI-processed from Habr AI; edited by Hamidun News
Two conversation limits in the amoCRM bot had been broken since launch: AND instead of OR and a faulty branch were to blame
Source: Habr AI. Collage: Hamidun News.
Listen to article
Article Fact Sheet & Key Figures
ParameterDetails
Primary sourceHabr AI ↗
Editor-in-Chief
Key metric #1The logic lived in the pipeline configuration: no unit tests, no CI and no readable diff
Key metric #2The solution was moved into a pure API function, which retrieves the limits from the settings itself
Key metric #3Each of the specification's 16 scenarios became a test with a descriptive name

Imagine this: the numbers are there in the admin panel, the setting is saved and the interface looks exactly as it should. In reality, it does nothing. That is precisely how, on September 30, the team discovered that neither conversation-length limit worked in the Octery bot—and apparently neither had worked from the very beginning.

Octery replies to leads in amoCRM chats and hands the deal over to a manager. It has two limits. Max Messages determines how many replies the bot gives before calling in a human. Hard Stop Threshold determines how many consecutive meaningless messages it will tolerate: "aaa," "test," "???" or even output from an unrelated bot. Every such message costs a model call, so there is no point responding to them indefinitely.

Two errors that concealed each other

The handoff decision was made after the model responded, inside the processing pipeline. The condition consisted of three checks joined by AND. According to the settings' intended semantics, the limits are independent: a handoff should occur if the reply counter is exhausted or if a streak of meaningless messages reaches the threshold. With AND, the bot called a manager only when the customer simultaneously exhausted the message limit and sent nonsense several times in a row. That never happened.

The second error was in the branch that was supposed to run when the condition was met. It overwrote the response with "handing over to a manager" and set the intent to manager. But the values were written into fields whose names had been replaced by expressions: a value was inserted into the name field and remained there. On top of that, response delivery and logging read data not from this branch but directly from the parsed model response. Even if someone had changed AND to OR, the customer would still have received a regular response and the logs would have shown no trace of the handoff.

The first manual run would have exposed either error on its own. Together, they made the limits invisible.

Why nobody noticed

The logic lived in the pipeline configuration rather than in code. You cannot write a unit test for the configuration, run it in CI or review the change as a readable diff: it merely looks like someone "fixed the condition." Manually driving a live conversation all the way to the limit takes time, so routine checks never reached it. There was neither a positive nor a negative answer to the question, "Do the limits work?"

What was done

The decision was moved into the API as a separate pure function. The pipeline passes it the model response and two counters from the conversation state, while the function retrieves the limits from the company's settings itself, preventing the caller from supplying different values. It returns one final decision: text, intent and handoff reason. Every subsequent step reads only that result.

The formulas contain two off-by-one traps. The reply counter is measured before the current message: the bot gives exactly max_messages replies and then hands off. The nonsense streak includes the current message, as stated in the interface hint. If both conditions trigger, the message limit is recorded as the reason. If the phrase template is empty, the default phrase is sent. The change also removed a side effect: the old branch rated the message as "meaningless," corrupting analytics.

Tests derived from the specification

The behavior was first described as scenarios, then each scenario became a test—16 in total. Their names speak for themselves: "hands off on the Nth non-meaningful message in a row, counting the current one" and "a meaningful message breaks the non-meaningful streak." The "old AND case" test was retained deliberately: the only case in which the old condition ever triggered is now a special case of the new behavior.

Rollout risk and takeaway

The main risk is not technical. Companies with low limits will see the bot fall silent and call a manager for the first time. To them, this will look like a new feature rather than a fix. They are therefore being notified before activation, as a separate item in the rollout plan.

The practical takeaway for teams using no-code and low-code pipelines is straightforward: if a number is visible to customers in the interface, its behavior should have an automated test, and the test name should state the promise made to the customer. Business logic that affects revenue and the handoff of a customer to a human is better moved out of configuration and into code, where it can be reviewed in a diff and exercised in CI.

Story

Key Questions Answered

What are the key details on Two errors that concealed each other?

The handoff decision was made after the model responded, inside the processing pipeline. The condition consisted of three checks joined by AND. According to the settings' intended semantics, the limits are independent: a handoff should occur if the reply counter is exhausted or if a streak of meaningless messages reaches the threshold. With AND, the bot called a manager only when the customer simultaneously exhausted the message limit and sent nonsense several times in a row. That never happened.

Why nobody noticed?

The logic lived in the pipeline configuration rather than in code. You cannot write a unit test for the configuration, run it in CI or review the change as a readable diff: it merely looks like someone "fixed the condition." Manually driving a live conversation all the way to the limit takes time, so routine checks never reached it. There was neither a positive nor a negative answer to the question, "Do the limits work?"

What was done?

The decision was moved into the API as a separate pure function. The pipeline passes it the model response and two counters from the conversation state, while the function retrieves the limits from the company's settings itself, preventing the caller from supplying different values. It returns one final decision: text, intent and handoff reason. Every subsequent step reads only that result. The formulas contain two off-by-one traps.

ZK
Zhemal Khamidun — CPO at Alpina Digital, product manager of AlpinaGPT. Building AI products since 2019. Hamidun News is a pet project that grew into a production aggregator with 6,000+ readers.
✦ Unified AI Workspace · Nomad

Test Frontier LLMs & Autonomous Agents in One Workspace

Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.

Try Nomad AI →Launch Nomad Agent
What do you think?
Loading comments…