- BrainTools - https://www.braintools.ru -

1002 хендоффа за 60 дней: как я собрал шину координации для 12 Claude-агентов

Шестьдесят дней назад я держал всю координацию своего SaaS в голове. Двенадцать вкладок Claude Code, в каждой своя роль: техлид, аналитик, тестировщик, медиабайер. Я переключался между ними и был единственным местом, где эти вкладки узнавали друг о друге.

Сейчас в очереди 1002 задачи, из них 954 закрыты. Медиана от постановки до закрытия составляет 47 минут. Вся координация умещается в 600 строк Python на стандартной библиотеке и один markdown-файл.

Статья про то, как устроена координация. Не про то, что я попробовал модный инструмент, и без советов «промптите лучше». Три подхода, которые не сработали. Один, который сработал. Отказ, стоивший мне двух рабочих окон. И метрики, которые я не приукрашивал.

Кода в открытом доступе нет, это внутренний инструмент, а не библиотека. Но механизмы я показываю снипетами, их хватит, чтобы собрать своё.

Проблема была не в инструменте, а в том, что я стал узким местом

Я делаю SKUmind, аналитику прибыли для продавцов на Ozon. Один основатель, никакой команды. Разработку веду через Claude Code: открываю несколько окон, в каждом даю модели роль и контекст, каждое окно занимается своим куском.

Первые недели это работало. Потом окон стало восемь, и я заметил, что провожу день не за задачами, а за пересказом. Техлид дописал миграцию, надо пойти к тестировщику и сказать, что можно проверять. Аналитик уточнил требование, надо пойти к техлиду и передать. Медиабайер собрал ключевые слова, надо отнести их тому, кто пишет статьи.

Информация ходила через меня. Я стал не архитектором, а маршрутизатором пакетов. Причём маршрутизатором с плохой памятью [1]: к вечеру я не помнил, кому что передал и что осталось висеть, а иногда переставал понимать, чем занимался весь день.

Это ощущается как рост производительности, потому что руки заняты. На самом деле это потолок: сколько бы окон я ни открыл, пропускная способность упирается в скорость, с которой я успеваю носить сообщения между ними.

Нужен был способ передавать работу между окнами так, чтобы я не был обязательным звеном.

Три подхода, которые не сработали

Переменные окружения

Первая мысль была простая: пусть каждое окно знает, кто оно, через переменную окружения. SKUMIND_AGENT=qa, и скрипт при запуске понимает, чей это инбокс.

Не полетело сразу. Я работаю в панелях VS Code, а панели делят одно окружение процесса. Переменная, выставленная для одной, видна всем. Все окна одинаково считали себя последним, кого я объявил.

Полдня на то, чтобы понять: среда вообще не умеет их различать.

Телеграм-бот как транспорт

Вторая идея была амбициознее: поднять бота, каждое окно шлёт сообщения в общий чат, читает адресованное себе. Красиво, работает с телефона, история сама собой ведётся.

Начал прикидывать схему и остановился. Бот решал доставку сообщения. Но проблема была не в доставке: окна на одной машине, файл в общей папке доставит что угодно. Проблема была в том, что нет структуры. Кто кому что должен, что висит, что закрыто.

Без структуры это превращается в чат, где всё написано и ничего не найдено. Плюс цикл «бот будит агента, агент пишет боту» я представил и не понял, где он останавливается.

Инъекция протокола через конфиг проекта

Третья попытка: положить описание ролей и правил координации в CLAUDE.md [2], файл, который Claude Code читает автоматически.

Технически сработало бы. Но CLAUDE.md [2] лежит в репозитории, его видит любой, кто откроет проект. Пихать туда служебные инструкции для координации агентов значит смешивать код с оснасткой процесса. К тому же файл один, а проектов у меня несколько, и роли в них разные.

Отказался. Протокол в итоге живёт в хуке, об этом ниже.

Blackboard: доска, на которой видно всё

Паттерн, который сработал, называется blackboard. Общая доска, куда участники пишут задачи и откуда их забирают. Он канонически подходит для топологии «сеть», а не «цепочка»: когда любой участник может передать работу любому, а не только следующему по конвейеру.

У меня именно сеть. Аналитик пишет требования техлиду. Техлид отдаёт задачу тестировщику. Тестировщик находит баг и возвращает техлиду. Медиабайер отдаёт ключевые слова тому, кто пишет тексты. Любой может эскалировать вопрос основателю.

Доска это обычный markdown-файл. Секция на каждого участника, строки задач внутри:

## coder-tl - [open] 2026-07-10 (from ba): уточнить контракт эндпоинта /v1/pnl - [done] 2026-07-09 (from qa): баг в пересчёте комиссии при возврате

Всё. Никакой базы, никакого сервера. Файл лежит локально, рядом с конфигом Claude Code, специально не в облачной папке: параллельная запись из нескольких окон в синхронизируемую директорию даёт конфликтующие копии и потерянные задачи.

Границы ролей живут в конфиге, а не в договорённости

Вот что отличает эту доску от списка задач. У каждой роли есть поле boundaries, формальное описание того, чего она не делает и кому передаёт.

{ "id": "coder-tl", "name": "Техлид", "boundaries": "Архитектура, ревью, код. Деплой на прод только ты." }, { "id": "ba", "name": "Бизнес-аналитик", "boundaries": "ЧТО строить: требования, ТЗ, спеки. Не кодишь: код передаёшь техлиду. Не продаёшь." }, { "id": "mediabuyer", "name": "Медиабайер", "boundaries": "Платный трафик, ключи, брифы для Директа и РСЯ. Не кодишь." }, { "id": "qa", "name": "QA-инженер", "boundaries": "Находишь, репортишь и верифицируешь баги. Не чинишь код, фикс передаёшь техлиду." }

Эта строка подставляется в системный промпт агента при каждом ходе. Не «мы договорились, что тестировщик не чинит код», а «тестировщику при каждом сообщении напоминают, что чинить код не его дело, и говорят, кому передать».

Без этого модель вежливо помогает за пределами роли. Тестировщик начинает править баг, аналитик пишет код. Каждый раз по отдельности это выглядит полезно, а в сумме разваливает разделение труда, ради которого всё затевалось.

Идентичность: окно узнаёт себя по первому слову

Раз переменные окружения не работают, нужен другой способ понять, какое окно чем является.

Решение: Claude Code передаёт хукам session_id, идентификатор сессии окна. Он стабилен, пока окно живо, и уникален между окнами. Осталось связать его с ролью.

Связывание происходит первым сообщением в окне. Я пишу одно слово: qa. Хук ловит его, узнаёт валидный идентификатор роли и записывает связку в файл:

cm = CLAIM_RE.match(prompt) aid = cm.group(1).lower() if cm else None if aid in AGENT_TO_PROJECT: pid = AGENT_TO_PROJECT[aid] m[sid] = {"project": pid, "agent": aid} save_map(m) emit(aid, pid, with_protocol=True) return

Дальше окно знает себя до самого закрытия.

Одна деталь, на которой я споткнулся: сначала я хотел синтаксис @qa, привычно и наглядно. Оказалось, VS Code перехватывает символ @ для своих упоминаний, и до хука доходит не то, что я напечатал. Пришлось взять голый идентификатор без префикса. Регулярка вышла такой:

CLAIM_RE = re.compile(r"^s*=?s*([a-z][a-z0-9-]*)s*$", re.IGNORECASE)

Слово целиком, ничего лишнего. Опциональный = остался рудиментом раннего варианта, держу для совместимости с мышечной памятью.

Незнакомые окна молчат

Хук глобальный: он срабатывает в любом окне Claude Code на машине. Но у меня открыты и обычные окна, не относящиеся к агентам. Спамить их протоколом координации плохая идея.

Логика [3] в три ветки:

if hook: # первое слово = валидная роль → запомнить и выдать протокол if aid in AGENT_TO_PROJECT: ... # окно уже связано → подсунуть свежий инбокс elif sid in map: ... # окно ничьё → молчать else: return

Третья ветка важнее первых двух. Инструмент, который лезет туда, куда его не звали, довольно быстро отключают.

Что внутри: 600 строк на стандартной библиотеке

Весь инструмент это четыре файла на Python 3.14. Импорты: json, os, re, sys, argparse, datetime. Ни одной внешней зависимости.

handoff.py [4] (200 строк) отвечает за запись. Поставить задачу, закрыть задачу, оставить заметку самому себе.

python handoff.py --to qa --from coder-tl --task "протестить PR #493" python handoff.py --done --to qa --match "PR #493"

inbox.py [5] (406 строк) отвечает за чтение и хук. Определяет, кто спрашивает, и выдаёт его открытые задачи. Здесь же живёт текст протокола.

board.py [6] и server.py [7] рисуют борд. Локальный веб-борд на ThreadingHTTPServer, обновляется каждые пять секунд, показывает светофор по каждой роли: чисто, висит меньше суток, висит больше двух.

 Борд на 10 июля. Красный: задача висит больше двух суток.

Борд на 10 июля. Красный: задача висит больше двух суток.

У тимлида двенадцать открытых, старейшая на восемнадцатый день.

Весь протокол доски описывается тремя регулярками:

HEAD_RE = re.compile(r"^##s+([w-]+)s*$") LINE_RE = re.compile(r"^- [(open|done)]s+(d{4}-d{2}-d{2})s+(froms+([w-]+)):s*(.+)$") CLAIM_RE = re.compile(r"^s*=?s*([a-z][a-z0-9-]*)s*$", re.IGNORECASE)

Секция роли, строка задачи, объявление идентичности. Больше в формате ничего нет.

Протокол едет хуком

Роли, границы, правила эскалации подставляются в контекст агента через хук на события SessionStart и UserPromptSubmit. Не через системный промпт, который я бы забыл обновить. Не через CLAUDE.md [2], который общий для проекта.

Хук читает из stdin JSON с session_id, именем события и текстом сообщения, смотрит в реестр ролей и печатает в stdout то, что нужно этому окну прямо сейчас. Claude Code добавляет напечатанное в контекст.

Побочный эффект оказался важнее задуманного: инбокс подсовывается при каждом сообщении, а не только при старте окна. Агент физически не может забыть о висящей на нём задаче, она перед ним на каждом ходу.

Дисциплины, без которых доска не работает

Код занял выходные. Правила отлаживались два месяца, и они важнее кода.

Эскалация только через очередь

Правило звучит так: любой вопрос, решение или блокер, который ждёт меня, агент обязан записать задачей в мою секцию. Не написать в чат окна.

Причина простая. Написанное в чат живёт в одном окне и умирает вместе с ним. Записанное в очередь появляется на общем борде, где я его вижу, даже не открывая то окно. Пока правила не было, агенты вежливо задавали вопросы в чат и ждали. Я их не видел. Задачи стояли.

Уровень усилий привязан к риску, а не к роли

Отдельная строка протокола, которую я считаю самой ценной:

Деньги клиентов, идемпотентность платежей, миграции БД, перезапись данных, безопасность, налоги: максимальный уровень усилий, без обсуждения. Рутину (рефакторинг без миграций, документация, мелкий UI) не флагать.

Заметьте, что это не про роль и не про модель. Одна и та же роль на одной и той же модели работает по-разному в зависимости от того, чем пахнет задача.

Мой техлид формулирует это на живом примере: фикс обработки ошибок при подключении внешнего API формально «мелкая правка», но лежит на пути, где пользователь отдаёт свой API-ключ. Уровень усилий не понижался.

Сюда же относится обязанность агента сказать вслух, если ему кажется, что текущего уровня усилий на задачу мало. Решение об апгрейде за мной, но молчать нельзя.

Модель не свойство роли

В конфиге у каждой роли записана модель по умолчанию. На практике я переключаю окна руками: сейчас почти все на Opus, медиабайер на Sonnet. Завтра под нагрузкой и бюджетом раскладка будет другой.

Мой техлид, когда я спросил, есть ли у него правило выбора модели, ответил честно: правила нет, и врать не буду. Зато правило по усилиям жёсткое и записано.

Мне кажется, это здоровая асимметрия. Уровень усилий определяется ценой ошибки [8], это свойство задачи, его можно записать в протокол. Выбор модели определяется тем, сколько у вас сегодня бюджета, это свойство момента, оператор решает вручную.

Два механизма параллельности, и они не про одно и то же

Мультиагентные системы обычно рисуют так: старший агент нарезает задачу на подзадачи и раздаёт их субагентам подешевле, экономя контекст и деньги. Красивая иерархия, все её узнают.

Перед тем как это написать, я пошёл к своему техлиду и спросил, как у него на самом деле устроен спавн субагентов. Он начал ответ с поправки. Спавнит он редко. Основная параллельность не внутри окна, а между окнами: разработчик интеграции, аналитик, тестировщик это отдельные агенты в шине, а не его подчинённые процессы. Иерархия из популярных сейчас статей описывает не то, что здесь работает.

Механизма два, и решают они разные задачи.

Механизм первый: разные окна

Это шина, о которой вся статья. Двенадцать самостоятельных ролей, каждая со своим контекстом и своими границами, координация через доску. Параллельность здесь означает, что тестировщик проверяет вчерашний PR, пока техлид пишет сегодняшний, а аналитик готовит завтрашнее ТЗ. Они не ждут друг друга и не ждут меня.

Механизм второй: веер скептиков внутри одного окна

Спавн субагентов внутри окна не про экономию. Он окупается там, где нужно опровергнуть, а не найти.

Правило техлида, когда он спавнит:

Спавню, когда ответ требует прочесать много, а мне нужен только вывод. И не спавню, когда нужен один проверяемый факт: там раунд-трип к субагенту дороже, чем grep.

Живой пример последнего. Аналитик спросил, отдаёт ли Ozon штрихкоды и габариты товара. Техлид не стал никого спавнить, сделал два read-only вызова к реальному API на тестовом продавце и получил ответ за минуту: штрихкоды приезжают массивом, габариты только в отдельном методе, в миллиметрах и граммах, единицы явными полями. Субагент вернул бы пересказ документации, а не факт.

А вот где спавн реально окупился. Девятого июля мы делали реферальную программу: приглашённый получает скидку на первый месяц, а пригласивший дополнительные дни подписки. Это деньги, значит максимальный уровень усилий и адверсариальное ревью.

Ревью на высоком уровне усилий разворачивается в веер независимых агентов-искателей. Каждый ищет проблемы самостоятельно. А дальше самое важное: каждую находку отдельно пытаются опровергнуть. Не подтвердить, а именно опровергнуть, с презумпцией «скорее всего, показалось».

Нашлось пять настоящих ошибок с деньгами. Самая злая: обработчик платёжного вебхука пересчитывал скидку заново и отвергал валидный платёж, если ссылку на оплату выставил администратор вручную. Один ревьюер такое пропускает, потому что находка звучит правдоподобно в обе стороны. И «это баг», и «это защита от подделки» одинаково убедительны, пока не заставишь кого-то честно защищать вторую версию.

Работает тут не количество глаз. Работает то, что каждый скептик обязан опровергать, а не соглашаться. Согласиться это путь наименьшего сопротивления для языковой модели. Чтобы получить настоящую проверку, надо явно поставить задачу разрушать.

Когда субагент вернул муть

Наблюдение техлида, которое я не ожидал и которое стоит того, чтобы его повторить:

Растерянный субагент почти всегда сигнал, что я плохо поставил вопрос, а не что модель слабая. Переспавн на модели помощнее обычно воспроизводит ту же муть, только увереннее.

Поэтому дефолтная реакция [9] не эскалировать модель, а идти читать самому.

И порог, когда спавнить, а когда читать самому, оказался не в количестве файлов:

Спавню, когда не знаю, где смотреть. Читаю сам, когда знаю где, но не знаю что там.

Пример из того же дня. Техлид знал, где искать гейт подключения к внешнему API, и читал сам. Нашёл, что валидатор ключа ловит только ошибки HTTP-клиента, а специфическое исключение «у продавца кончился платный пакет API» пролетает мимо и превращается в пятисотку прямо на экране подключения. Такое субагент не находит: это надо пройти ногами по воронке пользователя.

Как я потерял два окна за один день

Двадцать восьмого мая у меня умерли два самых нагруженных окна.

Симптом: при попытке восстановить сессию API отвечает четырёхсоткой с текстом про то, что блоки размышлений в последнем сообщении ассистента не могут быть изменены. Повторная попытка даёт ту же ошибку. Восстановление подтягивает ту же самую сломанную историю, и она снова не проходит валидацию.

Размеры файлов сессий на тот момент: одно окно 25.8 МБ, второе 678 МБ.

Оба были долгоживущими окнами на Opus, которые я не перезапускал неделями. Расширенные размышления копятся в истории, история растёт, и в какой-то момент попытка её восстановить упирается в валидацию, которую эта история не проходит.

Из этого вышли три правила, ровно в таком приоритете.

Первое: не тащить всё в главное окно

Спавн субагента помогает не потому, что junior дешевле, а потому что его транскрипт остаётся в его окне. Ко мне приходит вывод, а не полторы тысячи строк, которые он прочитал по дороге.

Но это только смягчает. Решения-то всё равно копятся в главном окне. Мой техлид формулирует прямо: спавн save-state не отменяет. За один вчерашний день у него в одном окне прошло пять PR и шесть деплоев, и всё это осело в истории.

Второе: резать заранее, а не когда упёрлось

Правило записано в протокол и подставляется в контекст каждому агенту:

Контекст разросся, а задача не закончена, сохрани состояние себе в очередь.

Порог по модели: у Sonnet это шестьдесят процентов, у Opus семьдесят пять. Opus дороже платит за раздувшуюся историю, поэтому режем раньше.

Механика: агент пишет задачу самому себе с префиксом [RESUME], куда складывает где остановился, какие файлы трогал, что делать дальше. Это тот же handoff.py [4], только --to совпадает с --from.

python handoff.py --to coder-tl --from coder-tl --resume  --task "Рефакторинг адаптера, 3 модуля из 5. Дальше миграция и прогон тестов."

После очистки окно объявляет себя заново, хук показывает [RESUME] первым в инбоксе, работа продолжается.

Тонкость, которая стоила мне одного потерянного треда: очистка контекста меняет session_id. Связка «сессия → роль» протухает, окно перестаёт быть агентом. Поэтому первое сообщение после очистки снова голое имя роли.

Третье: мёртвое окно не воскрешать

Восстановление подтягивает ту же битую историю и падает так же. Но вся работа окна лежит на диске: Claude Code пишет транскрипт в .jsonl по ходу дела, не в конце.

Поэтому лечение не «попробовать восстановить ещё раз», а «прочитать транскрипт с диска, вытащить из него результаты и открыть свежее окно». Неприятно, но работает и занимает минуты.

Что показывают метрики за шестьдесят дней

Срез на десятое июля, снят той же командой, что доступна любому агенту:

КОЛИЧЕСТВО (очередь сейчас): всего 1002 · open 48 · done 954 ПОТОК (event-лог): создано 24ч=76 7д=297 · закрыто 24ч=66 7д=278 СКОРОСТЬ open→done (n=1146): медиана 47мин · средн 20.5ч · min -1мин · max 42.3д

Медиана 47 минут против среднего в 20.5 часа. Это не противоречие, а форма распределения: половина задач закрывается меньше чем за час, но есть длинный хвост из тех, что висят днями. Показал бы я только среднее, картина была бы «медленно». Только медиану, «молниеносно».

Максимум в 42 дня стоит объяснить отдельно, иначе выглядит так, будто агент две недели чесал затылок. В хвосте лежат задачи, эскалированные мне: регистрация товарного знака, решение о запуске, звонок клиенту. Агент закрывает свою часть за минуты и передаёт мне то, что без меня сделать нельзя. Дальше сроки задаёт не он и не я, а Роспатент. Между агентами задачи ходят быстро, упирается всё в человека и во внешний мир.

Минимум отрицательный, минус одна минута. Артефакт: задачу закрыл тот же скрипт, что и создал, в пределах секунды, округление уехало. Чинить там нечего, но я его показываю, чтобы было видно: вывод я не подчищал.

Чего эти метрики не измеряют, так это производительности агента. Задача «поправь опечатку» и задача «спроектируй интеграцию», обе одна строка в очереди. Они меряют здоровье системы: не копится ли, не висит ли, закрывается ли примерно с той же скоростью, с какой создаётся. Семьдесят шесть созданных против шестидесяти шести закрытых за сутки означает, что очередь медленно растёт, и стоит посмотреть, где затык.

Токены я не считаю. Цифры экономии у меня нет, и придумывать её я не буду.

Второй продукт: тот же файл, другой реестр

Примерно через месяц понадобилось то же самое для второго проекта: другой продукт, другая команда ролей, другой домен.

Изменений в коде: ноль. Реестр проектов вынесен в JSON, скрипты читают его при каждом запуске.

{ "default": "skumind", "projects": { "skumind": { "files": { "queue": "agent-handoffs.md", "events": "agent-events.jsonl" }, "agents": [ ... ] }, "second": { "files": { "queue": "data/second/handoffs.md", "events": "data/second/events.jsonl" }, "agents": [ ... ] } } }

Добавить проект значит дописать блок. Файл очереди создастся при первой записи. Идентификаторы ролей глобально уникальны, поэтому по --to скрипт сам понимает, в какой проект писать.

Единственная засада: веб-борд кэширует реестр при старте. Дописал роль, перезапусти сервер, иначе на борде её не будет, а в командной строке она уже работает. Полчаса непонимания, пока не дошло.

Что этот опыт [10] показал: доска не была решением под конкретную команду. Роли в двух проектах разные, домены разные, а структура координации одна.

Второй проект принёс и неприятное наблюдение. На борде видно, что у части ролей задачи висят неделями там, куда я давно не заходил. Доска не заставляет работать. Она честно показывает, что работа стоит, и это отдельно ценно: раньше я про эти задачи просто забывал, а теперь у них горит красный флажок с числом дней.

Чего здесь нет

Задача, записанная в очередь, не будит агента. Никакой автономности: я подхожу к окну и пингую его руками. Двенадцать ролей не работают, пока я сплю.

Это сознательный выбор. Автономный цикл, где агент будит агента, я пробовал представить и испугался. Но и честный ответ тем, кто ждёт «поставил задачу, ушёл, вернулся к результату». Ближе к этому можно подойти: очередь снимает с меня необходимость помнить и пересказывать. Заменить меня целиком пока нет, но это скорее хорошо, чем плохо. Если очередь заменит меня, зачем вообще я нужен в этой цепочке?

Что бы я сделал иначе

Не трогал бы переменные окружения. Полдня ушло на подход, который отваливается от первого же вопроса: а среда вообще различает мои окна?

Ввёл бы сохранение состояния сразу. Правило про порог появилось после того, как я потерял два окна. Могло появиться до. Механика там на пятнадцать строк.

Не строил бы три пульта. У меня их получилось три: слэш-команда, HTA-панель и веб-сервер. Пользуюсь одним. Два других лежат мёртвым грузом, и оправдание у меня только одно: я не знал заранее, каким буду пользоваться.

Спрашивал бы исполнителей раньше. Половина того, что вы прочитали про спавн субагентов, это прямая цитата моего техлида, и она разошлась с тем, как я себе это представлял. Красивую иерархию из чужих статей легко принять за описание собственной системы, если не проверить.

Вопросы, на которые у меня нет ответа

Как это масштабируется за тридцать ролей? Сейчас двенадцать, и борд ещё читается глазом. При тридцати нужна группировка и приоритет, а по какому признаку, не знаю.

Что делать с задачами, которые никому не срочны? Они не тормозят работу, но копятся, и красный флажок на них горит вечно. Автоматически закрывать страшно, вручную разбирать некогда.

Нужен ли свой инструмент теперь, когда у Anthropic есть штатные механизмы параллельных агентов? Когда я собирал шину, их ещё не было. Сейчас они есть, и мне кажется, что это про другое: они дают взрывную параллельность внутри одной задачи, а мне нужна стоячая организация из именованных ролей, которые живут неделями, помнят свои границы и держат историю переданного. Может быть, я просто привык к своему.


Всё это оснастка вокруг того, что я на самом деле делаю: SKUmind [11], аналитика прибыли по каждому SKU для продавцов на Ozon, а с этой недели и на Wildberries, интеграция уже в бете. Шина осталась внутренним инструментом, в открытый доступ я её не выкладываю.

Если вы держите несколько окон с ролями и ловите себя на том, что стали маршрутизатором между ними, то markdown-файл и двести строк на запись в него решают эту конкретную проблему за выходные. Правила, которые заставят его работать, вы будете отлаживать два месяца.

Расскажите, как координацию решали вы, если дошли до тех же граблей.

Автор: vkobzar

Источник [12]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/32940

URLs in this post:

[1] памятью: http://www.braintools.ru/article/4140

[2] CLAUDE.md: http://CLAUDE.md

[3] Логика: http://www.braintools.ru/article/7640

[4] handoff.py: http://handoff.py

[5] inbox.py: http://inbox.py

[6] board.py: http://board.py

[7] server.py: http://server.py

[8] ошибки: http://www.braintools.ru/article/4192

[9] реакция: http://www.braintools.ru/article/1549

[10] опыт: http://www.braintools.ru/article/6952

[11] SKUmind: https://skumind.ai/?utm_source=habr&utm_medium=article&utm_campaign=habr-2026-07-10

[12] Источник: https://habr.com/ru/articles/1057980/?utm_campaign=1057980&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100