Merge pull request 'fix(supervisor): re-probe claude.ai quota instead of blindly waiting for the reported reset' (#242) from worktree-supervisor-quota-probe into main
This commit was merged in pull request #242.
This commit is contained in:
@@ -110,7 +110,7 @@
|
||||
| `ingest_incoming_batch.py` | python | קליטת batch של החלטות ועדת ערר מ-`data/precedents/incoming/` דרך המסלול הקנוני (`ingest_internal_decision`) + חילוץ מטא-דאטה לכל תיק (המסלול הפנימי לא מתזמן metadata — INV-ING3). רצף (לא מקבילי, להימנע מעומס CLI). רשימת `DECISIONS` נערכת ידנית לכל batch. config מ-`~/.env`. תומך תהליך [[project_precedent_incoming_workflow]]. | ידני, per-batch (חלופה ל-MCP `internal_decision_upload` כש-batch גדול) |
|
||||
| `drain_halacha_queue.py` | python | ריקון תור חילוץ ההלכות (`process_pending_extractions kind='halacha'`) ב-batches של 4 עד שהתור ריק (2 סבבים ריקים). **רץ רק בחלון-לילה 23:00–05:00 שעון ישראל** (`_in_window`, zoneinfo DST-safe — המכונה UTC); מחוץ לחלון `===SKIP===`, ונעצר `===STOP===` כשהחלון נסגר (השאר ממשיך בלילה הבא, FIFO+checkpoint). env: `HALACHA_DRAIN_WINDOW_START`/`_END`/`HALACHA_DRAIN_TZ`. **kill-switch `/operations`:** בודק `is_drain_disabled` בעלייה **וגם בתחילת כל סבב** — כיבוי באמצע-ריצה עוצר את הלולאה בגבול-הסבב הבא (התהליך עצמו נהרג מיד דרך ה-UI-toggle/סופרוייזר). חילוץ-הלכות נשאר על claude_session (לא Gemini). self-heal ל-orphaned `processing`. ההלכות נוחתות `pending_review` (שער-יו"ר). **חילוץ תיק-בודד שהיו"ר מבקש רץ מיד דרך ה-CEO (`precedent_extract_halachot`) ואינו מגודר כאן.** | דרך `legal-halacha-drain.config.cjs` (pm2 cron) / ידני |
|
||||
| `legal-halacha-drain.config.cjs` | pm2/js | **תזמון חלון-לילה של `drain_halacha_queue.py`** (cron UTC `10 20,21,22,23,0,1,2,3 * * *` = superset שמכסה את 23:00–05:00 ישראל בקיץ ובחורף; הסקריפט גוזם לחלון המדויק ב-zoneinfo). דקת-הצתה `:10` (לא `:00`) כדי לא לחלוק דקה עם metadata-drain (`:00`) או supervisor (`:05`) — מונע deadlock של DDL-המיגרציה כששני דריינים עולים יחד. `HALACHA_DRAIN_CRON` לעקיפה. ירייה כל שעה גם מחדשת one-shot שמת באמצע (advisory-lock הופך חפיפה לבטוחה). דורש claude CLI. התקנה: `pm2 start scripts/legal-halacha-drain.config.cjs && pm2 save`. | pm2 cron (host-side) |
|
||||
| `halacha_drain_supervisor.py` | python | **מנהל-בריאות קבוע ל-`legal-halacha-drain`** (אפס צריכת-Claude — קורא DB/לוגים/pm2 ומצית את הדריינר הקיים). טיק יחיד: **מכבד `is_drain_disabled` בעדיפות עליונה — אם כבוי ב-/operations עוצר את הדריינר ולא מצית** · מצית כשבטל+תור≠ריק · restart ל-run תקוע (liveness לפי checkpoints-per-chunk, **לא** mtime-לוג שמתעדכן רק בסיום תיק ~10 דק') · backoff ב-rate-limit (429 + parse איפוס, מגודר-טריות; `cost=0`=מנוי) · מאמת ש-staging מתחייב. **BURST** (חלון "רוץ ברצף עכשיו" ידני): מקור-אמת = `drain_controls.burst_until` ב-DB — אותו ערך ש-/operations קורא/כותב (G1 מקור-יחיד, G2 בלי מסלול מקביל); בעתיד→חלון מורם, אחרת חלון-לילה 23-05; פג-תוקף אוטומטי במועד. תת-פקודות: `tick` (ברירת-מחדל), `burst-on [--until]`, `burst-off`, `status`. | דרך `legal-halacha-supervisor.config.cjs` (pm2 cron) / ידני / כפתור /operations |
|
||||
| `halacha_drain_supervisor.py` | python | **מנהל-בריאות קבוע ל-`legal-halacha-drain`** (אפס צריכת-Claude — קורא DB/לוגים/pm2 ומצית את הדריינר הקיים). טיק יחיד: **מכבד `is_drain_disabled` בעדיפות עליונה — אם כבוי ב-/operations עוצר את הדריינר ולא מצית** · מצית כשבטל+תור≠ריק · restart ל-run תקוע (liveness לפי checkpoints-per-chunk, **לא** mtime-לוג שמתעדכן רק בסיום תיק ~10 דק') · backoff ב-rate-limit (429 + parse איפוס, מגודר-טריות; `cost=0`=מנוי) — **אך לא ממתין בעיוורון לשעה המדווחת: בכל טיק-בהמתנה מריץ `quota_available()` (בדיקת `claude -p` זעירה, `cost=0`) ומתחדש מיד כשהמכסה באמת חזרה (≤ טיק אחד), כי claude.ai משחרר לרוב מוקדם מהמדווח** · מאמת ש-staging מתחייב. **BURST** (חלון "רוץ ברצף עכשיו" ידני): מקור-אמת = `drain_controls.burst_until` ב-DB — אותו ערך ש-/operations קורא/כותב (G1 מקור-יחיד, G2 בלי מסלול מקביל); בעתיד→חלון מורם, אחרת חלון-לילה 23-05; פג-תוקף אוטומטי במועד. תת-פקודות: `tick` (ברירת-מחדל), `burst-on [--until]`, `burst-off`, `status`. | דרך `legal-halacha-supervisor.config.cjs` (pm2 cron) / ידני / כפתור /operations |
|
||||
| `legal-halacha-supervisor.config.cjs` | pm2/js | **תזמון כל 15 דק' של `halacha_drain_supervisor.py`** (cron `5-59/15 * * * *` = `:05,:20,:35,:50`, `HALACHA_SUPERVISOR_CRON` לעקיפה; דקת-הצתה `:05` כדי לא לחלוק דקה עם metadata-drain `:00` או halacha-drain `:10` — מונע deadlock של DDL-המיגרציה). `autorestart:false` (one-shot per tick). מצב-state ב-`~/halacha-drain-monitor/` (מחוץ ל-repo). התקנה: `pm2 start scripts/legal-halacha-supervisor.config.cjs && pm2 save`. | pm2 cron (host-side) |
|
||||
| `ingest_digests_batch.py` | python | קליטת batch של יומוני "כל יום" מ-`data/digests/incoming/` דרך המסלול העצמאי של קורפוס-הגילוי (`digest_library.ingest_digest`) — חילוץ-LLM (תג-מושג, כותרת-הלכה, מראה-מקום, שני-תאריכים), embedding יחיד, ו-autolink לפסק המקורי (X12/INV-DIG3). רצף (לא מקבילי). מזהה-יומון+תאריך נגזרים משם-הקובץ; העלון החודשי מדולג. **לא מעביר קבצים** — ה-DB (content_hash) הוא מקור-האמת היחיד; הרצה חוזרת מדלגת על קיימים (`exists`). config מ-`~/.env`. | ידני, per-batch (חלופה ל-MCP `digest_upload`) |
|
||||
| `drain_digests.py` | python | ריקון תור ההעשרה של יומונים (X12): מעבד כל digest בסטטוס `pending` דרך `digest_library.enrich_digest` (חילוץ-LLM Sonnet + embedding + autolink). מקבילי (CONCURRENCY=3, env-tunable), idempotent. מוסיף `~/.local/bin` ל-PATH כדי שה-claude CLI יימצא תחת cron. בודק דגל `drain_controls('legal-digest-drain')` ב-startup → no-op כשכבוי מ-/operations. | דרך `legal-digest-drain.config.cjs` (pm2 cron) + ידני אחרי backfill. חלופת-MCP: `digest_process_pending` |
|
||||
|
||||
@@ -70,10 +70,48 @@ def pm2_bin():
|
||||
return "pm2"
|
||||
|
||||
|
||||
def claude_bin():
|
||||
"""Resolve the claude CLI — PATH may be bare under pm2 cron, so fall back to
|
||||
the known install location (same binary the drain's claude_session uses)."""
|
||||
for c in ["claude", "/home/chaim/.local/bin/claude",
|
||||
*glob("/home/chaim/.nvm/versions/node/*/bin/claude")]:
|
||||
try:
|
||||
if subprocess.run([c, "--version"], capture_output=True,
|
||||
timeout=10).returncode == 0:
|
||||
return c
|
||||
except Exception:
|
||||
continue
|
||||
return "claude"
|
||||
|
||||
|
||||
PM2 = pm2_bin()
|
||||
CLAUDE = claude_bin()
|
||||
_ENV = {**os.environ, "HOME": "/home/chaim"}
|
||||
|
||||
|
||||
def quota_available() -> bool:
|
||||
"""Cheap live probe: is the claude.ai quota actually usable right now?
|
||||
|
||||
The 429 reset time claude.ai reports is often conservative — quota frees up
|
||||
earlier. Rather than trust that timestamp and wait blindly, we re-probe with
|
||||
a tiny `claude -p` call and resume the moment it succeeds. Conservative on
|
||||
failure: any non-zero exit, timeout, or limit message → treat as still
|
||||
limited (so a flaky probe never resumes the drain into a real 429)."""
|
||||
try:
|
||||
r = subprocess.run([CLAUDE, "-p", "Reply with exactly: OK"],
|
||||
capture_output=True, text=True, timeout=60, env=_ENV,
|
||||
cwd=REPO)
|
||||
except Exception:
|
||||
return False
|
||||
if r.returncode != 0:
|
||||
return False
|
||||
out = ((r.stdout or "") + (r.stderr or ""))
|
||||
low = out.lower()
|
||||
if "usage limit" in low or "session limit" in low or 'api_error_status":429' in out:
|
||||
return False
|
||||
return "OK" in out
|
||||
|
||||
|
||||
# ── DB access (via the repo venv; the module self-configures) ────────────────
|
||||
def _venv_py(code: str, timeout: int = 120) -> str:
|
||||
r = subprocess.run([VENV_PY, "-c", code], capture_output=True, text=True,
|
||||
@@ -319,8 +357,20 @@ def tick():
|
||||
cd_dt = datetime.fromisoformat(prev["cooldown_until"])
|
||||
except Exception:
|
||||
cd_dt = None
|
||||
cooldown_until = cd_dt.isoformat() if cd_dt else None
|
||||
in_cooldown = bool(cd_dt and now < cd_dt)
|
||||
# Don't trust the reported reset time — re-probe. claude.ai usually frees up
|
||||
# quota EARLIER than the 429 message claims, and the old code then sat idle
|
||||
# until that (conservative) timestamp. When we'd otherwise hold, a tiny live
|
||||
# probe lets us resume the instant quota is actually back (≤ one tick), no
|
||||
# manual kick. Runs at most once per tick and only while we think we're
|
||||
# limited, so the cost is negligible.
|
||||
if in_cooldown and quota_available():
|
||||
notes.append(
|
||||
f"בדיקת-מכסה הצליחה — המכסה חזרה לפני האיפוס המדווח "
|
||||
f"({cd_dt.astimezone(IDT):%H:%M IDT}); מתחדש מיד.")
|
||||
cd_dt = None
|
||||
in_cooldown = False
|
||||
cooldown_until = cd_dt.isoformat() if cd_dt else None
|
||||
weekly = bool(cd_dt and (cd_dt - now) > timedelta(hours=WEEKLY_GAP_HOURS))
|
||||
|
||||
# progress-based liveness (chunk checkpoints, NOT log mtime)
|
||||
|
||||
Reference in New Issue
Block a user