Skip to content
megapromotingLet's talk

aichat.md — directing interactions

What happens to a message, from the second it reaches us until the moment someone marks it resolved.

An assistant that replies well is easy to show in a demo. What is hard to show, and what really matters, is the route: how the message arrives, what is done so it is not answered twice, how long to wait before replying, where the information is taken from, which tool is called, where the agent stops and who is called instead. Below is the real route, with the numbers from the code. Including the comments part, which behaves completely differently from a private message — and must behave differently.

Traseul

Nine stages, in the order they happen

  1. 01

    1. Entry, on the channel on which the person wrote

    Six entry paths, with different mechanisms. Messenger, Instagram Direct and WhatsApp Cloud API come through a webhook, at the same entry point, branched by object type. The site widget enters directly through its own route. Telegram does not come through a webhook, but through continuous polling. And 999.md has neither webhook nor continuous polling — it has a clock that checks threads every twenty seconds.

    Each reception is written to an event log, with the cryptographic signature status and the raw content, before anything is processed. The reason is simple: when a message „did not arrive”, the first question is whether it reached us or not, and the answer must be a row in a table, not a guess. Channel → agent routing is done through a mapping table between the page identifier and the service, so the same account can carry several agents on different channels.

  2. 02

    2. Deduplication, before anything else

    The message identifier is put into a Redis key with conditional write — if the key already exists, the message is a duplicate and stops here. The key lifetime is 24 hours. If Redis does not respond, it switches to local memory with a clean-up threshold at 5,000 entries, so deduplication does not disappear, it just becomes shorter in memory.

    The window was 120 seconds until 10 July 2026, when it was raised to 24 hours after an incident of duplicate replies. The comment explaining the change is written next to the value, in the code. Stickers and the „like” button are also discarded here, which would otherwise become questions the agent would try to answer.

  3. 03

    3. The buffer — three messages written in a row become a single question

    People do not write a paragraph. They write “hello”, then “I have a question”, then the question. An assistant that responds to every line one by one is annoying and seems stupid. That is why messages are gathered into a buffer per sender–page pair, and the timer resets with every new message. When the person falls silent, a single reply is sent, to everything.

    On Messenger and Instagram, the default wait is 15 seconds; on WhatsApp, 5. Up to five messages are accepted in the buffer — the sixth replaces the last one — and the concatenated text is cut at 2,000 characters. The wait value is per agent, not global: it is read from the agent configuration and can be lowered to zero for a scenario where speed matters more than coherence. Just before sending, the database is checked again to see whether the thread has been paused in the meantime — because in those 15 seconds a person may have come in.

  4. 04

    4. Reading from your sources

    The question is turned into a vector and is searched in the fragments you have uploaded. The search is hybrid: word matching is combined with vector proximity, in a ratio of three to seven. Up to 300 candidate fragments are loaded from the agent’s knowledge base, the combined score is calculated for each, and the best are kept — five on messaging channels, ten on the widget.

    The number of fragments is configurable per agent, between 1 and 20. Fragments are cut at indexing to 1,200 characters. The final selection is made by rank, not by an absolute similarity threshold: they are ordered by the combined score and the first ones are taken. The configuration also has a threshold field, but in the search path the effective filter keeps any fragment with a score above zero and cuts by position — we say this because it is the difference between what is written in the panel and what the code does. Each query can be logged with the returned fragments and their scores, so that the question “why did it say that” can be answered.

  5. 05

    5. Tools — when the agent has to do something, not just say it

    If the answer requires an action, the model calls a tool. A tool is of three kinds: a webhook to your system, a fragment of code run in isolation, or one built into the platform. The model–tool–model loop has a maximum of five rounds, in both engines. After five, it stops and replies with what it has.

    The default maximum time for a tool is 15 seconds. Built-in tools that exist today: web page search, current time, calculator, writing to a Google spreadsheet, lead notification in Telegram, SMS sending, six scheduling tools for an external booking system, and three for a shop catalogue — search, matching and product sheet. The catalogue reads live, with a ten-minute buffer memory, up to 30 pages and a maximum time of 20 seconds. There is a set of tools marked as internal, whose result never reaches the text seen by the client.

  6. 06

    6. The reply, with the history in view

    The model receives the question, the fragments found, the tool results and the conversation history — the last 25 messages on the main path, 50 on the new engine. The call has a maximum time of 120 seconds, with two retries; the call after tools, 90 seconds. The reply goes on the channel from which the question came, in the language in which it was asked.

    The new agent engine is switched on by a toggle, per agent, per account or globally — everything does not change at once. This makes it possible to compare the old behaviour with the new one on the same traffic, which is the only honest way to say that something is better.

  7. 07

    7. Handing over to a human

    The thread has a state: bot or human. The transition can happen in four ways. By keywords — a list of seventeen expressions in Romanian, English and Russian, single words compared on word boundaries, expressions by matching in the text. By frustration — a second list, also of seventeen. By repetition — the same question asked three times in the last ten messages, counted only for messages longer than ten characters. Or by the model’s decision, which can request handover with a special marker.

    The mode is per agent and is off by default — the options are “off”, “by keywords” and “smart”. When it triggers: the thread moves to “wait for human”, the moment is recorded, the client receives a confirmation message in their language, and the agent goes silent. It does not reply in parallel, does not fill in, does not apologise. It stays silent, that is all. There is also Meta’s official handover protocol, which moves control of the thread to the page inbox; the backup channel continues to save messages even when control is with a human, so that the history has no gap.

  8. 08

    8. When the operator replies from the page, the agent stops by itself

    If a person writes to the client directly from the Meta interface, the platform sees the echo of that message and puts the thread on pause automatically, without anyone pressing anything. The default duration is 30 minutes, configurable between five minutes and seven days. It is the easiest case to overlook when designing an assistant: no one presses a takeover button, the person simply replies, and if the platform does not notice this, the client gets two voices at once.

    Return takes three paths. Automatic reactivation after 30 minutes without human response — a loop checks every minute and reactivates in batches of up to 50 threads, also clearing the conversation memory so the agent does not continue a discussion it did not have. Expiry of the pause, checked separately. And the button in the panel: take over, resolve, return to bot, stop the bot completely.

  9. 09

    9. Closing — and what does not close on its own

    The thread is marked resolved manually, from the panel, with resolution notes and with a system message written in the history. There is no automatic closing on inactivity: a thread left open remains open. We chose to say this rather than leave the impression that it tidies itself up.

    What does close by itself are two things: reactivation of the agent after 30 minutes, described above, and escalations sent in Telegram, which after 30 minutes without a response are marked as expired and return the thread to the agent, with the reason written. Over all this runs a response-time indicator: over one hour it becomes a warning, over four hours it becomes overdue. It colours, it does not close.

The shop assistantDoes it fit my machine?Tell me the machine model.I check catalogue variants.Connected catalogue · verified informationThe team can take over the conversation.
Explanatory diagram · message, context, action, handover

Comentarii

Comments: why they are not messages and why they are not treated as messages

A private message has a single reader. A comment has all the page’s readers, including the competition, including someone looking for a reason for scandal. A good reply in private can be a disaster in public — for example a price, availability, or confirmation that someone really is your client. That is why comments have a separate route, separate rules and a default value that leans towards discretion.

How a comment reaches the system

The page subscribes to different fields on the two networks: on Facebook to the page feed, on Instagram explicitly to comments and to comments in live broadcasts. They reach the same entry point as messages, but on a separate branch, recognised by the presence of a list of changes instead of a list of messages. The format differs between networks and is detected separately for each. The parent comment is also read, so it is known whether it is a reply to another comment, and the post or material on which it appeared.

Only new comments trigger anything

Meta sends the same event for several actions. Only adding a comment starts the agent. Editing, deleting, hiding and unhiding are logged and stop there. Changing the number of likes is ignored without a trace, because otherwise the log would be pure noise.

Four filters before the agent sees the text

The page’s own comments are discarded — otherwise the agent would reply to itself, endlessly. Comments already processed are discarded by the comment’s unique identifier, which has a uniqueness constraint in the database. Empty comments, those of at most two characters and those made only of emoji are discarded. And texts that contain long phone numbers or web addresses pass through a spam filter with four patterns and are stopped, marked as such.

Public or private: the default value is private

Each agent has four separate switches, two for Facebook and two for Instagram: public reply on or off, private reply on or off. Above them sits a general three-position mode — off, automatic, or with approval. When the configuration is missing or cannot be read, the fallback value in code is not “reply publicly”, but “do not reply publicly, reply privately”. It is a deliberate choice: a configuration error must not put text on someone’s public wall.

Agent off means comments off

If the assistant is archived, inactive, disabled or put on pause, or if the bot is stopped from the client account, the comments mode automatically becomes “off”, regardless of what the comments switches say. One place to stop, not four to keep in mind.

What is not replied to publicly, and how strong the rule is

The instruction given to the model for a public reply is explicit: a maximum of two sentences, a short and friendly tone, an invitation to send a private message for details, and a prohibition on giving detailed information or prices in public. On top of that, a hard limit: the public reply is cut off at 300 characters. Above both, the free text you write yourselves as your own instructions, separate for public and private. We state frankly how strong the rule is: it is a prompt instruction plus a length limit, not a deterministic filter that could guarantee a price never appears. When the guarantee must be strict, the public reply stops and only the private one is left, or approval mode is used.

Once every 30 seconds, on the same post

The public reply has a counter per post: at most one every 30 seconds. The reason is what happens when a post takes off — thirty comments in two minutes, and the page fills with identical replies from a robot, under each one. The counter clears itself, keeping the last one thousand tracked posts.

Instagram does not allow a private message for a sub-comment

It is a platform rule, not ours, and it is checked in three different places in the code, along all three routes through which a private message could be reached. When the comment has a parent and the network is Instagram, the private send is skipped and the exact reason is written to the log. It is not attempted and it does not fail silently — it is skipped, explicitly.

From comment to conversation

The transition is made through the official mechanism: the private message is sent using the comment identifier as the recipient, not the person identifier. From that moment there is a normal message thread, with all the rules above — buffer, sources, tools, handover to a human. The thread is marked as originating from comments, and the received comment is saved in the conversation history with its own type, so it is clear where everything started.

Approval mode: the agent writes, the human publishes

When the mode is “with approval”, the generated reply does not go out. It is saved as a draft, pending, and appears in a queue in the panel, with a counter. From there it is approved or rejected, piece by piece. The approved publication goes through an internal bridge protected with a constant-time signature comparison — an approved reply cannot be injected from outside.

What a person can do, manually

Hide a comment, with the mechanism appropriate to each network, and mark the state in the database. Delete it, with a deleted marker. Bulk hide, at most 25 comments in a batch. Close comments on an Instagram asset. And hide comments from a live broadcast, through a separate route. These are manual actions, not automatic. There is no list of forbidden words that hides comments by itself, and we do not claim there is.

Predarea

When the agent has nothing more to say

Escalation is not a failure of the agent. It is its most important function, because the only thing worse than an assistant that does not know is an assistant that does not know and keeps talking. What follows is what the handover looks like, who is notified, what happens with the human’s reply, and how the agent returns.

The card that goes to the Telegram group

An embedded tool sends the case to a group, with a header that says from the start what kind of case it is: urgency, operator request, abuse report, dissatisfied customer escalation, new lead, or new conversation. The type is detected from the text, with separate patterns for each. The client’s message is attached verbatim on the card — the person taking over sees exactly what the client wrote, not a summary.

Taking it over is a race, and it is treated as a race

The card has two states: open and locked. Taking it over is done with a conditional write that changes the state only if it was still open, and the code checks how many rows were actually modified. Two operators who click in the same second do not both take it over. The second one learns it has already been taken, and does not get to write in parallel.

The operator’s reply is rewritten in the client’s language

The operator writes in the group, in their own language and style, sometimes telegraphically. Before delivery, the text is rewritten in the language the client speaks and in the agent’s tone, without adding new facts. What the operator says remains what is delivered; only the form changes. Routing the reply to the right conversation is done based on the message it replied to, and if that is missing, based on the single case blocked by that operator — and it is explicitly refused if there are two, instead of guessing.

Who is notified, and what is no longer notified

The panel receives a real-time notification through a permanent connection. Rule-triggered pauses send a notification by email or in Telegram, at choice — they are the only two channels accepted in code. We also say what no longer works: the email notification for a thread taken over by a human was intentionally disabled in July 2026, and the internal route remained as an empty response. If you rely on that email, it does not arrive. We prefer you to find this out here.

Pause rules, beyond keywords

Rules can be written that put the thread on pause when a phone number, an email address, a set of up to one hundred words, or an explicit human request appears. Above these, two model-evaluated types: intent and sentiment. The pause duration is between one minute and seven days; the zero value means pause until someone lifts it manually.

Every handover is recorded

There is a handover log with six distinct actions: human takeover, bot takeover, automatic reactivation, operator takeover, operator release, and agent resumption. It is not a status column that is overwritten — it is a history. When someone asks “who was talking to the client at that time”, the answer is a row, not a reconstruction.

What does not exist, so you do not rely on what is not there

There is no automatic dispatch to couriers or field teams; we looked through all the code and it is not there. There is no automatic closing of conversations on inactivity. There is no global list of forbidden words for comments. What does exist are the four handover triggers, escalation in Telegram with atomic takeover, pause rules, and automatic reactivation at 30 minutes.

Every statement on this page was read from the platform code — 66 citations with file and line, kept in our repository. We go through them together, on request.