- 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 первым)
67 lines
No EOL
2.6 KiB
Python
67 lines
No EOL
2.6 KiB
Python
"""Simbiot Harness — command side (CQRS): валидация + события.
|
|
|
|
Задачи подаются через CLI/Telegram/файл-инбокс; каждая команда
|
|
валидируется и только потом пишет событие.
|
|
"""
|
|
import os
|
|
import secrets
|
|
from datetime import datetime, timezone
|
|
|
|
from . import es
|
|
from .levels import size_class
|
|
|
|
|
|
def submit_task(title: str, details: str = "", minutes: int = 5, source: str = "cli"):
|
|
"""SubmitTask: класс по оценке длительности. Идемпотентно по id."""
|
|
task_id = f"t-{datetime.now(timezone.utc).strftime('%y%m%d%H%M%S')}-{secrets.token_hex(3)}"
|
|
size = size_class(minutes)
|
|
es.append_event("task", task_id, "TaskSubmitted", {
|
|
"id": task_id, "title": title, "details": details,
|
|
"size": size, "est_minutes": minutes, "source": source,
|
|
})
|
|
return task_id, size
|
|
|
|
|
|
def requeue_task(task_id: str, reason: str = "manual requeue"):
|
|
"""Вернуть failed/прерванную задачу в очередь (TaskPromoted → ready)."""
|
|
model = es.run_projections()
|
|
t = model["tasks"].get(task_id)
|
|
if t is None:
|
|
raise ValueError(f"задача {task_id} не найдена")
|
|
if t["status"] not in ("failed", "done"):
|
|
raise ValueError(f"задача {task_id} в статусе {t['status']} — requeue не нужен")
|
|
es.append_event("task", task_id, "TaskPromoted", {"to": t["size"], "reason": reason})
|
|
return task_id
|
|
|
|
|
|
def set_policy(identity: str = None, cpu_alert_threshold: int = None):
|
|
"""PolicyChanged: идентичность + пороги. Мержим поверх текущей политики."""
|
|
model = es.run_projections()
|
|
current = dict(model.get("policy") or {})
|
|
patch = {}
|
|
if identity is not None:
|
|
patch["identity"] = identity
|
|
if cpu_alert_threshold is not None:
|
|
patch["cpu_alert_threshold"] = cpu_alert_threshold
|
|
current.update(patch)
|
|
es.append_event("harness", "policy", "PolicyChanged", current)
|
|
return current
|
|
|
|
|
|
def list_tasks():
|
|
model = es.run_projections()
|
|
out = []
|
|
for t in model["tasks"].values():
|
|
out.append(t)
|
|
return out
|
|
|
|
|
|
def status():
|
|
model = es.run_projections()
|
|
return {
|
|
"cycle_state": model["cycle_state"],
|
|
"tasks_ready": sum(1 for t in model["tasks"].values() if t["status"] == "ready"),
|
|
"tasks_done": sum(1 for t in model["tasks"].values() if t["status"] == "done"),
|
|
"digests": len(model["digests"]),
|
|
"spend": model.get("spend", [])[-8:],
|
|
} |