Кейс 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>
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>
- portal_channel: команда cmd-settg-* (кнопка TG в UI портала) перехватывается
в pull() ДО инбокса (токен-секрет не попадает в стор/LLM), апплаер:
getMe-самотест -> атомарный .env (SIMBIOT_TG_TOKEN+ALLOWED, старый аллоулист
сохраняется) -> рестарт только simbiot-harness-tg; идемпотентно; отчёт в
чат портала (push, fallback outbox-файл); TgChannelConfigured в стор.
- isolated_runner: после TimeoutExpired kill процесс-группы воркера и ожидание
фактической смерти — без этого сабпроцессы продолжали писать BudgetSpent/
ToolCalled после SchedulerTimeout (burn 9router вхолостую, t-261002090242).
- levels: tick_timeout min 300->480 (занятый мин-уровень штатно поглощает такты;
эскалация e-261002085000). Контекст-схема не тронута (256k/512k/1M).
Тесты: test_budget_stub_guard / test_inbox_batch / test_delivery_idempotency —
зелёные; e2e на живом канале: cmd-settg consumed=1, стор чист, .env цел.
Инцидент: Alex получал презентацию трижды (m-516173239/241). Две причины:
1) гонка push (тик) vs pull (delivery-демон): оба проходили проверку
delivery_status != 'sent', пока TG-отправка (до 35с) была в полёте;
2) ad-hoc curl sendDocument из мин-тиков вообще без метки delivered.
Фикс — delivery_ledger (INSERT OR IGNORE: факт доставки раз и навсегда)
+ delivery_locks (атомарный claim ДО отправки, TTL 120с; одно SQLite-
выражение = нет гонок между потоками и процессами). deliver_result /
deliver_artifact: ledger → claim → отправка → запись; сбой → release,
демон досыплет. Requeue сбрасывает ledger (доработка доставляется заново).
Новое: cli.py tg-deliver-file (идемпотентная отправка файлов, --force),
tg-notify -k <ключ>, tg_inbox.send_document (multipart на чистом urllib),
deliver_pending pre-filter по ledger, мандаты уровней: прямые
curl/api.telegram отправки запрещены. DeliverySkipped логирует пропуски.
Тесты: tests/test_delivery_idempotency.py — 49 проверок (гонка потоков,
межпроцессный claim, сбои+ретраи, requeue, CLI). Регрессия: 6/6 тестов ОК.
Минутный тик отвечал по одному сообщению за тик (m-516173222..228 —
7 тиков подряд на один разговор): контекст показывал все, но мандат
говорил «ответь на него», и главное — нечем было закрыть несколько
входящих одним ответом (tg-reply закрывает одно, «хвосты» снова будили
гейт важности).
- tg_inbox.send_batch_reply: одна TG-отправка → ReplySent на КАЖДОЕ
входящее батча (полный текст у головного, у хвостовых — ссылка);
гонки (replied/ignored выпадают, пустой батч → ValueError), разные
чаты делят батч — каждому своя отправка
- cli tg-reply <id1>,<id2>,… -c: батч-режим; несколько id без -c — отказ
(защита от случайного закрытия); одиночный режим обратно совместим
- build_context: секция инбокса = «БАТЧ из N» с готовой командой
(все id через запятую) и инструкцией закрыть ВСЁ за этот тик; кап
10 → 50 (показываем весь батч, а не хвост)
- мандат min: батч-семантика (сводный ответ по смыслу разговора)
- тесты tests/test_inbox_batch.py: 32 проверки (hermetic), все зелёные
- события TaskAwaitDelivery / DeliverySent: флаг 'результат доставить в чат'
на задаче, доставка идемпотентна (один раз sent — повторов нет)
- commands.mark_awaiting_delivery / deliver_result (единственный путь отправки) /
complete_task_with_delivery: завершение и отправка идут парой —
«сделал и промолчал» больше не случается
- scheduler: push после TaskCompleted + delivery-демон (pull каждые 15с —
досылает, если тик умер между завершением и отправкой); requeue сбрасывает
доставку — доработка снова уйдёт в чат
- orphan_requeue: running-задачи с мёртвым воркером (рестарт демона убивает
воркеров, TaskCompleted не пишется) возвращаются в очередь автоматически;
корень эскалации e-260929234911: 'повторный таймаут часового воркера'
был убийством живого воркера рестартом в 23:12/00:03, не таймаутом
(трейс и BudgetSpent шли вплоть до секунды рестарта)
- cli: task -a (подать с доставкой) + task-await (пометить существующую);
мин-уровень помечает задачи-просьбы из TG
- 18 тестов на копии БД: submit→await→complete→deliver→idempotency→
requeue→re-deliver→pull→orphan→rebuild
Часть 1 (часовой уровень, задача t-260929155421-9f7bff):
- EscalationRaised/Acknowledged в ES-спину, проекция escalations
- CLI: escalate / escalations / escalation-ack
- уровни видят открытые эскалации в контексте; scheduler перезапускает
критическую эскалацию сразу (не дожидаясь границы такта)
Часть 2 (минутный, этот тик):
- fix(cli): 'events -n N' показывал первые N событий вместо хвоста стора
(README обещает хвост) — теперь хвост; --after N — от id N вперёд
- + es.count_events()