feat(learning): curator §A fires on mark-final — capture source='curator' findings on every final (#159)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

חלופה ז (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:
2026-06-28 21:56:36 +00:00
parent 07bd8bee48
commit 4d6268820c
3 changed files with 34 additions and 17 deletions

View File

@@ -34,13 +34,19 @@ case "$WAKE" in
nohup .venv/bin/python ../scripts/final_${KIND}_pipeline.py --case "$CASE" \ nohup .venv/bin/python ../scripts/final_${KIND}_pipeline.py --case "$CASE" \
> "/tmp/final_${KIND}_${CASE}.log" 2>&1 & > "/tmp/final_${KIND}_${CASE}.log" 2>&1 &
sleep 2 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" ;; *) echo "NO_PIPELINE_WAKE" ;;
esac 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` הוא שמריץ את דיסטילציית > **הערה (INV-LRN4 / X16):** הצינור `final_learning_pipeline.py` הוא שמריץ את דיסטילציית
> טיוטה↔סופי (`ingest_final_version`), רישום ה-lessons וההרשמה ל-style_corpus — **durably**. > טיוטה↔סופי (`ingest_final_version`), רישום ה-lessons וההרשמה ל-style_corpus — **durably**.
@@ -144,10 +150,13 @@ record_curator_findings(
``` ```
מיפוי תג→category: `[סגנון]``style` · `[מבנה]``structure` · `[לקסיקון משפטי]``lexicon` · `[טבלאי]``tabular`. מיפוי תג→category: `[סגנון]``style` · `[מבנה]``structure` · `[לקסיקון משפטי]``lexicon` · `[טבלאי]``tabular`.
הכלי כותב כל ממצא כ-`decision_lesson` (`source='curator'`, `review_status='proposed'`) ומדלג על כפילויות. הכלי כותב כל ממצא כ-`decision_lesson` (`source='curator'`, `review_status='proposed'`) ומדלג על כפילויות.
אם הוא מחזיר שגיאת "לא נמצאה רשומת style_corpus" — הסופי טרם נקלט לקורפוס-הסגנון; ציין זאת ב-comment והמשך (אל תיכשל). אם הוא מחזיר שגיאת "לא נמצאה רשומת style_corpus" — הסופי טרם נקלט (מירוץ נדיר מול enroll שבצינור). **המתן ~30 שניות ונסה פעם נוספת**; אם עדיין נכשל — ציין זאת ב-comment והמשך (אל תיכשל).
**זו הצעה הממתינה לאישור דפנה — לא שינוי-קול. אתה עדיין read-only על התוכן ולא נוגע ב-skills/קבצים.** **זו הצעה הממתינה לאישור דפנה — לא שינוי-קול. אתה עדיין read-only על התוכן ולא נוגע ב-skills/קבצים.**
## 6. בחר interaction (חובה — רוב המקרים יש) ## 6. בחר interaction (חובה — רוב המקרים יש)
> **במצב AUTO (יקיצת `PIPELINE_STARTED_LEARNING` מ-mark-final): דלג על כל §A.6 ועבור ל-§A.7.** אל תעלה interaction
> ואל תעיר את דפנה — הממצאים כבר נרשמו כ-`proposed` (§A.5b) ונסקרים בטאב "אוצֵר" ב-/training. §A.6 חל רק על יקיצת-תגובה/ידנית.
לפי הקונטקסט בחר **אחד** מ-3 הסוגים. אם **אין שום החלטה אנושית נדרשת** — דלג ישירות ל-§A.7. לפי הקונטקסט בחר **אחד** מ-3 הסוגים. אם **אין שום החלטה אנושית נדרשת** — דלג ישירות ל-§A.7.
### 6a. ask_user_questions — לסינון/בחירה ממצאים ### 6a. ask_user_questions — לסינון/בחירה ממצאים

View File

@@ -62,7 +62,7 @@
3. **בדיקת-ציטוטים**`extract_internal_citations` מקשר את הפסיקה שההחלטה מצטטת לספרייה; כל ציטוט שאינו בספרייה **מסומן אוטומטית** כ-`missing_precedent` (open) להעלאה ע"י היו"ר. 3. **בדיקת-ציטוטים**`extract_internal_citations` מקשר את הפסיקה שההחלטה מצטטת לספרייה; כל ציטוט שאינו בספרייה **מסומן אוטומטית** כ-`missing_precedent` (open) להעלאה ע"י היו"ר.
4. הציטוטים-המקושרים מזינים את **לולאת-ה-corroboration** (X11): ציטוט-נכנס מההחלטה שלנו מחזק את ההלכות של התקדים המצוטט (`corroboration_rebuild`). 4. הציטוטים-המקושרים מזינים את **לולאת-ה-corroboration** (X11): ציטוט-נכנס מההחלטה שלנו מחזק את ההלכות של התקדים המצוטט (`corroboration_rebuild`).
ואז שני שלבים אוטומטיים נפרדים (`run-learning` / `run-halacha`) המעירים worker מקומי (claude/DeepSeek/Gemini מקומיים בלבד): ואז שני שלבים אוטומטיים נפרדים (`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`). - **הלכות:** `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); לקחי-**סגנון** בקונצנזוס זורמים אוטומטית-והפיך לכותב. שני הפאנלים **הפיכים** (גיבוי-CSV ל-`data/audit/`) ומסלימים מחלוקות. ההטמעה ל-`SKILL.md`/`legal-decision-lessons.md` ולכל **מהות** נשארת **אישור-יו"ר ידני קשיח** (INV-LRN1/G10); לקחי-**סגנון** בקונצנזוס זורמים אוטומטית-והפיך לכותב.
@@ -109,11 +109,11 @@
הדו-סוכני שהצינור מפעיל (§0.6 — DeepSeek+Gemini, `source='panel:deepseek+gemini'`). שניהם הדו-סוכני שהצינור מפעיל (§0.6 — DeepSeek+Gemini, `source='panel:deepseek+gemini'`). שניהם
`decision_lessons` ממתיני-שער; ערוץ הדיסטילציה (`appeal_type_rules`, §0.4) הוא השלישי. `decision_lessons` ממתיני-שער; ערוץ הדיסטילציה (`appeal_type_rules`, §0.4) הוא השלישי.
> **פער-מימוש פתוח (TaskMaster #157):** ה-`PIPELINE-WAKE BRANCH` (`hermes-curator.md:24-43`) מפעיל את > **נסגר (TaskMaster #159, 2026-06-28):** ה-`PIPELINE-WAKE BRANCH` (`hermes-curator.md`) מבדיל כעת
> הצינור על `final_learning_*` ו**מסיים מיד** — ניתוח-§A האיכותי (וממילא `record_curator_findings`) > `final_learning_*` מ-`final_halacha_*`: על **learning** הסוכן מפעיל את הצינור ברקע ו**ממשיך ל-§A**
> **אינו רץ אוטומטית על mark-final**, אלא רק ביקיצת-תגובה/ידנית. כדי שממצאי-האוצֵר ייתפסו על כל סופי > (מצב AUTO — רושם ממצאי-`curator`, מדלג על interaction), על **halacha** יוצא מיד כקודם. המירוץ מול
> יש להכריע: שהסוכן ימשיך ל-§A אחרי הפעלת-הצינור (לטפל במירוץ enroll_style_corpus), או שהצינור עצמו > `enroll_style_corpus` נסגר בכך ש-enroll רץ **ראשון** בצינור (§0.6) — הקורפוס קיים תוך שניות, הרבה לפני
> יפלוט `source='curator'`. נדחה לתכנון נפרד. > שהסוכן/ה-§A מסיים את ניתוח-ה-LLM; `record_curator_findings` כולל גם retry כרשת-ביטחון.
### 1.2 לולאת-פידבק-היו"ר (capture → ניתוח שבועי → לקחים) ### 1.2 לולאת-פידבק-היו"ר (capture → ניתוח שבועי → לקחים)

View File

@@ -91,15 +91,22 @@ async def main(args: argparse.Namespace) -> int:
# The 3 steps as durable nodes (X16 / INV-DUR1) — shared runtime with # 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 # 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: 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"]) status = await _latest_pair_status(case["id"])
if status == "analyzed" and not args.force: 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"} 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) raw = await ingest_final_version(case_number, file_path=final_path)
try: try:
env = json.loads(raw) env = json.loads(raw)
@@ -120,8 +127,9 @@ async def main(args: argparse.Namespace) -> int:
return {"ingest": "done"} return {"ingest": "done"}
async def step_enroll(results: dict) -> dict: async def step_enroll(results: dict) -> dict:
# [2] enroll into style_corpus (idempotent) — lessons need a corpus_id. # [1] enroll into style_corpus (idempotent) — lessons need a corpus_id, and
print("[2/3] רישום לקורפוס-הסגנון (idempotent)…", flush=True) # 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): if await _has_style_corpus(case_number):
print(" ✓ כבר רשום בקורפוס-הסגנון") print(" ✓ כבר רשום בקורפוס-הסגנון")
return {"enroll": "exists"} return {"enroll": "exists"}
@@ -150,8 +158,8 @@ async def main(args: argparse.Namespace) -> int:
return {"panel_rc": rc or 0} return {"panel_rc": rc or 0}
steps = [ steps = [
_pipeline_runtime.Step("ingest_final_version", step_ingest),
_pipeline_runtime.Step("enroll_style_corpus", step_enroll), _pipeline_runtime.Step("enroll_style_corpus", step_enroll),
_pipeline_runtime.Step("ingest_final_version", step_ingest),
_pipeline_runtime.Step("style_panel", step_panel), _pipeline_runtime.Step("style_panel", step_panel),
] ]
checkpoint_db = config.DATA_DIR / "checkpoints" / "learning.sqlite" checkpoint_db = config.DATA_DIR / "checkpoints" / "learning.sqlite"