cron-migrate r2 fixes (t-261002222610): scheduler guard против дайджест-перезаписи уже закрытых задач; runner ledger-first (best_completed_answer: projection могла быть перетёрта digest-закрытием); generation-only промпт (легаси-ротацию из промпта убрал — теперь только runner ведёт темы); дата выпуска в details
This commit is contained in:
parent
4bf5ef9d77
commit
98511453d7
3 changed files with 56 additions and 14 deletions
|
|
@ -535,7 +535,22 @@ def run_level_tick(level_cfg, seq: int, model: dict):
|
|||
from .commands import looks_like_budget_stub
|
||||
if (text or "") and looks_like_budget_stub(text):
|
||||
return _guard_budget_stub_requeue(claimed, text)
|
||||
# guard двойного закрытия (r2 e2e cron-migrate 2026-10-03): воркер мог
|
||||
# ЗАКРЫТЬ задачу сам (cli complete) ещё до батч-фазы — его TaskCompleted
|
||||
# содержит целевой ответ; повторный append поверх перезаписал бы answer
|
||||
# дайджестом тика и испортил бы проверенный результат (перезапись
|
||||
# «последний-победил» в проекции). Закрываем только ещё-running.
|
||||
_model = es.run_projections()
|
||||
_done = {tid for tid in (claimed or [])
|
||||
if ((_model.get("tasks") or {}).get(tid) or {}).get("status") == "done"}
|
||||
_first = True
|
||||
for tid in (claimed or []):
|
||||
if tid in _done:
|
||||
if _first:
|
||||
_log("skip батч-догзакрытие готовых задач: "
|
||||
+ ", ".join(sorted(_done)))
|
||||
_first = False
|
||||
continue
|
||||
es.append_event("task", tid, "TaskCompleted", {
|
||||
"result_ref": trace_id, "answer": (text or "")[:2000],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -147,7 +147,10 @@ def pick_theme(job):
|
|||
cands = [i for i in range(1, len(themes) + 1)
|
||||
if _days_ago(by_theme.get(i, "")) >= cutoff]
|
||||
pool = cands or list(range(1, len(themes) + 1))
|
||||
n = min(pool, key=lambda i: (_days_ago(by_theme.get(i, "")), i))
|
||||
# Если ни одна тема не вышла из окна повтора — берём наименее свежую
|
||||
# (макс. days_ago), НЕ свежую: min по days_ago повторял вчерашнюю тему
|
||||
# (e2e 2026-10-03: тема 3 вышла 10-02 и снова 10-03).
|
||||
n = min(pool, key=lambda i: (-_days_ago(by_theme.get(i, "")), i))
|
||||
return {"num": n, "title": themes[n - 1],
|
||||
"last_used": by_theme.get(n, "никогда"),
|
||||
"note": note_by_theme.get(n, ""), "state_path": sp}
|
||||
|
|
@ -202,16 +205,39 @@ def completed_answer(es, task_id):
|
|||
return json.loads(row[0]).get("answer") or ""
|
||||
|
||||
|
||||
def completed_answers(es, task_id):
|
||||
"""Все ответы TaskCompleted из ledger по порядку (старые→новые).
|
||||
|
||||
Проекция (read model) хранит последний answer: дайджест-закрытие тика
|
||||
могло перетереть целевой ответ — поэтому quality-решения принимает
|
||||
ledger-first (r2 e2e 2026-10-03: ответ сценария перезаписан digest'ом).
|
||||
"""
|
||||
import sqlite3 as _sq
|
||||
db = _sq.connect(es.DB_PATH)
|
||||
rows = db.execute(
|
||||
"SELECT data FROM events WHERE aggregate_id=? AND event_type='TaskCompleted' "
|
||||
"ORDER BY id", (task_id,)).fetchall()
|
||||
db.close()
|
||||
return [(json.loads(d).get("answer") or "") for _ts, d in rows
|
||||
if (json.loads(d).get("answer") or "").strip()]
|
||||
|
||||
|
||||
def best_completed_answer(es, job, task_id):
|
||||
"""Лучший по quality-gate ответ из ledger; если ни один не качественный —
|
||||
первый (чтобы честно провалу не изображать успех)."""
|
||||
ans = completed_answers(es, task_id)
|
||||
if not ans:
|
||||
return None
|
||||
ok = [a for a in ans if quality_ok(job, a)]
|
||||
return (ok or ans)[0]
|
||||
|
||||
|
||||
def task_status(es, task_id):
|
||||
model = es.run_projections()
|
||||
t = (model.get("tasks") or {}).get(task_id)
|
||||
if not t:
|
||||
return None
|
||||
res = t.get("result") or ""
|
||||
if t.get("status") == "done":
|
||||
a = completed_answer(es, task_id)
|
||||
res = a if a is not None else res
|
||||
return {"status": t.get("status"), "answer": res}
|
||||
return {"status": t.get("status")}
|
||||
|
||||
|
||||
def quality_ok(job, answer):
|
||||
|
|
@ -326,7 +352,8 @@ RETRY_NOTE = ("⚠ ПРЕДЫДУЩАЯ ПОПЫТКА ЭТОЙ ЖЕ ТЕМЫ В
|
|||
|
||||
def build_details(job, pick, retry=False):
|
||||
head = [prompt_text(job)]
|
||||
head.append("\nТЕКУЩАЯ ТЕМА ДНЯ: №%d — %s." % (pick["num"], pick["title"]))
|
||||
head.append("ДАТА ВЫПУСКА: %s." % today_utc())
|
||||
head.append("ТЕКУЩАЯ ТЕМА ДНЯ: №%d — %s." % (pick["num"], pick["title"]))
|
||||
head.append("В прошлый раз тема %s выходила %s (%s). Сегодня придумай НОВЫЙ "
|
||||
"угол, прежние формулировки не повторяй."
|
||||
% (pick["num"], pick["last_used"], pick["note"] or "без заметки"))
|
||||
|
|
@ -345,7 +372,7 @@ def attempt(es, job, task_id, pick, retry, deadline_ts):
|
|||
проекцию), просто жду/читаю статус."""
|
||||
st = task_status(es, task_id)
|
||||
if st and st["status"] == "done":
|
||||
return "done", st["answer"]
|
||||
return "done", best_completed_answer(es, job, task_id) or ""
|
||||
if st and st["status"] == "failed":
|
||||
return "failed", ""
|
||||
if not st or st["status"] not in ("ready", "running"):
|
||||
|
|
@ -356,7 +383,7 @@ def attempt(es, job, task_id, pick, retry, deadline_ts):
|
|||
time.sleep(25)
|
||||
st = task_status(es, task_id)
|
||||
if st and st["status"] == "done":
|
||||
return "done", st["answer"]
|
||||
return "done", best_completed_answer(es, job, task_id) or ""
|
||||
if st and st["status"] == "failed":
|
||||
return "failed", ""
|
||||
return "wait", ""
|
||||
|
|
@ -407,7 +434,7 @@ def main():
|
|||
if args.reconcile:
|
||||
st = task_status(es, base)
|
||||
if st and st["status"] == "done":
|
||||
a = st["answer"]
|
||||
a = best_completed_answer(es, job, base) or ""
|
||||
if quality_ok(job, a):
|
||||
if was_delivered(job, base, a):
|
||||
record_theme_today(job)
|
||||
|
|
@ -466,7 +493,7 @@ def main():
|
|||
# ── сегодня: идемпотентные короткие выходы ──
|
||||
st = task_status(es, base)
|
||||
if st and st["status"] == "done":
|
||||
a = st["answer"]
|
||||
a = best_completed_answer(es, job, base) or ""
|
||||
if quality_ok(job, a):
|
||||
# тема дня уже записана (запись сделана в handle_day) — не перетираю
|
||||
if was_delivered(job, base, a):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Ты — Рита, агент студии Shkor Labs. Задача: каждое утро (сегодня понедельник-пятница, 09:00 Минск = 06:00 UTC) сгенерировать ОДИН сценарий короткого вертикального видео (60-90 сек, Instagram Reels / TikTok / Shorts) для привлечения людей на БЕСПЛАТНЫЙ 20-минутный разбор стартапа от Алекса Шкора. Страница: https://mentor.cil.by — бесплатный разбор, слоты ежедневно 18:00-22:00 по Минску, апсейл — менторство $999/мес. Позиционирование Алекса: строит продукты с 2010, co-founder Paralect и DEIP, Shkor Labs («42 Labs»), 50+ созданных технологий, AI-агенты.
|
||||
Ты — Рита, агент студии Shkor Labs. Задача: сгенерировать ОДИН сценарий короткого вертикального видео (60-90 сек, Instagram Reels / TikTok / Shorts) для привлечения людей на БЕСПЛАТНЫЙ 20-минутный разбор стартапа от Алекса Шкора. Страница: https://mentor.cil.by — бесплатный разбор, слоты ежедневно 18:00-22:00 по Минску, апсейл — менторство $999/мес. Позиционирование Алекса: строит продукты с 2010, co-founder Paralect и DEIP, Shkor Labs («42 Labs»), 50+ созданных технологий, AI-агенты.
|
||||
|
||||
Формат сценария (строго):
|
||||
1. ХУК (первые 3 сек) — провокационный или неожиданный тезис, без клише («вин-вин», «пушечная идея» — запрещено). Без эмодзи.
|
||||
|
|
@ -7,6 +7,6 @@
|
|||
4. CTA — «Запишись на бесплатный 20-минутный разбор — слоты каждый день с 18 до 22, mentor.cil.by». Одной фразой.
|
||||
5. ТЕКСТ ПОД ВИДЕО (2-3 строки) + 5 хэштегов.
|
||||
|
||||
Правила: разговорный живой русский, короткие рубленые фразы, никаких «друзья, сегодня поговорим о». Каждый день — НОВАЯ тема (ротация: 1) разбор ошибок фаундеров с ИИ, 2) кейс студии (что-то реальное: Totem-маскоты, видеоконвейер, DEIP-биржа, Simbiot-агент), 3) контр-интуитивный тезис о внедрении ИИ, 4) ответ на частый вопрос, 5) разбор типового сценария «как ИИ даёт деньги в <ниша>»). Темы не должны повторяться минимум 7 дней — хранить последнюю тему в файле ~/.hermes/cron/output/video-script-state.json и читать её.
|
||||
Правила: разговорный живой русский, короткие рубленые фраза, никаких «друзья, сегодня поговорим о». Тему и дату выпуска берём ИСКЛЮЧИТЕЛЬНО из блока ниже (ТЕКУЩАЯ ТЕМА ДНЯ / ДАТА) — никакие файлы не читаем, состояние ведёт runner.
|
||||
|
||||
В конце вывести блок ДЛЯ РУЧНОЙ ЗАПИСИ: дата, тема, 3-5 пунктов что показать на экране (b-roll/текст на экране). Не более 40 строк итога.
|
||||
В конце вывести блок ДЛЯ РУЧНОЙ ЗАПИСИ: дата, тема, 3-5 пунктов что показать на экране (b-roll/текст на экране). Не более 40 строк итога. Готовый сценарий — целиком в answer задачи: никаких «сделал, записал в файл», никаких отчётов о процессе — только сам сценарий.
|
||||
Loading…
Reference in a new issue