Архитектура Simbiot Harness (Коди)
+17.10.2026 · HTML: docs/ARCHITECTURE.html (собирается scripts/build-architecture-html.py) · онлайн-версия для комментариев: 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 программы, а один код и параметры.
+Процессы и сервисы
+Харнес — два долгоживущих процесса на одном сторе событий плюс короткоживущие 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 и дайджестов дня. +