diff --git a/.claude/agents/hermes-curator.md b/.claude/agents/hermes-curator.md index f0074ae..eba7348 100644 --- a/.claude/agents/hermes-curator.md +++ b/.claude/agents/hermes-curator.md @@ -34,13 +34,19 @@ case "$WAKE" in nohup .venv/bin/python ../scripts/final_${KIND}_pipeline.py --case "$CASE" \ > "/tmp/final_${KIND}_${CASE}.log" 2>&1 & sleep 2 - echo "PIPELINE_STARTED final_${KIND}_pipeline case=$CASE log=/tmp/final_${KIND}_${CASE}.log" + if [ "$KIND" = "learning" ]; then + echo "PIPELINE_STARTED_LEARNING case=$CASE log=/tmp/final_learning_${CASE}.log CONTINUE_TO_ANALYSIS" + else + echo "PIPELINE_STARTED_HALACHA final_${KIND}_pipeline case=$CASE log=/tmp/final_${KIND}_${CASE}.log" + fi ;; *) echo "NO_PIPELINE_WAKE" ;; esac ``` -אם הפלט הוא `PIPELINE_STARTED ...` — **זו כל המשימה**: כתוב comment קצר בעברית ("הופעל צינור לתיק ; התוצאות יופיעו ב-/training (סגנון) או /approvals + /precedents (הלכות) תוך מספר דקות."), סגור את ה-issue (status=done), ו**סיים מיד — אל תמשיך לסעיפים שלמטה**. -אם הפלט הוא `NO_PIPELINE_WAKE` — המשך כרגיל לתבנית שלמטה. +**ניתוב לפי הפלט:** +- `PIPELINE_STARTED_HALACHA ...` — **זו כל המשימה**: comment קצר בעברית ("הופעל צינור הלכות לתיק ; התוצאות יופיעו ב-/approvals + /precedents תוך מספר דקות."), סגור issue (status=done), **סיים מיד — אל תמשיך**. +- `PIPELINE_STARTED_LEARNING ... CONTINUE_TO_ANALYSIS` — **מצב AUTO (mark-final)**: צינור-הפאנל רץ ברקע (אל תריץ `ingest_final_version` ידנית — ראה ההערה למטה). **המשך ל-§A** וזהה דפוסים משלך, אבל ב-AUTO: בצע §A.1–§A.5b בלבד, ואז **דלג על §A.6 (interaction) — אל תעיר את דפנה** (הממצאים `proposed` ונסקרים ב-/training); המשך ל-§A.7. רשומת `style_corpus` כבר קיימת (enroll רץ ראשון בצינור). +- `NO_PIPELINE_WAKE` — יקיצת-תגובה/ידנית: המשך כרגיל ל-§A **כולל** §A.6 (interaction). > **הערה (INV-LRN4 / X16):** הצינור `final_learning_pipeline.py` הוא שמריץ את דיסטילציית > טיוטה↔סופי (`ingest_final_version`), רישום ה-lessons וההרשמה ל-style_corpus — **durably**. @@ -144,10 +150,13 @@ record_curator_findings( ``` מיפוי תג→category: `[סגנון]`→`style` · `[מבנה]`→`structure` · `[לקסיקון משפטי]`→`lexicon` · `[טבלאי]`→`tabular`. הכלי כותב כל ממצא כ-`decision_lesson` (`source='curator'`, `review_status='proposed'`) ומדלג על כפילויות. -אם הוא מחזיר שגיאת "לא נמצאה רשומת style_corpus" — הסופי טרם נקלט לקורפוס-הסגנון; ציין זאת ב-comment והמשך (אל תיכשל). +אם הוא מחזיר שגיאת "לא נמצאה רשומת style_corpus" — הסופי טרם נקלט (מירוץ נדיר מול enroll שבצינור). **המתן ~30 שניות ונסה פעם נוספת**; אם עדיין נכשל — ציין זאת ב-comment והמשך (אל תיכשל). **זו הצעה הממתינה לאישור דפנה — לא שינוי-קול. אתה עדיין read-only על התוכן ולא נוגע ב-skills/קבצים.** ## 6. בחר interaction (חובה — רוב המקרים יש) +> **במצב AUTO (יקיצת `PIPELINE_STARTED_LEARNING` מ-mark-final): דלג על כל §A.6 ועבור ל-§A.7.** אל תעלה interaction +> ואל תעיר את דפנה — הממצאים כבר נרשמו כ-`proposed` (§A.5b) ונסקרים בטאב "אוצֵר" ב-/training. §A.6 חל רק על יקיצת-תגובה/ידנית. + לפי הקונטקסט בחר **אחד** מ-3 הסוגים. אם **אין שום החלטה אנושית נדרשת** — דלג ישירות ל-§A.7. ### 6a. ask_user_questions — לסינון/בחירה ממצאים diff --git a/docs/spec/07-learning.md b/docs/spec/07-learning.md index c078c60..0865259 100644 --- a/docs/spec/07-learning.md +++ b/docs/spec/07-learning.md @@ -62,7 +62,7 @@ 3. **בדיקת-ציטוטים** — `extract_internal_citations` מקשר את הפסיקה שההחלטה מצטטת לספרייה; כל ציטוט שאינו בספרייה **מסומן אוטומטית** כ-`missing_precedent` (open) להעלאה ע"י היו"ר. 4. הציטוטים-המקושרים מזינים את **לולאת-ה-corroboration** (X11): ציטוט-נכנס מההחלטה שלנו מחזק את ההלכות של התקדים המצוטט (`corroboration_rebuild`). ואז שני שלבים אוטומטיים נפרדים (`run-learning` / `run-halacha`) המעירים worker מקומי (claude/DeepSeek/Gemini מקומיים בלבד): -- **למידה:** `ingest_final_version` (Opus distillation) → **פאנל-סגנון דו-סוכני** (DeepSeek+Gemini, "למידה כפולה") שמצביע על כל לקח-style_method; הסכמה 2/2 → `decision_lesson` (`source=panel:deepseek+gemini`) **שזורם אוטומטית לכותב** כ-`approved` (שער-מדורג, INV-LRN1) — הפיך (veto-יו"ר ב-/training); פיצול → ליו"ר. +- **למידה:** הצינור (`final_learning_pipeline.py`) רץ בסדר `enroll_style_corpus` (יצירת רשומת-הקורפוס, מהיר — **ראשון** מאז #159 כדי שהקורפוס יהיה זמין לפני הדיסטילציה הארוכה) → `ingest_final_version` (Opus distillation) → **פאנל-סגנון דו-סוכני** (DeepSeek+Gemini, "למידה כפולה") שמצביע על כל לקח-style_method; הסכמה 2/2 → `decision_lesson` (`source=panel:deepseek+gemini`) **שזורם אוטומטית לכותב** כ-`approved` (שער-מדורג, INV-LRN1) — הפיך (veto-יו"ר ב-/training); פיצול → ליו"ר. **במקביל** (יקיצת `final_learning_*`) האוצֵר ממשיך ל-§A ורושם ממצאי-`source='curator'` (ערוץ ג׳, §1.1). - **הלכות:** `extract_internal_citations` → `precedent_extract_halachot` → `corroboration_rebuild` → **פאנל-הלכות תלת-סוכני** (`halacha_panel_approve.py --apply`). שני הפאנלים **הפיכים** (גיבוי-CSV ל-`data/audit/`) ומסלימים מחלוקות. ההטמעה ל-`SKILL.md`/`legal-decision-lessons.md` ולכל **מהות** נשארת **אישור-יו"ר ידני קשיח** (INV-LRN1/G10); לקחי-**סגנון** בקונצנזוס זורמים אוטומטית-והפיך לכותב. @@ -109,11 +109,11 @@ הדו-סוכני שהצינור מפעיל (§0.6 — DeepSeek+Gemini, `source='panel:deepseek+gemini'`). שניהם `decision_lessons` ממתיני-שער; ערוץ הדיסטילציה (`appeal_type_rules`, §0.4) הוא השלישי. -> **פער-מימוש פתוח (TaskMaster #157):** ה-`PIPELINE-WAKE BRANCH` (`hermes-curator.md:24-43`) מפעיל את -> הצינור על `final_learning_*` ו**מסיים מיד** — ניתוח-§A האיכותי (וממילא `record_curator_findings`) -> **אינו רץ אוטומטית על mark-final**, אלא רק ביקיצת-תגובה/ידנית. כדי שממצאי-האוצֵר ייתפסו על כל סופי -> יש להכריע: שהסוכן ימשיך ל-§A אחרי הפעלת-הצינור (לטפל במירוץ enroll_style_corpus), או שהצינור עצמו -> יפלוט `source='curator'`. נדחה לתכנון נפרד. +> **נסגר (TaskMaster #159, 2026-06-28):** ה-`PIPELINE-WAKE BRANCH` (`hermes-curator.md`) מבדיל כעת +> `final_learning_*` מ-`final_halacha_*`: על **learning** הסוכן מפעיל את הצינור ברקע ו**ממשיך ל-§A** +> (מצב AUTO — רושם ממצאי-`curator`, מדלג על interaction), על **halacha** יוצא מיד כקודם. המירוץ מול +> `enroll_style_corpus` נסגר בכך ש-enroll רץ **ראשון** בצינור (§0.6) — הקורפוס קיים תוך שניות, הרבה לפני +> שהסוכן/ה-§A מסיים את ניתוח-ה-LLM; `record_curator_findings` כולל גם retry כרשת-ביטחון. ### 1.2 לולאת-פידבק-היו"ר (capture → ניתוח שבועי → לקחים) diff --git a/scripts/final_learning_pipeline.py b/scripts/final_learning_pipeline.py index edbb893..85a8170 100644 --- a/scripts/final_learning_pipeline.py +++ b/scripts/final_learning_pipeline.py @@ -91,15 +91,22 @@ async def main(args: argparse.Namespace) -> int: # The 3 steps as durable nodes (X16 / INV-DUR1) — shared runtime with # final_halacha (scripts/_pipeline_runtime.py). A crash/OOM in the long style - # panel [3] resumes from [3] instead of re-paying the Opus distillation [1]. + # panel [3] resumes from [3] instead of re-paying the Opus distillation [2]. + # + # Order (#159): enroll FIRST. enroll creates the style_corpus row (fast, no LLM) + # that decision_lessons attach to. Running it before the up-to-30-min distillation + # means the corpus exists within seconds — so the curator agent's §A (which fires + # in parallel on a final_learning_* wake) can record source='curator' findings + # without racing the 30-min step. enroll/ingest are mutually independent; panel + # needs both, so it stays last. async def step_ingest(results: dict) -> dict: - # [1] distillation (Opus) — skip if already analyzed (idempotent; --force to redo). + # [2] distillation (Opus) — skip if already analyzed (idempotent; --force to redo). status = await _latest_pair_status(case["id"]) if status == "analyzed" and not args.force: - print("[1/3] ingest_final_version — דולג (הזוג כבר analyzed; --force לחידוש)") + print("[2/3] ingest_final_version — דולג (הזוג כבר analyzed; --force לחידוש)") return {"ingest": "skipped:analyzed"} - print("[1/3] ingest_final_version — דיסטילציית טיוטה↔סופי…", flush=True) + print("[2/3] ingest_final_version — דיסטילציית טיוטה↔סופי…", flush=True) raw = await ingest_final_version(case_number, file_path=final_path) try: env = json.loads(raw) @@ -120,8 +127,9 @@ async def main(args: argparse.Namespace) -> int: return {"ingest": "done"} async def step_enroll(results: dict) -> dict: - # [2] enroll into style_corpus (idempotent) — lessons need a corpus_id. - print("[2/3] רישום לקורפוס-הסגנון (idempotent)…", flush=True) + # [1] enroll into style_corpus (idempotent) — lessons need a corpus_id, and + # it must exist early (before the long distillation) so curator §A can attach. + print("[1/3] רישום לקורפוס-הסגנון (idempotent)…", flush=True) if await _has_style_corpus(case_number): print(" ✓ כבר רשום בקורפוס-הסגנון") return {"enroll": "exists"} @@ -150,8 +158,8 @@ async def main(args: argparse.Namespace) -> int: return {"panel_rc": rc or 0} steps = [ - _pipeline_runtime.Step("ingest_final_version", step_ingest), _pipeline_runtime.Step("enroll_style_corpus", step_enroll), + _pipeline_runtime.Step("ingest_final_version", step_ingest), _pipeline_runtime.Step("style_panel", step_panel), ] checkpoint_db = config.DATA_DIR / "checkpoints" / "learning.sqlite"