36 KiB
Архитектура Simbiot Harness (Коди)
17.10.2026 · копия документа https://claude.ai/code/artifact/195fd476-44e7-47cc-9093-2d41ea53c8c9 (живая версия — там, со схемой)
Обзор
Simbiot Harness — ритмичный агент кооператива: он живёт тактами (секунда, минута, час, день), а в Telegram отвечает как Коди (@juno42bot). Главная метрика — долгосрочный AUM кооператива: каждое решение проверяется вопросом «добавит ли это ≥1% к AUM на горизонте год+».
Архитектура держится на пяти принципах:
- Всё — события. Единственный источник правды — append-only стор событий; состояние (read model) — проекция. Каждое событие подписано ed25519 и сцеплено с предыдущим.
- Дешёвое решает раньше дорогого. Детерминированные правила и локальные/облачные decision-модели отсекают лишнее до запуска LLM: гейт важности, фильтр адресации, выбор глубины памяти.
- LLM — в изоляции. Каждый LLM-тик — отдельный процесс с бюджетом шагов, таймаутами и стражем прав на каждом вызове инструмента.
- Доверие — по личности, не по тексту. Права тика определяет самый недоверенный вход его контекста.
- Учится на исходах. Ядро Коди собирает решения и их последствия, обучает маленьких учеников и допускает их в бой только при честной победе над базовой линией.
Куб 4×4×4 задаёт поведение одной таблицей политик: 4 круга доверия × 4 тика × 4 глубины памяти. Это не 64 программы, а один код и параметры.
Telegram ──► TG-поллер ──► Адресация ◄┄┄ Decision-модель (clef:27b → nimble)
Портал/агенты ──────────► │
▼
Гейт важности
▼
Круг доверия и память ◄┄┄ Память и ядро (индекс nomic, ученики)
▼
Человек (cli, TUI) LLM-воркер ──────► 9router и портал MCP
│ │
▼ ▼
Стор событий: подписи ed25519 · проекции · сжатие с архивом · каждый шаг пишет события
Процессы и сервисы
Харнес — два долгоживущих процесса на одном сторе событий плюс короткоживущие LLM-воркеры; код — /root/simbiot-harness, данные инстанса — /var/lib/simbiot-harness (SIMBIOT_HOME).
| Процесс | Запуск | Что делает |
|---|---|---|
| Лестница | simbiot-harness.service → python -m harness.scheduler |
такты sec/min/hour/day в отдельных потоках, демон доставки и сирот, триаж, ночная тренировка ядра |
| TG-поллер | simbiot-harness-tg.service → python -m harness.tg_inbox |
long polling @juno42bot: входящие, файлы, расшифровка голоса, кнопки |
| LLM-воркер | harness/_llm_worker.py, сабпроцесс на каждый LLM-тик |
агент Strands с инструментами, страж прав, бюджет шагов |
| Дашборд | simbiot-dashboard.service, HTTP 8765 |
доска задач и тиков, только чтение стора |
| CLI | cli.py (и обёртка simbiot) |
команды агентов и людей: ответы, задачи, эскалации, ядро, память, доверие |
Каталоги инстанса (/var/lib/simbiot-harness):
| Путь | Содержимое |
|---|---|
db/harness.db |
стор событий + read model + журнал доставок (SQLite, WAL) |
env/.env |
секреты и флаги (600) |
audit/ |
ключ ed25519 харнеса |
archive/ |
сжатые события (jsonl.gz, подписаны) |
core/ |
чемпионы учеников ядра + кеш эмбеддингов |
memory/ |
индекс памяти Коди |
inbox/ |
файлы из чатов |
Внешние зависимости:
| Система | Зачем |
|---|---|
9router (127.0.0.1:20128) |
LLM для тиков, модель auto |
| Ollama Cloud | decision-модель clef:27b |
Ollama локально (127.0.0.1:11434) |
запасная decision-модель nimble, эмбеддинги nomic-embed-text |
| Портал cil.by (MCP) | звонки, реестры, канал агент↔агент |
| Telegram Bot API | чаты Коди |
| Deepgram | расшифровка голосовых |
RAG /root/rag |
поиск по базе знаний кооператива |
Event Sourcing и криптоаудит
Всё состояние харнеса выводится из подписанной цепочки событий (harness/es.py, harness/audit.py); после сжатия 17.10 стор — 200 MB, полная проверка — около 30 с.
Стор. Таблица events (aggregate, aggregate_id, event_type, data, timestamp, version, sig, key_id, prev_sig). Уникальность (aggregate, aggregate_id, version) делает запись идемпотентной: тик level:seq не произойдёт дважды. Клейм задачи — атомарный (claim_task_if_ready, BEGIN IMMEDIATE), поэтому два уровня не возьмут одну задачу.
Проекции. run_projections() инкрементально строит read model (задачи, инбокс, эскалации, дайджесты, круги доверия, политика AUM) и сохраняет снапшот в read_model. Перед применением хвост проверяется подписями: подделка останавливает проекции.
Подписи. Каждое событие подписано ключом харнеса (ed25519, key_id faf8d19d9aed71fe), prev_sig ссылается на подпись более раннего. События до внедрения подписей помечены genesis:<id>, неподписанные — legacy-границы. Защита от чужого ключа: стор, уже подписанный одним ключом, другим не подписывается. Полная проверка — cli.py verify.
Сжатие с подписью (harness/compact.py, scripts/compact-store.sh). Шум сек-уровня старше часа уходит в архив:
- Строки целиком, со всеми подписями, пишутся в
archive/events-<first>-<last>-<id>.jsonl.gz(600). - Подписи удалённых строк, на которые ссылаются остающиеся, и маркеры
boundary:<id>для legacy-границ попадают в мостcompaction_bridge. - Подписанное событие
StoreCompactedфиксирует sha256 архива, строк и моста. - Удаление идёт пачками по 20 000 строк без остановки сервисов; VACUUM — под остановленными писателями (секунды).
verify принимает мост, только если его хеш совпадает с подписанным StoreCompacted; архивы проверяются построчно (cli.py compact --verify-archives). Первое сжатие: 2 886 765 строк в архив (246 MB), 1389 → 200 MB.
Сек-уровень больше не шумит: CycleTicked/ReflexObserved пишутся heartbeat'ом раз в 60 с и в такты с событием (алерт, команды, каналы).
Тактовая лестница
Четыре уровня тикают независимо, каждый в своём потоке (harness/scheduler.py, harness/levels.py); дорогой LLM-тик запускается, только если есть событие важнее порога уровня.
| Уровень | Интервал | LLM | Порог гейта | Задачи | Шагов на тик | Таймаут | Память по умолчанию |
|---|---|---|---|---|---|---|---|
| sec | 1 с | нет | — | — | — | — | — |
| min | 1 мин + по входящему | effort low, контекст 256k | critical | S | 30 | 480 с | D1 |
| hour | 1 ч | effort medium, 512k | warn | S, M, L | 300 | 2880 с | D2 |
| day | 1 д | effort high, 1M | warn | S, M, L | 10 000 | таймаут уровня | D3 |
sec — рефлекс без LLM. Телеметрия и алерт нагрузки, мгновенные команды бота (/лестница, /статус, /задачи), каналы портала и агент↔агент.
Гейт важности. Сигналы: входящее без ответа и готовая задача — critical; эскалации — по их severity; высокая нагрузка — warn. Ниже порога тик закрывается детерминированно (GateSkipped + заглушка-дайджест). Серая зона (только warn/info) спрашивает decision-модель — сейчас в режиме shadow.
Тик по входящему. Демон раз в секунду смотрит хвост стора; через 3 с тишины после MessageReceived или MessageTranscribed запускает внеочередной мин-тик в слот следующей минуты. Ответ в Telegram приходит за 10–40 с вместо ~80 с медианы до нуджа.
Батч задач. Уровень берёт все ready-задачи своих классов в бюджете контекста: из 9 старейших первой ставится самая ценная для AUM (decision-модель), остальные — FIFO. В один батч попадают задачи одного круга доверия, и уровень берёт только обслуживаемые круги.
Эскалация недоделанных. «Бюджет исчерпан» или смерть воркера возвращают задачу в очередь; после 2 захватов на уровне она растёт до размера, которого этот уровень не берёт (S → M, к часу). До этого S-задача могла браться минутным уровнем 33 раза подряд. Задачи про внешние системы (портал, сервер, деплой, UI, git) сразу уходят часу — правило головы task_fit.
Занятый уровень поглощает тик (TickAbsorbed), запущенная задача не прерывается, наверх идут только дайджесты и эскалации (cli.py escalate). Демон доставки возвращает задачи-сироты после рестартов и доставляет результаты в чат идемпотентно (журнал доставок).
LLM-воркер
Каждый LLM-тик — отдельный процесс (isolated_runner.py → _llm_worker.py) со свежим asyncio-рантаймом Strands: падение или таймаут не роняют лестницу, а вся группа процессов добивается по таймауту уровня.
Вход воркера (payload): мандат уровня, собранный контекст тика, модель и effort, флаг shell, круг доверия тика. Результат возвращается JSON-файлом; трассу (InvocationStarted, ToolCalled, ToolResult, BudgetSpent) воркер пишет в стор сам и инкрементально, чтобы таймаут не съел учёт расходов.
Инструменты:
| Инструмент | Ограничение |
|---|---|
shell, read, write, edit |
только уровни с can_shell; в кругах ≥ 2 — страж прав |
rag_lookup |
1 запрос на тик, k ≤ 5, таймаут 20 с |
portal_mcp |
4 вызова на тик, деньги-тулы запрещены по умолчанию |
cli.py … через shell |
основной способ действовать: tg-reply, task, complete, escalate, tg-deliver-file, decide |
Хуки на каждом вызове (BeforeToolCallEvent, до исполнения):
- Страж прав —
trust.check_tool: запрещённый вызов отменяется (cancel_tool) и пишетсяTrustDenied. - Бюджет шагов — сверх лимита уровня вызовы отменяются, цикл завершается итоговым текстом; задачи без
completeвозвращаются или эскалируются. - Трасса —
ToolCalled/ToolResultс trace_id, уровнем и длительностью.
TTFT-сторож. Если апстрим 9router принял запрос, но за 45 с не прислал первый токен, стрим закрывается и делается новый вызов (до 3 попыток). Начавший отвечать стрим не трогается.
Окружение. SIMBIOT_TICK_CIRCLE наследуют shell и cli.py: задачи из тика штампуются его кругом, а cli.py trust set из тика отказывает.
Telegram-канал
Коди в Telegram — бот @juno42bot (id 7245454391); поллер пишет входящие в стор, а отвечает минутный (реже часовой) тик через cli.py tg-reply.
Приём (harness/tg_inbox.py). Long polling (message, edited_message, callback_query). Отправитель вне SIMBIOT_TG_ALLOWED → MessageIgnored(not_allowed). Документы и фото сразу сохраняются на диск, голосовые расшифровываются. Сохраняется, на чьё сообщение сделан reply и упомянут ли бот. Правка уже принятого сообщения не становится новым входящим.
Адресация в группах (harness/addressing.py) — до гейта, без LLM:
- Личка, портал, агенты — всегда адресовано.
- В группе «да» без модели:
@juno42bot, reply на сообщение бота, звательное обращение («Коди, …», «…, Коди», «Коди!»). - «Нет» без модели: reply на сообщение другого человека.
- Серая зона — decision-модель: «кому адресовано — боту, человеку или группе», P(бот) ≥ 0.5. Разговор О Коди в третьем лице обращением не считается. Модель недоступна — бот молчит.
Вердикт: MessageTriaged{addressed} + 👀 или MessageIgnored(not_addressed) — тогда ни один уровень его не видит. В тени рядом работает ученик ядра addressing.
Ответы. В группе — reply'ем на последнее сообщение батча (allow_sending_without_reply), в личке — без цитаты. Сводный ответ на несколько сообщений — tg-reply m-1,m-2 -c. Правило «сначала ответ, потом работа»: если нужно больше ~30 с, сначала короткий ответ, итог — tg-reply <id> --followup в тот же чат (идемпотентно по тексту).
Реакции. 👀 ставится при приёме в личке и триажем в группе — только на адресованное боту. Параметр Bot API — reaction (массив); до 17.10 ошибка в имени параметра делала реакцию невидимой.
Журнал исходящих. Каждое сообщение бота → OutboundMessage (chat:message_id → полный текст). Reply на сообщение бота получает reply_to_full (до 4000 символов вместо 300 от Telegram), а тик видит блок «ЭТО ОТВЕТ НА ТВОЁ СООБЩЕНИЕ». Собственные рассылки бота за 48 ч попадают в память тика.
Кнопки. core:… — «Это было мне?» для ученика ядра; trust:… — «Разрешить полные права», нажать может только владелец.
Decision-модель
Решения «выбрать один из нескольких» и «да/нет» принимает не LLM, а decision-модель Ollama /v1/systemone (harness/decision.py): облачная clef:27b отвечает за 0.6–0.9 с, запасная локальная nimble (qwen3.5-9B Q8_0, CPU) — за 10–30 с.
Контракт. choice — варианты объектом {ключ: описание}, ответ — ключ и распределение вероятностей; noul — P(«да»). Шортлист строго ≤ 9 вариантов: по замерам systemone-bench точность падает со 100% на 6 до 50% на 10. confidence — концентрация распределения, а не вероятность правильности.
Надёжность. Бэкенды опрашиваются по порядку, у каждого свой предохранитель: облако — таймаут 30 с, после сбоя пауза 60 с; локальная — 90 с и 5 минут. Ответы детерминированы, поэтому кешируются на час по хешу вопроса и состояния. Каждый реальный вызов → DecisionMade (бэкенд, модель, латентность, применено ли). Модель недоступна — вызывающий берёт свой детерминированный фолбэк.
| Потребитель | Вопрос | Режим | Фолбэк |
|---|---|---|---|
| адресация в группах | кому адресовано сообщение | on | молчать |
| порядок батча задач | какая из 9 старейших ценнее для AUM | on | FIFO |
| тема инициативы | что сейчас полезнее предложить | on | по кругу |
| серая зона гейта | нужен ли дорогой LLM-тик | shadow | порог |
| агент в тике | cli.py decide -q … -o ключ=описание |
on | — |
Режимы: SIMBIOT_DECISION=off|shadow|on, точечно SIMBIOT_DECISION_<ПОТРЕБИТЕЛЬ>; ключ облака — SIMBIOT_DECISION_API_KEY в .env инстанса. Статус и статистика — cli.py decide, cli.py decide --stats.
Ядро Коди
Ядро (harness/kodi_core.py) — цикл самообучения по образцу systemone-bench (engine42/router42): учитель и правила решают, маленький ученик учится на их решениях и реальных исходах и получает право решать сам только после честной проверки. Сейчас у ядра три головы.
| Голова | Решение | Метки | Режим | Состояние на 17.10 |
|---|---|---|---|---|
addressing |
отвечать ли в группе | учитель (вес 1), реакция чата (3), кнопки владельца (5) | shadow | чемпион на 262 примерах |
task_fit |
влезет ли S-задача в мин-тик | исходы задач из истории | on (правило «внешние системы») | ML-чемпион снят: не бил базу; действует правило (точность 0.74) |
depth |
нужна ли глубокая память | решения правил (1), нехватка памяти по трассе (3) | shadow | копит данные, нужно 40 примеров |
Ученик. Признаки — эмбеддинг nomic-embed-text (локально, ~70 мс, с дисковым кешем) плюс флаги головы. Модель — логистическая регрессия на numpy (Adam, L2): на сотнях примеров она устойчивее MLP. kNN-память даёт «новизну»; её пороги калибруются при обучении.
Роутер. Знакомая ситуация и |p − 0.5| ≥ 0.4 → решает ученик; незнакомая и учитель сомневается → вопрос владельцу кнопками (≤ 5 в сутки); иначе — учитель. В shadow ученик только пишет CorePrediction.
Честный допуск (ночная тренировка в дневной тик, cli.py core train):
- Чемпион помнит
trained_upto; с претендентом сравнивается только на свежих данных (≥ 10), которых он не видел. - Претендент обязан бить базовую линию — константу, равную доле класса.
- Чемпион, который на свежих данных хуже базы, снимается (
CoreModelRetired) — ученик молчит. - Смена чемпиона — подписанное
CoreModelPromotedс sha256 файла; подменённый файл не грузится; откат —cli.py core rollback --head <голова>.
Рефлексия. Раз в сутки: ≥ 3 расхождений адресации с метками человека/чата или ≥ 3 S-задач, не влезших в мин-тик, → задача часу с автодоставкой: разобрать случаи и предложить владельцу правило. Первая рефлексия (17.10) привела к правилу task_fit «внешние системы → сразу к часу».
Недельный отчёт. По понедельникам владельцу в личку, без LLM, идемпотентно по неделе (cli.py core report).
Куб 4×4×4: круги доверия
Поведение задаёт одна таблица POLICY (harness/trust.py): круг доверия определяет инструменты, уровни времени и потолок глубины памяти. Вторая ось — тики (sec/min/hour/day), третья — глубина D0–D3.
| Круг | Кто | Shell | Файлы | Портал MCP | Уровни | Память до |
|---|---|---|---|---|---|---|
| 1 владелец | Alex, локальный терминал, система (тик без внешних входов) | полный | полный доступ | всё (деньги — по флагу) | все | D3 |
| 2 правление | Vlad, Yura | cli.py: ответы, задачи, эскалации, доставка файлов, обзор доски |
чтение docs/out/sites/artifacts и присланного | звонки, реестры, фичи; без казны и member_create | min, hour, day | D3 |
| 3 свои | участники портала, MCP-агенты, партнёры | cli.py: ответы, задачи, эскалации |
нет | запись на звонок, проекты, фичи | min, hour | D2 |
| 4 остальные | прочие Telegram-пользователи | cli.py: только ответ и эскалация |
нет | нет | min | D1 |
Как определяется круг. Только по проверенной личности: Telegram user_id, канал MCP-агента или портала, локальный терминал. Текст сообщения прав не даёт. Меняет круг только человек на сервере (cli.py trust set <субъект> <1-4>) — из LLM-тика это запрещено и стражем, и самим CLI.
Круг тика = самый недоверенный вход его контекста (входящие + задачи батча). Задачи наследуют круг тика, в котором поданы: просьба правления не исполняется позже часовым уровнем с правами владельца.
Страж проверяет каждый вызов инструмента до исполнения. Shell в кругах 2–4 — одна команда cli.py из белого списка круга, без ; && | > $() и обратных кавычек; файлы с секретами закрыты всегда. Задача правления упёрлась в права → владельцу приходят кнопки «Разрешить / Нет»; «Разрешить» даёт задаче круг 1.
Вживую проверено на реальном воркере в круге 2: cat /etc/hostname отменён до исполнения, cli.py decide --stats выполнился.
Динамическая память D0–D3
Перед каждым LLM-тиком харнес выбирает, сколько собственной истории подгрузить (harness/memory.py, SIMBIOT_MEMORY=on); индекс на 17.10 — 1650 разговоров, 270 итогов задач, 91 дайджест.
| Глубина | Что в контексте | Когда |
|---|---|---|
| D0 | только текущие входящие | все реплики односложные: «ок», «спасибо» |
| D1 | + 12 последних реплик чата + свои сообщения за 48 ч | минутный тик по умолчанию |
| D2 | + до 8 эпизодов из своей истории по смыслу (≤ 5000 символов) | час; «помнишь», «вчера», «как договаривались»; задачи в батче; reply на сообщение бота старше 48 ч |
| D3 | + до 15 записей архива (≤ 12 000 символов) | день; «за месяц», «что ты делал», «итоги», «план на» |
Потолок задаёт круг: «свои» — до D2, «остальные» — до D1.
Индекс (<home>/memory/index.sqlite) инкрементально догоняет стор перед поиском (~3.5 с, только чтение):
conv— сообщение + ответ Коди (в батче — ответ головы);out— собственные рассылки бота, не ответы;task— задача и её итог;digest— содержательные дайджесты часа и дня (заглушки отсеиваются).
Приватность. У каждой записи — круг: сказанное в чате видно всем его участникам, поэтому круг записи = самый недоверенный участник чата (личка владельца — 1, «Правление» — 2); задача — её круг; дайджест — 1. Поиск отдаёт только записи с кругом ≥ круга тика: тик с сообщением правления не поднимет личку владельца. Круги читаются прямо из событий TrustSet, чтобы отставшая проекция не дала записи слишком широкий круг.
Релевантность. Эмбеддинги nomic-embed-text (search_document / search_query). Калибровка на прод-индексе: релевантное — 0.78–0.88, посторонний запрос — до 0.75. Порог 0.76 и окно 0.07 от лучшего совпадения; свежий разговор того же чата (уже в D1) не дублируется.
Обучение выбора. Решение о глубине → DepthChosen с причинами и признаками нехватки памяти по трассе: агент полез в базу знаний или в свой стор, ответил «не помню». Это сырьё для головы ядра depth.
Конфигурация, CLI, деплой и тесты
Настройки читаются из env процесса, затем из /var/lib/simbiot-harness/env/.env; новые подсистемы по умолчанию выключены или в shadow, а на проде включаются строкой в .env.
| Переменная | Прод | Что делает |
|---|---|---|
SIMBIOT_DECISION |
on | decision-модель (гейт — shadow) |
SIMBIOT_DECISION_API_KEY |
задан | облако clef:27b, запасная — локальная nimble |
SIMBIOT_MEMORY |
on | индекс памяти и блоки D2/D3 |
SIMBIOT_CORE_ADDRESSING |
shadow | ученик адресации |
SIMBIOT_CORE_TASK_FIT |
on | сразу к часу задачи про внешние системы |
SIMBIOT_CORE_DEPTH |
shadow | ученик глубины памяти |
SIMBIOT_TG_ALLOWED |
Alex, Yura, Vlad | кто вообще обрабатывается |
SIMBIOT_MIN_NUDGE |
on | тик по входящему |
SIMBIOT_TASK_ATTEMPTS_PER_LEVEL |
2 | попыток до эскалации размера |
SIMBIOT_MAX_TOOL_CALLS_<УРОВЕНЬ> |
min 30, hour 300 | бюджет шагов тика |
CLI (cli.py, для людей и агентов в тике):
| Группа | Команды |
|---|---|
| чат | tg-reply [--followup], tg-notify -k, tg-deliver-file, tg-list, tg-seen |
| задачи | task, tasks, complete, block, promote, requeue, close |
| уровни | ladder, status, escalate, escalations, escalation-ack |
| решения | decide, decide --stats |
| ядро | core status / train / backfill / feedback / reflect / report / rollback / retire [--head] |
| память | memory sync [--all] / stats / search "…" --circle N |
| доверие | trust list, trust set <субъект> <1-4> (только человек) |
| стор | events, verify, compact [--dry-run / --vacuum / --verify-archives] |
Деплой. Код — ветка master в git.cil.by/symbiot/simbiot-harness. Перезапуск лестницы — только scripts/safe-restart.sh: он ждёт, пока закончится идущий LLM-воркер, чтобы задача не застряла в running. TG-поллер перезапускается напрямую: Telegram копит апдейты. Сжатие стора — scripts/compact-store.sh.
Тесты. tests/test_*.py — скрипты (./venv/bin/python tests/<имя>.py), каждый со своим временным стором (SIMBIOT_DB). Внешние системы подменены: фейковый /v1/systemone, эмбеддинги «мешок слов», заглушки Telegram. Перед каждым коммитом гоняется полный набор и сравнивается с предыдущим прогоном. Несколько старых тестов (test_tg_commands, test_reply_routing, test_portal_mcp, test_tool_budget) падают и на чистом коде — они зависят от живого состояния.
Известные ограничения и дорожная карта
Главное открытое место — косвенные каналы между кругами и автономность часового уровня в круге 1.
Ограничения:
- Выжимки нижних уровней считаются внутренними. Пересказ сообщений правления в дайджесте попадает в часовой и дневной тик с кругом 1.
- Часовой уровень в круге 1 может менять код сам. Рефлексия 17.10 закоммитила правило вопреки «код не меняй».
- Адресация ошибается на коротких репликах без контекста. «Выбирай любую.», сказанное боту, фильтр пропустит; выход — «Коди, …» или reply.
- Ученики пока слабые.
addressingна свежих данных совпадает с учителем в 71% и уверен в ~10% случаев; ML-версияtask_fitне бьёт базу;depthещё не обучался. - Расход токенов часового уровня — в среднем 46k токенов на вызов, до 205 вызовов инструментов на тик. Лимит 300 оставлен по решению владельца.
- Уровни неделя/месяц/квартал/год пока не работают; куб сознательно сжат до 4 тиков.
Дальше:
- Рефлексия и другие задачи «только предложить» — в ограниченном круге, внедрение после «да» владельца.
- Круг приватности для дайджестов — закрыть косвенный канал.
- Команда в боте для смены кругов (только владелец), чтобы не ходить на сервер.
- Решение по ученику адресации по цифрам недельного отчёта.
- Режим on для серой зоны гейта — по статистике shadow.
- Уровни неделя и месяц на основе D3 и дайджестов дня.