simbiot-harness/tests/test_kodi_core_reflect.py
Simbiot Harness ebcfe3b9be ядро Коди: эскалация недоделанных задач, голова task_fit, рефлексия, недельный отчёт
Разбор гейта (трассы с 15.10): hour — 0% пустых тиков, min — 6% (15%
токенов уровня) → модель-гейт не нужен. Пустые мин-тики — зацикливание:
«бюджет исчерпан» возвращал S как S, мин брал её снова с нуля (S-задача
33 захвата за 2 ч; 45 из 272 задач ≥ 2 мин-захватов).
- requeue_or_escalate: после 2 захватов на уровне (SIMBIOT_TASK_ATTEMPTS_
  PER_LEVEL) размер растёт выше классов уровня (S→M, к часу, 300 шагов) —
  и для guard «бюджет исчерпан», и для смерти воркера; счётчик попыток
  best effort (сбой → обычный requeue, не ломает путь падения воркера);
- kodi_core — несколько голов (общие: хранение/обучение/промоут/откат,
  nf в модели; SIMBIOT_CORE_<ГОЛОВА>). Голова task_fit: влезет ли S в
  мин-тик (метки — из истории задач); shadow — прогноз перед первым
  мин-захватом, on — уверенное «не влезет» сразу S→M. Честно: на 93
  задачах 5 фолдов 0.58–0.62 при базе 0.52, уверенных 0% — пока инертна;
- рефлексия (как reflexion.py): ≥ 3 расхождений вердикта адресации с
  меткой человека/чата или ≥ 3 S-задач, не влезших в мин → задача часу
  с автодоставкой: разобрать и предложить Alex правило (код не трогать);
- недельный отчёт ядра (понедельник, без LLM, идемпотентно по неделе);
  ночью: train_all → reflect → weekly_report; cli.py core reflect|report|--head.
Тесты: test_task_escalation 8, test_kodi_core_tasks 13, test_kodi_core_
reflect 10, test_kodi_core 28 PASS; полный набор — без регрессий.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:32:41 +00:00

89 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""Ядро Коди: рефлексия и недельный отчёт — hermetic.
Запуск: ./venv/bin/python tests/test_kodi_core_reflect.py
Проверяет: < порога ошибок — рефлексии нет; ≥ 3 расхождений вердикта с
меткой человека/чата → задача часу (M, автодоставка, случаи в постановке)
+ CoreReflection; повтор — только по новым случаям; задачи: ≥ 3 S-задач
не влезли в мин → своя рефлексия, разобранные не повторяются; недельный
отчёт: не в понедельник — нет, force — текст с метриками, повтор недели
— SKIP (идемпотентная доставка).
"""
import json
import os
import sys
import tempfile
sys.path.insert(0, "/root/simbiot-harness")
_tmp = tempfile.mkdtemp(prefix="simbiot_core_refl_")
os.environ["SIMBIOT_DB"] = os.path.join(_tmp, "harness.db")
os.environ["SIMBIOT_CORE_DIR"] = os.path.join(_tmp, "core")
os.environ.pop("SIMBIOT_CORE_REFLECT_MIN", None)
from harness import es, kodi_core, tg_inbox
es.init_db()
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
if not cond:
fails.append(name)
SENT = []
tg_inbox.notify_chat = lambda text: SENT.append(text) or {"ok": True}
G = -1004402779881
N = [0]
def wrong(verdict_addressed: bool, truth: int, src="feedback"):
N[0] += 1
mid = f"m-{N[0]}"
es.append_event("inbox", mid, "MessageReceived", {"update_id": N[0], "chat_id": G,
"chat_type": "supergroup", "first_name": "Alex", "text": f"реплика {N[0]}"})
es.append_event("inbox", mid, "MessageTriaged" if verdict_addressed else "MessageIgnored",
{"addressed": verdict_addressed, "reason": "not_addressed", "rule": "decision", "p": .6})
es.append_event("core", f"lab:{mid}", "CoreLabel" if src == "human" else "CoreFeedback",
{"head": "addressing", "msg": mid, "label": truth, "source": src})
def tasks_with(event_type):
return [json.loads(e["data"]) for e in es.get_events(0, 10**6) if e["event_type"] == event_type]
wrong(True, 0); wrong(False, 1)
check("2 ошибки < порога — рефлексии нет", kodi_core.reflect(log=lambda *_: None) == [])
wrong(True, 0, "human")
made = kodi_core.reflect(log=lambda *_: None)
sub = {t["id"]: t for t in tasks_with("TaskSubmitted")}
check("3 ошибки → задача рефлексии", len(made) == 1 and "Рефлексия адресации" in sub[made[0]]["title"], made)
check("задача — к часу (M) с автодоставкой",
sub[made[0]]["size"] == "M" and es.run_projections()["tasks"][made[0]].get("await_delivery"))
check("случаи в постановке", "я ответил" in sub[made[0]]["details"] and "было не боту" in sub[made[0]]["details"])
check("повтор без новых случаев — нет", kodi_core.reflect(log=lambda *_: None) == [])
# задачи: 3 S-задачи не влезли
for i in range(3):
tid = f"t-{i}"
es.append_event("task", tid, "TaskSubmitted", {"id": tid, "title": f"найди плашку {i}", "size": "S"})
es.append_event("task", tid, "TaskClaimed", {"level": "min"})
es.append_event("task", tid, "TaskClaimed", {"level": "min"})
made2 = kodi_core.reflect(log=lambda *_: None)
sub = {t["id"]: t for t in tasks_with("TaskSubmitted")}
check("задачи: 3 не влезли → рефлексия задач", len(made2) == 1 and "Рефлексия задач" in sub[made2[0]]["title"], made2)
check("задачи: разобранные не повторяются", kodi_core.reflect(log=lambda *_: None) == [])
# недельный отчёт
from datetime import datetime, timezone
if datetime.now(timezone.utc).weekday() != 0:
check("не понедельник — отчёта нет", kodi_core.weekly_report() is None and not SENT)
txt = kodi_core.weekly_report(force=True)
check("отчёт: текст с головами и метриками", txt and "Адресация в группах" in txt and "Задачи" in txt
and "рефлексий" in txt and SENT, txt)
check("отчёт: повтор недели — SKIP", kodi_core.weekly_report(force=True) is None and len(SENT) == 1)
print(f"\n{'OK' if not fails else 'FAILED'}: {len(fails)} fail(s)")
sys.exit(1 if fails else 0)