feat(learning): curator §A fires on mark-final — capture source='curator' findings on every final (#159)
חלופה ז (agent-centric) — סוגר את פער-המימוש מ-#157: 1. final_learning_pipeline.py — סדר-מחדש: enroll_style_corpus רץ ראשון (לפני הדיסטילציה של עד 30 דק'), כך שרשומת style_corpus קיימת תוך שניות — והאוצֵר יכול לרשום ממצאים בלי מירוץ. enroll/ingest בלתי-תלויים; panel נשאר אחרון. תוויות [1/3]/[2/3] עודכנו. 2. hermes-curator.md PIPELINE-WAKE BRANCH — מבדיל final_learning_* (ממשיך ל-§A, מצב AUTO) מ- final_halacha_* (exit כקודם). ב-AUTO: §A.1–§A.5b → record_curator_findings, דילוג על §A.6 (interaction) כדי לא להעיר את דפנה (הממצאים proposed ונסקרים ב-/training). §A.5b קיבל retry כרשת-ביטחון למירוץ-שארית. 3. docs/spec/07-learning.md §0.6 + §1.1 — עודכנו לסדר-הצינור החדש; הערת "פער-מימוש פתוח" → "נסגר". Invariants: G2 (מחולל-יחיד לממצאי-curator — הסוכן; אין כפילות-תבונה), INV-LRN1/G10 (proposed, שער-יו"ר), INV-LRN3, INV-DUR1 (סדר-צעדים בשמות יציבים; checkpoint resume — edge-case של ריצה מקבילה-לפריסה, סיכון נמוך). depends-on #157. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 קצר בעברית ("הופעל צינור <KIND> לתיק <CASE>; התוצאות יופיעו ב-/training (סגנון) או /approvals + /precedents (הלכות) תוך מספר דקות."), סגור את ה-issue (status=done), ו**סיים מיד — אל תמשיך לסעיפים שלמטה**.
|
||||
אם הפלט הוא `NO_PIPELINE_WAKE` — המשך כרגיל לתבנית שלמטה.
|
||||
**ניתוב לפי הפלט:**
|
||||
- `PIPELINE_STARTED_HALACHA ...` — **זו כל המשימה**: comment קצר בעברית ("הופעל צינור הלכות לתיק <CASE>; התוצאות יופיעו ב-/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 — לסינון/בחירה ממצאים
|
||||
|
||||
@@ -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 → ניתוח שבועי → לקחים)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user