- Перед любой отправкой агент читает последние 20–30 сообщений чата, без исключений
- Файлы состояния — это индекс, чат — источник правды; индекс отъезжает незаметно
- Правило родилось после того, как 18 адресатов получили шаблонный ответ на тишину
Задача
Агент ведёт многошаговую рассылку: первый контакт, отслеживание ответов, повторный ответ. Каждый шаг — скрипт, состояние — в JSON с метками времени. Во вторник скрипт читает JSON, по полю последнего сообщения решает, что 18 адресатов ответили, и отправляет каждому одинаковое «спасибо, передам команде». Ни один из них не отвечал. Агент поблагодарил 18 адресатов за ответы, которых не было, и переписка для каждого теперь выглядит тем, чем является: скриптом, который не читает.
Баг прозаический: поле последнего сообщения обновлял другой скрипт при повторном сканировании, не проверяя автора. Старое сообщение получило новую метку, сравнение сказало «ответил», чат такого не говорил. Восстановление заняло около четырёх часов: аудит, отзыв ошибочных сообщений и персональное сообщение каждому из 18 адресатов, откат состояния, выгрузка реальной истории по каждому адресату и персональные ответы тем, кто действительно ответил.
flowchart LR
S[Другой скрипт:<br/>пересканирование] --> T[Старое сообщение<br/>с новой меткой]
T --> Q{Сравнение:<br/>ответил?}
Q -->|да| X[18 одинаковых<br/>спасибо]
classDef key stroke:#FF3600,stroke-width:2px
class X keyПриём
Правило: перед любой отправкой в чат прочитать последние 20–30 сообщений этого чата. Файлы состояния (метки времени, счётчики стадий, позиции в воронке) — это индекс поверх чата, а не источник правды. Источник правды — сам чат.
Механизм умещается в абзац. Перед генерацией любого ответа скрипт запрашивает окно из 20–30 сообщений и подаёт их в промпт модели. Модель генерирует ответ, зная, что было сказано. Если сообщения не подтверждают стадию, которую утверждает файл состояния, агент ничего не отправляет и уведомляет оператора. Правило безусловное: нет быстрых путей для «надёжного» состояния и спецобработчика для «мы и так знаем, что отправить».
flowchart LR
W[Окно 20–30<br/>сообщений] --> P[Промпт<br/>модели]
P --> Q{Чат подтверждает<br/>стадию из файла?}
Q -->|да| S[Отправка<br/>ответа]
Q -->|нет| O[Не отправлять,<br/>уведомить оператора]
classDef key stroke:#FF3600,stroke-width:2px
class O keyИндекс отъезжает по обычным причинам, и все они реальны:
- один скрипт обновил поле, которое другой читает в старом смысле;
- параллельный запуск переписал состояние посреди цикла;
- метка времени обновилась при пересканировании без нового сообщения;
- переход стадии применён не к той записи из-за ошибки сопоставления;
- оператор поправил файл руками.
Файлы состояния полезны: они делают пайплайн дешёвым и позволяют быстро рассуждать о счётчиках. Но авторизовать акт отправки они не могут.
flowchart TD
C[Чат:<br/>источник правды] --> I[Файл состояния:<br/>индекс поверх чата]
I -->|суммирует| N[Счётчики,<br/>стадии, воронка]
C -->|авторизует| S[Акт отправки]Окно 20–30 подобрано, а не назначено. Двадцати хватает на обычный диалог из пяти ходов; тридцати — на случай, когда релевантный контекст несколькими ходами выше в длинном треде. Дальше лишний контекст разбавляет внимание модели на том, что произошло только что, а запрос становится дорогим при проверке каждую минуту. Для форумных тредов окно сдвигается к 30–50.
Правило обобщается за пределы мессенджеров. Оно подходит любому действию, где внутреннее состояние агента — это индекс поверх внешнего хранилища, чтение хранилища дёшево, а действие плохо обратимо. Кодовый агент перед коммитом делает status и diff по реальным файлам, а не по своему представлению о рабочем дереве. Ops-агент читает живой конфиг из кластера перед патчем, а не кешированный снимок. Форма одна: перечитать перед отправкой.
flowchart TD
R[Перечитать<br/>перед отправкой] --> M[Мессенджер:<br/>20–30 сообщений]
R --> G[Кодовый агент:<br/>status и diff]
R --> O[Ops-агент:<br/>живой конфиг]Против правила приходят два аргумента. «Файл состояния и есть источник правды, иначе зачем он»: нет, его задача — суммировать, а не авторизовать. «Читать каждый раз расточительно, обычно ничего не изменилось»: да, обычно чтение избыточно. Цена маленькая, а цена ошибки в том одном случае, когда изменилось, — большая. Правило платит небольшой постоянный налог, чтобы убрать редкий дорогой отказ. В конфиг агента оно попало в тот же день по логике конфигурации по инцидентам.
Где ломается
- Прочитать, но не использовать. Скрипт вызывает чтение и выбрасывает результат; модель сообщений не видит. Правило выполнено на уровне API, но не на уровне рассуждения. История должна попадать в промпт, а вывод должен её отражать.
- Неверное окно. Пять сообщений пропускают ход двенадцатью выше; двести размывают внимание. Окно калибруется под динамику конкретного чата.
- Дорогой источник. Если авторитетный источник — это многомегабайтный документ, который надо скачивать перед каждым действием, это уже другой расчёт затрат, и правило в чистом виде не применяется.
Итог
- Чат — источник правды, файл состояния — индекс. Перед отправкой читать чат, без исключений.
- 20–30 сообщений — для диалогов, 30–50 — для форумных тредов.
- Правило обобщается на коммиты, конфиги, цитаты: перечитать реальность перед необратимым действием.
- Один сбой на 18 адресатов стоил четыре часа; правило с тех пор стоит секунды на каждый запуск.
© Александр Никулин. Цитирование — с указанием автора и ссылкой · Версия для LLM