harness/kodi_core.py — цикл engine42/router42 бенча на решениях Коди,
первая голова addressing (отвечать ли в группе):
- опыт — подписанные события core: CoreLabel (учитель по истории;
человек — кнопки «Это было мне?» в личку, ≤5/сут), CoreFeedback
(неявно из чата: ответил, а следом «не тебе/продолжает» → 0; промолчал,
а автор тут же «@juno42bot»/«Коди?» → 1), живые вердикты учителя;
веса: учитель 1, обратная связь 3, человек 5;
- признаки: nomic-embed-text сообщения (локально, кеш) + 8 флагов;
контекстный эмбеддинг убран — шумел (5 фолдов 0.822 → 0.845 без него);
- ученик: логрегрессия (numpy, Adam, L2) — эмбеддинги без поразмерной
стандартизации (раздувала шум на сотнях примеров); kNN-память новизны,
пороги новизны калибруются на холдауте (у nomic она 0.03–0.09);
- роутер: знакомо и |p−0.5| ≥ 0.4 → ученик; незнакомо и учитель
сомневается → вопрос Alex; иначе учитель. SIMBIOT_CORE_ADDRESSING=
shadow (дефолт: решает учитель, ученик пишет CorePrediction) | on | off;
- тренировка в дневной тик: временной холдаут, претендент против
чемпиона по log-loss, CoreModelPromoted с sha256; подмена файла →
ученика нет; cli.py core status|train|backfill|feedback|rollback.
Репетиция на истории правления (259 примеров): холдаут — совпадение с
учителем 73%, уверенная доля 4% при 100% совпадения; 5 фолдов при
CONF 0.4 — покрытие 25%, совпадение 95%. До включения нужны метки.
Поллер: callback_query (allowed_updates явно). numpy — в requirements.
Тест: test_kodi_core 28 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Жалоба Alex: «реплаю на сообщения Коди — не реагирует». Триаж реплай на
бота распознавал (07:57, rule=reply_to_bot, ответ через 25 с), но:
- set_reaction слал reaction_type одиночным объектом вместо reaction —
массива ReactionType: Telegram видел пустую реакцию — в личке молча
«снимал реакции» (ok → MessageSeen, 596/нед, 👀 не было видно), в
группе 400 REACTION_EMPTY. Тест test_seen_indication закреплял баг —
исправлен под Bot API;
- ответ в группе уходил отдельным сообщением: теперь reply на сообщение
(батч — на последнее, досылка --followup — на исходный вопрос) с
allow_sending_without_reply; личка — как было;
- edited_message уже принятого сообщения писался новым входящим
(m-516174914/915 — исправленная опечатка дала дубль в батче) — пропуск.
Тест: test_tg_group_ux 10 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Кейс 2026-10-17 07:40: фильтр отсёк «Блять)»/«Продолжает)», но правило
«имя в тексте = обращение» ответило на «Так теперь Коди должен отвечать
когда его тегают…» и «Ещё и забыл что он Коди)» — люди обсуждали бота.
- детерминированно — только звательное: «Коди, …», «Коди!», «…, Коди»;
имя посреди фразы — серая зона;
- промпт decision-модели: «Коди» = Cody, речь о нём в третьем лице —
не обращение. Пробы: 8/8 (о боте ≤ 0.14, к боту ≥ 0.96); реплей
истории правления — оба утренних ложных ответа ушли (0.40/0.13),
разговор Alex с ботом без тега по-прежнему распознаётся;
- мандат мин-уровня: «Коди» в чате — это ты (бот писал о себе в
третьем лице: «Коди … на вашей стороне»).
Тест: test_addressing 31 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- старые записи инбокса без chat_type (до t-261013113629) считались
личкой и обходили фильтр адресации — группа теперь и по chat_id < 0;
- утренний холодный старт clef:27b в облаке (> 15 с) взводил
предохранитель на 5 минут → ответы от CPU-nimble по 10 с: облако
ждём до 30 с, его кулдаун 60 с (локальной — по-прежнему 5 минут).
Реплей реальной истории «Правления» (62 сообщения, бот отвечает только
на адресованное, облако ~0.66 с): приветствия и переписка людей между
собой отфильтрованы; разговор Alex с ботом без упоминания распознан
по контексту; спорные — 2–3 в каждую сторону, «Коди»/@/reply — всегда.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Кейс «Правления»: бот отвечал на каждое сообщение группы — «Доброе
утро!», реплики Alex для Vlad, ответы Vlad на вопросы Alex: фильтра не
было, любое сообщение разрешённого человека будило минутный LLM.
harness/addressing.py — триаж ДО гейта, без LLM:
- личка/портал/агенты — как раньше;
- группа, детерминированно: @juno42bot, reply на бота, имя (Коди/Cody,
SIMBIOT_BOT_NAMES) → боту; reply на человека → не боту;
- серая зона — decision-модель, choice «кому адресовано»: бот / человек /
группа; P(бот) ≥ 0.5 (SIMBIOT_ADDRESSING_MIN_P). В истории — только
ответы бота на адресованное: «встревания» сбивали модель. Замер на
истории правления: адресованное 0.93, остальные 14 ≤ 0.22 (noul с
полной историей давал 4 ошибки). Модель недоступна → молчим.
Вердикт: MessageTriaged{addressed} (+ 👀 только тогда) или
MessageIgnored{not_addressed}. Поллер пишет reply_to_user_id/is_bot и
mentions_bot; 👀 в группах ставит триаж, а не приём.
Тест: test_addressing 25 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
harness/compact.py + cli.py compact [--dry-run|--vacuum|--verify-archives]:
шум сек-уровня (CycleTicked sec, ReflexObserved без алерта/команд/каналов)
старше часа → архив jsonl.gz (0600, строки целиком со sig) → мост цепочки
(подписи унесённых строк, на которые ссылаются остающиеся, + маркеры
boundary:<id> для legacy-границ) → подписанный StoreCompacted с sha256
архива/строк/моста → удаление пачками. audit.verify_events принимает мост
только при совпадении хеша с подписанным событием; проекции подгружают
мост лениво, если хвост перекрыл сжатый диапазон (rebuild с нуля).
Репетиция на копии прода: 1389 → 200 МБ, verify 484 с/3.4 ГБ → 29 с/420 МБ,
архив 246 МБ — все 2.47 млн подписей проверены; rebuild_projections OK.
Найдено репетицией: legacy-граница (id 466633) и коллизия id сжатия в
одну секунду — оба покрыты тестом. scripts/compact-store.sh — процедура
с VACUUM под остановленными писателями.
мин-уровень: если до ответа нужна работа >30 с — сначала короткий
tg-reply, итог — cli.py tg-reply <id> --followup (в чат входящего:
группа → группа; идемпотентно по тексту). p90 «старт LLM → ответ» был 223 с.
hour: лимит 300 вызовов оставлен — реальный пик 205 (p90 169), 100
оборвал бы 8 из 19 тиков.
Тесты: test_compact 25 PASS, test_followup 10 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- фото/документ/видео без подписи выпадали из триажа (_pending_inbox и
build_context пускали только текст/голос): висели до 23 ч, все 4
неотвеченных за неделю — такие. Один фильтр _is_pending, медиа с тегом
и путём файла; окно свежести SIMBIOT_MEDIA_PENDING_H=6 — старые не
будят LLM задним числом; стикер без текста — не повод.
- MinNudge: мин-тик через 3 с тишины после MessageReceived/Transcribed, не
ждём границы минуты (было: медиана 36 с до старта LLM, p90 93 с при
24 с самой модели). Слот следующего такта, граница его пропускает;
SIMBIOT_MIN_NUDGE=0 — выключить.
- сек-уровень: CycleTicked+ReflexObserved каждую секунду = 1.35 млн
подписанных событий/нед (98% стора 1.37 ГБ). Теперь heartbeat раз в
60 с + такты с событием (алерт, команды, каналы); «reflex ok» не
спамит журнал.
- 6 тестов писали в боевой стор под SIMBIOT_HOME тика (TTFTWatchdogRetry
с бюджетом 0.5 с в проде — это они): изоляция SIMBIOT_DB.
Тест: tests/test_tick_latency_noise.py 20 PASS; полный набор — без регрессий.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Разрешены конфликты: isolated_runner (тщательный upstream-вариант killpg-wait), cli.py task-диспетчеризация upstream + guard ValueError, aum.py paths-изоляция + STORE_DB fallback.
Тесты после мерджа: 5 устарели к новой реальности — починены по интентам:
- test_aum 9c: hermetic-мок OPENROUTER_KEY (ключ уехал со старого хоста)
- test_router_env_fallback: патчит paths.env_path (runtime больше не читает _HARNESS_DIR)
- test_submit_title_guard: контракт task list = показ доски (exit 0), а не ERROR
- test_tg_commands: доска задач от тестовой ready-задачи (в прод-копии ready пусто)
- test_instance_isolation: SKIP RAG-контура без вендора /root/rag (хост мигрировал)
Итог: 19/19 зелёные
- voice.py: kind=video в TRANSCRIBABLE_KINDS; extract_audio_track() — ffmpeg
вытягивает дорожку (моно 16к opus) из видеоконтейнера; речь ≤60с
(voice/video_note/video) сразу идёт в faster-whisper-large-v3-turbo
(base на видео давал кашу). E2E: m-516173698 расшифрован.
- tg_inbox.py: поллер транскрибирует и msg.video, не только voice/audio/video_note.
- tools/tui_chat.py: TUI-чат (t-261004132716) лежал только у Volya (33301d3) —
в origin его не было, из-за этого терминал не грузился у 6 агентов.
Тестовое TUI-сообщение (t-261004132716, chat_id=tui:local) стало
последним в inbox → notify_chat/send_document целились в несуществующий
Telegram-чат → уведомления тихо SKIP'ались при живом чате. Фильтр
локальных каналов расширен: portal:* И tui:*.
Слепой TaskPromoted/TaskFailed по списку claimed возвращал в очередь
задачи, уже закрытые воркером ВНУТРИ тика до таймаута (кейс: полная
миграция Гермеса→Vybirai complete+delivery в 14:31, тик убит 14:48,
задача воскрешена — двойная работа и риск дублей).
_task_still_active(): живая проекция как истина; только running
ре-кьюится/фейлится. Ошибка чтения → прежнее поведение (True).
Воркер мог сам решить судьбу задачи: complete (done), requeue (ready),
close в архив (closed). Дайджест тика больше не перезаписывает его решения
TaskCompleted'ом — иначе requeue'd задачи нечестно закрывались бы дайджестом
(поверх проверенного результата «последний-победил»). found при e-261003094432
- es: TaskClosed → статус closed (терминальный, обратимый через requeue; closed_by manual|archive)
- commands: close_task (ручной, только failed, идемпотентен), archive_stale_failed(max_age_hours=24, now=None) — демон-архиватор
- scheduler._delivery_and_orphans_loop: каждый проход (15с) архивирует failed>24ч
- requeue принимает closed → ready (доработка из архива)
- cli: simbiot close <id> [-r reason]
- dashboard: порядок ready→running→failed→done→архив, счётчик «архив» в статусбаре, схема закрытых фейлов под одной строкой-счётчиком (тумблер показывает полностью), pill.closed
- тест tests/test_close_stale_failed.py: 15 проверок (закрытие, порог через симуляцию +25ч, done не трогается, идемпотентность, полный цикл closed→ready→failed→closed)