simbiot-harness/harness/levels.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

80 lines
No EOL
3.8 KiB
Python

"""Simbiot Harness — уровни и конфигурация тактовой лестницы.
Контекст ∝ масштабу: чем крупнее цикл, тем больше контекст и модель.
"""
from dataclasses import dataclass, field
SEC = 1
MIN = 60
HOUR = 3600
DAY = 86400
WEEK = 7 * DAY
MONTH = 30 * DAY
QUARTER = 91 * DAY
YEAR = 366 * DAY # округляем год как 366 дней для простоты выравнивания
@dataclass
class CycleLevel:
name: str
label: str
every: int # секунд между тиками
uses_llm: bool
model: str # модель 9router (или "none")
effort: str # effort Strands: auto/low/medium/high/off
ctx_tokens: int # бюджет контекста уровня
task_classes: tuple # какие классы задач может брать
mandate: str # мандат уровня (в промпт)
can_shell: bool # инструменты уровня
def seq(self, ts: int) -> int:
"""Номер такта уровня для unix-ts (для идемпотентности тиков)."""
return ts // self.every
LEVELS = {
"sec": CycleLevel(
name="sec", label="Секунда · рефлекс", every=SEC, uses_llm=False,
model="none", effort="off", ctx_tokens=0, task_classes=(),
mandate=("Ты — рефлекторный уровень харнеса. Работает без LLM: телеметрия, "
"heartbeat, алерты. Никаких решений — только наблюдение."),
can_shell=False,
),
"min": CycleLevel(
name="min", label="Минута · пульс", every=MIN, uses_llm=True,
model="auto", effort="low", ctx_tokens=4_000, task_classes=("S",),
mandate=("Ты — минутный пульс харнеса Simbiot. Триаж входящих, быстрые ответы, "
"мелкие фиксы (S-задачи до 5 минут). Отвечай кратко, не углубляйся. "
"Если задача больше твоего класса — откажись и предложи промоушен."),
can_shell=True,
),
"hour": CycleLevel(
name="hour", label="Час · поток", every=HOUR, uses_llm=True,
model="auto", effort="medium", ctx_tokens=32_000, task_classes=("S", "M"),
mandate=("Ты — часовой поток харнеса Simbiot. Дайджест часа: что произошло, "
"статусы фоновых задач, батч мелочей (S/M). Консолидируй минутные "
"события в выжимку — сырое наверх не поднимай."),
can_shell=True,
),
"day": CycleLevel(
name="day", label="День · манифест", every=DAY, uses_llm=True,
model="auto", effort="high", ctx_tokens=128_000, task_classes=("S", "M", "L"),
mandate=("Ты — дневной цикл харнеса Simbiot. Манифест дня: план, выбор задач "
"L-класса, дневной отчёт. Думай глубоко, планируй на день вперёд."),
can_shell=True,
),
}
# v0.2+: week/month/quarter/year — включаются по мере готовности
FUTURE_LEVELS = ("week", "month", "quarter", "year")
def size_class(minutes: int) -> str:
"""Класс задачи по оценке длительности."""
if minutes <= 5:
return "S"
if minutes <= 60:
return "M"
if minutes <= 8 * 60:
return "L"
return "XL"