/thread-clean — стереть свой спам, потом писать заново
Зачем (принцип, anton 03.09.2026, ДВА голосовых)
Первый голосовой: «у человека память короткая… он забывает мгновенно всё, что ему написали.
Поэтому мы просто вычищаем всё неугодное у каждого лида, которому хотим что-то предложить,
и шлём человеку ещё раз то, что нам нужно».
⭐ Второй голосовой — УТОЧНЕНИЕ, отменяет «и его» из первого: «мы НЕ будем удалять сообщение
лида, будем удалять НАШЕ сообщение до последнего сообщения лида. Если он написал, что мы
спамеры — мы на это красиво шуткой ответим». То есть под нож идёт ТОЛЬКО наше; обиду лида
не стираем — снимаем шуткой в новом касании (§3.3 голос Майкрофта).
Практический смысл: тред, где висят 3-4 наших неотвеченных залпа подряд, убивает любой
следующий питч ещё до прочтения. Чистка возвращает диалог в состояние «последнее, что было
между нами — живой тёплый разговор», и новое письмо читается как первое касание за годы.
⚖️ Серое, не чёрное: удаляем СВОИ сообщения в СВОЕЙ личке штатной кнопкой платформы (revoke).
Копия всего остаётся в архиве dm_messages.db (1.8 млн сообщений на хабе) — CRM-история не
теряется, теряется только видимая собеседнику простыня.
⚖️ HARD-FLOOR (граница Claude): планировщик СТРОЮ и ПОКАЗЫВАЮ я (обратимо, 0 удалений). Сам
необратимый delete_messages — на прокоже прибора, который жмёт Антон / CRM-рутина, не
авто-Claude в потоке: перманентное удаление сообщений у Claude в запретном списке независимо
от «+». Это не тормоз стратегии (она зелёная), а тот же класс, что «пароль вводит человек».
Шаг 1. RECALL — на кого замахнулись
Карточка CRM + история: leads.db, chats.db, прошлые касания в логе проекта.
Стоп-сигналы ДО чистки: явный отказ («stop», «not looking for…»), 🪦-метка, Tier-2-тема.
Отказ = не чистим и не пишем; чистка не отменяет [[declined-decisions]].
Шаг 2. План (детерминированно, 0 LLM, ничего не удаляет)
Исполняется на ХАБЕ ([машина флота]): там лежит tg_archive.db (6 ГБ) — пиры его не держат.
Ключ диалога = numeric dialog_id (из resolve_username / get_history).
python ~/.claude/scripts/thread_clean_plan.py <dialog_id> --json plan.json
⚠️ Сверено с кодом 03.09 вечером: флагов --handle и ключа slug у прибора нет (раньше
этот раздел описывал их — сессия по такой инструкции спотыкалась). Живые флаги: --source,
--wa-account, --db, --account, --blast-min, --also, --since, --json; правда —
--help, а не этот файл.
Что печатает: каждое НАШЕ (out=1) сообщение с меткой
BLAST xN — похожее наше исходящее ушло в N разных диалогов = веерная рассылка. Похожесть
считается Жаккаром по словам (порог 0.6) в окне ±120 дней, поэтому «gm [человек]…» и
«gm [человек]…» из одной рассылки узнаются как один шаблон. Замер 03.09: топ-веер = 6069
получателей, дальше 4852 и 4259.
UNANS — после него собеседник не ответил ни разу.
MANUAL — НАШЕ сообщение, добавленное флагом --also <msg_id>.
⚠️ ЖЕЛЕЗНЫЙ ИНВАРИАНТ ядра: сообщения ЛИДА (out=0) в план НЕ попадают НИКОГДА — ни авто,
ни через --also; попытка печатается как ⛔ ОТКАЗ по --also и уезжает в
plan["also_rejected"]. Обиду («stop spamming me», «вы кинули нас») НЕ стираем: отвечаем
шуткой в новом касании. Стережёт тест test_their_message_rejected_even_with_also
(до 03.09 21:0x код пускал чужое, а тест закреплял старое поведение — ложный зелёный).
Прогон по большому треду — секунды–минуты (Жаккар по окну на каждое наше сообщение).
Шаг 2-бис. WhatsApp — да, можно, но окно другое (замер 03.09.2026)
Прямой вопрос Антона из голосовой («в WhatsApp — не знаю, можно или нельзя, надо посмотреть»)
закрыт чтением кода MCP, а не догадкой:
python ~/.claude/scripts/thread_clean_plan.py <chat_jid> --source whatsapp --wa-account main
# chat_jid вида [id]@s.whatsapp.net; --wa-account main | wa2
- План работает: базы MCP лежат локально (
main 134 405 сообщений / 18 831 наших /
10 498 чатов; wa2 14 570 / 2 282 / 7 842), схема отличается только именами колонок, поэтому
ядро одно, а различия живут в SCHEMAS внутри прибора. Боевой прогон 03.09 по треду
[id]@s.whatsapp.net дал 7 кандидатов из 22 сообщений.
- Удаление:
mcp__whatsapp__delete_message шлёт протокольный revoke
({delete: {remoteJid, fromMe, id}}) — стирает у обеих сторон и из локальной базы, и требует
двухфазного confirmed=true. Чужое сообщение в личке платформа не даёт удалить в принципе
(только админу в группе), то есть наш инвариант тут совпадает с ограничением WhatsApp.
- ⚠️ Окно ревока НЕ замерено. У Telegram лимита по возрасту нет (стёрли сообщение
3,5-летней давности), у WhatsApp «удалить у всех» исторически ограничено сроком, и Baileys
этот срок не проверяет — сервер просто откажет. Переносить телеграмный опыт нельзя
([[prichina-kak-claim]]): перед первой боевой чисткой в WA — канарейка на одном СТАРОМ
сообщении, и только потом пачка.
- ⚠️ В WhatsApp у Антона много бытовых тредов (автосервис, доставка). Там
UNANS значит
«человек не ответил», а не «мы спамим» — без BLAST такой тред не чистим вообще.
Шаг 3. Показать Антону и получить «+»
Удаление необратимо, поэтому план всегда идёт ему глазами: сколько всего сообщений, сколько
под нож, что именно останется (первая строка каждого выжившего), и одна строка «после чистки
последним в треде будет: <дата, чьё, текст>». Ждём «+» по КОНКРЕТНОМУ лиду или на список.
Standing-мандат на класс (например «все 🔥-треды грок-теста») действует, только если Антон
дал его явно и он записан в файл проекта; молчание мандатом не считается.
Шаг 3-бис. ⚠️ ЧЕЙ АККАУНТ (замер 03.09.2026, чуть не стоил провала)
Один тред у лида может быть набит сообщениями с РАЗНЫХ наших аккаунтов. Замер: диалог
Парвеза [id] = 74 сообщения с [рабочий аккаунт] + 71 с [рабочий аккаунт].
Правило: удалять сообщение можно ТОЛЬКО с того аккаунта, который его отправил. Прибор
печатает [аккаунт] у каждого кандидата и ругается, если аккаунтов больше одного. Нужного
аккаунта нет в рельсе → эта часть плана остаётся, и мы честно говорим, что тред почищен
наполовину; тихо «почистили» о половине не отчитываемся.
Инвентарь аккаунтов (замер 03.09.2026, HP17)
Две независимые рельсы, у каждой свой список — сверять НАДО обе:
- MCP (демон
127.0.0.1:8765, конфиг [путь владельца], ключи
TELEGRAM_SESSION_STRING[_<LABEL>]): ⚠️ безымянного ключа больше НЕТ — замер 10.09.2026 на
хабе даёт четыре ЯВНЫХ ярлыка [рабочий аккаунт] · [рабочий аккаунт] · [рабочий аккаунт] · TONYDZI
(api_id у всех общий [id]). Псевдоним [рабочий аккаунт]→default из-за этого стал вести в
пустоту и живой аккаунт печатался как «сессии нет в .env» — починено резолвером
resolve_account_label() (точное имя раньше псевдонима), класс alias-outlives-the-key.
- Telethon-рельса архива (
[путь владельца], ключи <ACC>_SESSION):
REFRESH=@[рабочий аккаунт] · [рабочий аккаунт] · TONYDZI — все живые.
- 🔴 ЗАПИСЬ «[рабочий аккаунт] МЁРТВ В ОБЕИХ» ОПРОВЕРГНУТА 10.09.2026. Живой замер на хабе:
is_user_authorized() == True, get_me().username == '[рабочий аккаунт]' в обеих рельсах. Прежняя
строка (03.09, HP17, AuthKeyUnregistered) верна ТОЛЬКО для того дня и той машины. Мораль —
канонная: запрет это такой же claim, и он протухает ([[ban-is-a-claim-recheck-before-workaround]]).
⛔ Не строй «чистим наполовину» по памяти: перед каждой чисткой гоняй живую проверку ниже.
Как проверить живость всех разом (0 токенов, ничего не меняет): пройтись Telethon'ом по
ключам обоих .env и напечатать is_user_authorized + get_me — образец кода в истории
сессии 03.09; «в конфиге есть» ≠ «сессия жива» ([[prichina-kak-claim]]).
Перелогин мёртвого аккаунта: [путь владельца] send <phone> →
код приходит в приложение того аккаунта или SMS → ... code <код> → (при 2FA) ... pass <пароль>.
Код может забрать только человек, если аккаунт не залогинен ни в одной нашей рельсе, — это
истинный блок, а не повод молчать: одна строка Антону.
После правки .env демон надо ПЕРЕЗАПУСТИТЬ (иначе новый аккаунт не виден):
Stop-Process по telegram-mcp\main.py → запустить ~/.claude/scripts/telegram_mcp_sse.cmd.
⚠️ Демон общий на флот: перезапуск на секунды роняет телегу у всех сессий, а СВОЯ сессия
теряет SSE-соединение до перезапуска клиента («Invalid request parameters» на любой вызов) —
проверять результат тогда напрямую Telethon'ом, а не MCP.
Шаг 3-тер. ДВЕРЬ ИСПОЛНЕНИЯ (thread_clean_apply.py, построена 03.09.2026)
План строит thread_clean_plan.py, а исполняет ~/.claude/scripts/thread_clean_apply.py —
раньше этого файла не было, и «удалить пачку» приходилось делать вызовами руками (бывшая
«Точка роста #1»). Рельса Telethon напрямую, не MCP: клиент MCP в сессии может лежать, а
демон общий на флот и его не трогают.
# сухой прогон (0 удалений, печатает что сделал бы)
python ~/.claude/scripts/thread_clean_apply.py --plan plan.json
# боевой — жмёт ЧЕЛОВЕК (у Claude перманентное удаление в запретном списке)
python ~/.claude/scripts/thread_clean_apply.py --plan plan.json --apply
| Инвариант | Что делает | Откуда взялся |
|---|
out=True живьём | перед каждым revoke перепроверяет, что сообщение НАШЕ | железный запрет скилла |
--min-age-days 14 | не трогает свежие | замер 03.09: в план попало наше «интро sui/aptos ещё нужно?» двухдневной давности — UNANS там значит «не успел ответить» |
--keep-last-ours | сохраняет наш последний ответ, если иначе последним останется сообщение собеседника | замер 03.09: у Парвеза план стирал наш ответ, оставляя финальным «I dont think a collab would work tbh» |
| мёртвый аккаунт → пропуск вслух | печатает «тред чистится наполовину» | 87 из 169 кандидатов сидят на @[рабочий аккаунт], а он не авторизован с 30.08 |
| сбой треда не роняет прогон | ловит исключение и идёт дальше | нерезолвнутый [человек] уносил с собой три следующих треда |
--handle <id>=@username | резолв, когда numeric id нет в кэше сессии | Telethon кидает ValueError на id, которого не видел |
| журнал | ~/.claude/change_ledger/thread-clean-<HOST>.jsonl | удаление необратимо — след обязателен |
Сторож: ~/.claude/scripts/_test_thread_clean_apply.py (6 проверок, 0 сети). Показан КРАСНЫМ
на трёх поломках ядра: снят фильтр возраста · снята защита последнего ответа · сломан алиас
аккаунта.
⚠️ Удаление = revoke у обеих сторон = необратимо = Tier-2 класс E: нужен «+» Антона через
approval.py ask --cat delete --critical, а не просто показ плана в чате.
Шаг 3-кватер. КНОПКА ЧЕЛОВЕКА → РУКА CRM (thread_clean_runner.py, 04.09.2026)
Модель Антона дословно (03.09, голосом): «стирать будет скрип CRM-ки, ты просто нажимаешь
кнопку, даёшь CRM-ке знать, а она уже всё стирает». Цепочка собрана так, и в ней НЕТ звена,
где Claude стирает по собственному решению:
сессия Claude -> строит план + кладёт задание в очередь (обратимо, 0 удалений)
человек в 02 POLICE -> отвечает «+» на аск (ЭТО И ЕСТЬ КНОПКА)
approval_reply_tick.py -> слышит «+», ставит аску approved (каждые 15 мин, 0 LLM)
thread_clean_runner.py -> видит approved, зовёт …_apply --apply (каждые 20 мин, 0 LLM)
thread_clean_apply.py -> revoke у обеих сторон + журнал (Шаг 3-тер)
# поставить задание (ждёт «+», без него не выстрелит никогда)
python ~/.claude/scripts/thread_clean_runner.py --add --ask-id <id аска> --plan <plan.json> --handle [id]=[аккаунт]
python ~/.claude/scripts/thread_clean_runner.py --status # что стоит и почему
FAIL-CLOSED: запуск разрешён ровно при status == 'approved'. Нет аска в базе, pending,
stale, expired, битая БД, пропавший план — рутина пишет «жду: кнопка не нажата» и не
трогает ничего. Сторож: _test_thread_clean_runner.py (13 проверок; показан красным на
fail-open — «запускать всё, что не rejected»). Рот рутины:
_thread_clean_runner_state.json, зарегистрирован в output_freshness.py (max_age_h: 3),
потому что тихо снесённая задача = одобренная человеком чистка, которая не случится никогда.
Шаг 4. Канарейка, потом пачка
✅ ПРОВЕРЕНО 03.09.2026: mcp__telegram__delete_message удаляет у ОБЕИХ сторон (revoke).
Доказательство: с default отправлено сообщение на [рабочий аккаунт] (у получателя msg 172847),
удалено с default (msg 1875552) → повторный get_history ГЛАЗАМИ ПОЛУЧАТЕЛЯ показал, что
сообщение исчезло и у него. Лимита по возрасту нет: боевая канарейка стёрла в треде Парвеза
сообщение msg 688220 от 2023-03-25 (3,5 года), пропало из истории.
Порядок всё равно осторожный:
- Одно сообщение из плана →
get_history → убедиться, что исчезло.
- Дальше порциями,
get_history после каждой порции, счётчик «было / стало».
- Если в новом окружении revoke вдруг не сработает (чужой аккаунт, бот-сессия) — остановиться
и сказать Антону: чистить только у себя бессмысленно, у него всё остаётся.
Пере-проверять revoke каждый раз не нужно, но при смене аккаунта или рельсы (Telethon вместо
MCP) — проверка заново: это claim, а он протухает.
Шаг 5. Верификация и лог
get_history после чистки → счётчик «было N, стало M, последнее сообщение = …».
- Строка в лог касаний соответствующего проекта: дата · лид · сколько удалено · причины.
- Карточка CRM: пометка «тред очищен <дата>, причина: подготовка к <питч>».
⛔ ГЕЙТ ОДНОГО ГОЛОСА — до отправки проверь, не писал ли этому человеку другой наш аккаунт (03.09.2026)
python "[путь владельца]" --peer <tg_id> --account <с какого шлём>
STOP (exit 2) = этому человеку уже писали с другого нашего аккаунта в окне 72ч → НЕ шлём вторым
голосом, ведём тред тем аккаунтом, что уже там. WARN (exit 3) = оба источника слепы, шлём, но
вслух говорим, что не проверили. Замер-повод: лид Jiten Oswal, 28.08.2026 — за ОДИННАДЦАТЬ секунд
ему ушло четыре сообщения с @[рабочий аккаунт], @[рабочий аккаунт], @TonyDzi, @[рабочий аккаунт]; у двух последних это было
первое в жизни сообщение ему («my claude is waiting for yours», «check our room - gifts inside»).
С его стороны это неотличимо от скама с трёх номеров; лид молчит с 25.08.
Гейт стоит слоем 0 в safe_send (рутины) — эта строка закрывает вторую половину: ЖИВЫЕ сессии,
которые шлют через MCP мимо ledger. Прибор смотрит в ДВА источника (ledger + архив телеги), потому
что в ledger всего 14 событий за историю, а реальных касаний по одному лиду — 50.
Шаг 6. Пауза, потом свежее касание
Не слать питч тем же вызовом, что и удаление: секунда между «исчезли 15 сообщений» и «привет!»
выглядит машинно, если человек в этот момент в чате. Разрыв хотя бы в десяток минут, лучше
следующий заход. Текст — по правилам канала ([[vip-leads-no-robot-text]], тир раскрытия).
Стоп-краны
- ⛔ Только ЛИЧКИ. Группы, каналы, клубы (СОСТАВ, ClawEng) — не наша юрисдикция: там чужая
модерация и чужие свидетели.
- ⛔ Не удаляем содержательное: договорённости, цифры, обещания, чужие вопросы без ответа —
это материал CRM и наша репутация. Под нож идёт МУСОР НАШЕГО производства.
- ⛔ Не удаляем ничего в тредах, где есть деньги, обязательства, юридические темы (Tier-2) —
там переписка это доказательство.
- ⛔ Не чистим тред, чтобы скрыть свой факап от Антона или от команды.
- ⛔ Прямой вопрос собеседника «ты удалял сообщения?» — не отрицаем.
- ⛔ Чистка не даёт права на новый веер: одно точное письмо в правильную дверь (§1.5).
Точки роста (Антону на доработку)
- ✅ ЗАКРЫТО ПО-НАСТОЯЩЕМУ 10.09.2026 (03.09 было ЛОЖНЫМ ЗЕЛЁНЫМ): массовая рельса =
thread_clean_apply.py (Шаг 3-тер), Telethon пачкой + журнал в change_ledger.
⚠️ Что вскрылось на первом боевом прогоне (тред [аккаунт]): пара строитель→исполнитель
никогда не работала сквозь. thread_clean_plan.py печатает ОДИН диалог
{dialog_id, candidates:[…]}, а дверь читала список тредов с peer_id/per_account/
date_raw — и падала KeyError: 'peer_id' на любом реальном плане. Запись «закрыто»
стояла 7 суток, потому что сквозной прогон ни разу не делали, а тесты проверяли только
внутренние фильтры двери. Класс: builder-executor-contract-drift.
Починка: normalize_plan() на входе двери принимает ОБЕ формы (тесты
test_builder_plan_is_consumable, test_already_normalized_plan_passes_through).
Осталось на будущее: связать с budget.py, если чистка пойдёт десятками тредов за прогон.
1-бис. ✅ ЗАКРЫТО 10.09.2026: задание рутины несёт СВОИ флаги двери (--extra, белый список
safe_extra()). Повод там же: решение «оставить последним слово лида» принимается на этапе
ПЛАНА, а thread_clean_runner.py звал дверь жёстко без флагов — по кнопке человека уцелела
бы ровно та простыня, ради удаления которой чистка затевалась. Класс:
решение-на-плане-теряется-в-рельсе. Тесты test_job_flags_reach_the_door,
test_unknown_flag_is_dropped (очередь = внешний вход, в командную строку двери удаления
пускаем только известные флаги).
- ⏸️ В БЭКЛОГ (оценено 03.09 по §3.5): предпосчитанная таблица отпечатков рассылок. Ускорит
план, но сейчас чистка идёт единицами тредов, а прогон занимает секунды–минуты — «кровь из
носу сейчас» не выполняется. Строим, когда чистка пойдёт десятками за прогон.
- ⏸️ В БЭКЛОГ (там же): автометка «выжжен» в CRM по счётчику неотвеченных подряд. Правильная
идея, но у неё нет потребителя до тех пор, пока чистка не встанет рутиной на входе в аутрич;
сегодня её роль выполняет
/triage + шкала теплоты ([[warmth-scale-lead-grading]]).
- ✅ ЗАКРЫТО ЗАМЕРОМ 03.09 (см. «Замер порога» ниже): порог
--blast-min 3 больше не «с
потолка». Заодно вскрылось, что мерить его отпечатком НЕЛЬЗЯ — только боевым Жаккаром.
Связанное
- Прибор:
~/.claude/scripts/thread_clean_plan.py (docstring = паспорт), тест
_test_thread_clean_plan.py (7 проверок, красная на поломках ядра).
- Данные:
[путь владельца] (архив Telegram, 6 ГБ; копия остаётся
после чистки), WhatsApp — ~/.local/share/whatsapp-mcp/store/whatsapp.db (main) и
~/.local/share/wa-wa2/whatsapp-mcp/store/whatsapp.db (wa2), плюс leads.db, chats.db.
- Канон в домах: память [[thread-clean-before-recontact]] · Библия
reglament-chistka-treda-pered-novym-kasaniem · CLAUDE.md §9.11.
- Соседи:
/triage (кого касаться), /telegram-lead-outreach (что писать), /bold-followup
(пинг без блока), /pipeline.
- Канон: [[cold-pr-into-silent-queue]] (4-е неотвеченное = спам), §1.5 (спам ≠ громкость),
§1.5-бис (пинг кому угодно), [[declined-decisions]], [[crm-lead-full-provenance]].
<!--kit-footer-->
Like this skill? It is one of 100 in second-brain-starter-kit: the second brain we built for ourselves and run every day at Palo Alto AI Research Lab. Install the whole set with npx skills add tonydzi/second-brain-starter-kit. Everything is open source and free, so take what you need.
Flagships worth a look on their own: secondop-panel (a second opinion from a panel of external models), claude-memory-tidy (stop your agent's memory from rotting), telegram-mcp-kit (your own Telegram over MCP in about 15 minutes).
Author: Anton Dziatkovskii, Palo Alto AI Research Lab. Telegram @tonydzi - WhatsApp +1 341 222 9178 - X @Tony_Stef_
Engineers: want to test-drive this setup? Message me. I hand out free starter seeds to engineers who test and report back, and custom skill requests are welcome.