feat(paperclip): close 11 integration gaps (#16-#28)
Brings the legal-ai ↔ Paperclip integration in line with the official Paperclip skill. Net effect: HEARTBEAT.md -47% (370→195 lines), all 14 agents on uniform runtime_config + budget + instructionsBundleMode, and two cross-company helpers replacing manual SQL. Highlights: - HEARTBEAT.md refactor: project-specific only, delegates to the official paperclipai/paperclip skill (loaded per agent). Adds heartbeat-context fast-path (§1.7) and PAPERCLIP_WAKE_PAYLOAD_JSON shortcut (§1.5). - Issue Thread Interactions API: legal-ceo.md now uses ask_user_questions / request_confirmation / suggest_tasks instead of free-text comments — gives chair structured UI with idempotency keys. - pc.sh + paperclip_api.pc_request: every API call goes through helpers that inject Authorization + X-Paperclip-Run-Id (audit trail). - sync_agents_across_companies.py: master(CMP)→mirror(CMPA) sync via Paperclip API, idempotent, with --verify and --apply modes. - skills/new-company-setup: 11-step blueprint distilling all 11 gaps into a single onboarding runbook for the next company. - .taskmaster: 12 tasks covering each gap (one already closed: #29). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,197 +1,165 @@
|
|||||||
# HEARTBEAT.md — רשימת ביצוע לכל ריצה
|
# HEARTBEAT.md — רשימת ביצוע לכל ריצה (Project-Specific)
|
||||||
|
|
||||||
## שפה — כלל עליון
|
> **🎯 קובץ זה — Project-specific only.** ה-skill הרשמי `paperclipai/paperclip/paperclip` (טעון אוטומטית בכל heartbeat דרך `paperclipSkillSync`) מכיל את כל ה-API patterns הגנריים: identity (`/api/agents/me`), `PAPERCLIP_WAKE_PAYLOAD_JSON`, `APPROVAL_ID`, inbox, comments, checkout, status updates, וכו'. **קובץ זה מתעד רק התאמות שלנו** — סינון חברה, helpers, workarounds, ו-quirks.
|
||||||
|
>
|
||||||
**כל הפלט שלך חייב להיות בעברית בלבד.** זה כולל:
|
> **בקונפליקט:** קובץ זה גובר על ה-skill (project-specific מנצח default).
|
||||||
- Comments ב-Paperclip
|
|
||||||
- הודעות סטטוס
|
|
||||||
- תיאורי שגיאות
|
|
||||||
- סיכומים ודיווחים
|
|
||||||
- חשיבה פנימית (thinking)
|
|
||||||
|
|
||||||
אין יוצאים מן הכלל. גם שמות tools, פקודות, ונתיבי קבצים — ההסבר סביבם בעברית.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
הרץ את הרשימה הזו בכל heartbeat.
|
## שפה — כלל עליון
|
||||||
|
|
||||||
## 1. זיהוי וסינון חברה
|
**כל הפלט שלך חייב להיות בעברית בלבד.** כולל: comments, סטטוס, שגיאות, סיכומים, ו-thinking פנימי. אין יוצאים מן הכלל. גם שמות tools, פקודות, ונתיבי קבצים — ההסבר סביבם בעברית. ה-skill הרשמי באנגלית — תרגם אם נדרש.
|
||||||
|
|
||||||
- וודא שאתה יודע מי אתה: `$PAPERCLIP_AGENT_ID`
|
---
|
||||||
- בדוק הקשר: `$PAPERCLIP_TASK_ID`, `$PAPERCLIP_WAKE_REASON`
|
|
||||||
- **זהה את החברה שלך**: `$PAPERCLIP_COMPANY_ID`
|
|
||||||
|
|
||||||
### ⚠️ סינון תיקים לפי חברה — כלל ברזל
|
## §0. כל קריאה ל-Paperclip API — דרך `pc.sh` בלבד
|
||||||
|
|
||||||
**אתה אחראי רק על תיקים ששייכים לחברה שלך.** הספרה הראשונה של מספר התיק קובעת:
|
**ה-skill הרשמי משתמש ב-`curl` ישיר. אצלנו אסור.** משתמשים ב-helper שלנו:
|
||||||
|
|
||||||
| חברה | COMPANY_ID | סוגי תיקים | טווח מספרים |
|
|
||||||
|------|------------|-------------|-------------|
|
|
||||||
| ועדת ערר רישוי ובניה | `42a7acd0-30c5-4cbd-ac97-7424f65df294` | רישוי ובניה | **1xxx** |
|
|
||||||
| ועדת ערר היטלי השבחה | `8639e837-4c9d-47fa-a76b-95788d651896` | היטל השבחה + פיצויים ס' 197 | **8xxx, 9xxx** |
|
|
||||||
|
|
||||||
- אם `$PAPERCLIP_COMPANY_ID` = `42a7acd0...` → עבוד רק על תיקים שמתחילים ב-**1**
|
|
||||||
- אם `$PAPERCLIP_COMPANY_ID` = `8639e837...` → עבוד רק על תיקים שמתחילים ב-**8** או **9**
|
|
||||||
- **לעולם אל תיצור פרויקט, issue, או תוכן לתיק שלא בטווח שלך**
|
|
||||||
- אם issue שהוקצה לך מכוון לתיק שלא בטווח שלך — סרב בנימוס ודווח ב-comment
|
|
||||||
|
|
||||||
## 2. בדוק תיבת דואר
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" "$PAPERCLIP_API_URL/api/agents/me/inbox-lite"
|
~/legal-ai/scripts/pc.sh <METHOD> <PATH> [BODY_JSON] [extra curl args...]
|
||||||
```
|
```
|
||||||
|
|
||||||
- תעדוף: `in_progress` קודם, אחר כך `todo`
|
מוסיף אוטומטית: `Authorization`, `X-Paperclip-Run-Id` (audit), `Content-Type`, base URL.
|
||||||
- אם `PAPERCLIP_TASK_ID` מוגדר — תעדף אותו
|
|
||||||
|
|
||||||
## 2b. קרא תגובות אחרונות על ה-issue
|
**דוגמאות:**
|
||||||
|
```bash
|
||||||
|
~/legal-ai/scripts/pc.sh GET "/api/agents/me/inbox-lite"
|
||||||
|
~/legal-ai/scripts/pc.sh POST "/api/issues/$ISSUE_ID/checkout"
|
||||||
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/$ISSUE_ID" '{"status":"done"}'
|
||||||
|
```
|
||||||
|
|
||||||
לפני שאתה מתחיל לעבוד, בדוק אם יש comments חדשים מחיים:
|
**ל-body גדול עם backticks** — `Write` ל-temp file, אז `pc.sh ... "" -H "Content-Type: application/json" -d @/tmp/comment.json`. ראה §דיווח למה.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1. זיהוי וסינון חברה — כלל ברזל ⚠️
|
||||||
|
|
||||||
|
| חברה | COMPANY_ID | סוגי תיקים | טווח מספרים | CEO Agent ID |
|
||||||
|
|------|------------|-------------|---------------|---------------|
|
||||||
|
| ועדת ערר רישוי ובניה (CMP) | `42a7acd0-30c5-4cbd-ac97-7424f65df294` | רישוי ובניה | **1xxx** | `752cebdd-6748-4a04-aacd-c7ab0294ef33` |
|
||||||
|
| ועדת ערר היטלי השבחה (CMPA) | `8639e837-4c9d-47fa-a76b-95788d651896` | היטל השבחה + פיצויים ס' 197 | **8xxx, 9xxx** | `cdbfa8bc-3d61-41a4-a2e7-677ec7d34562` |
|
||||||
|
|
||||||
|
- אם `$PAPERCLIP_COMPANY_ID` = `42a7acd0...` → רק תיקים ש-**1xxx**
|
||||||
|
- אם `$PAPERCLIP_COMPANY_ID` = `8639e837...` → רק תיקים ש-**8xxx/9xxx**
|
||||||
|
- **אסור** ליצור פרויקט/issue/תוכן לתיק שלא בטווח שלך
|
||||||
|
- אם issue שהוקצה לך מכוון לתיק שלא בטווח — סרב בנימוס ב-comment, והעֵר את ה-CEO של החברה הנכונה
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1.5. טיפול ב-wake (skill הרשמי + תוספות שלנו)
|
||||||
|
|
||||||
|
ה-skill מסביר `PAPERCLIP_WAKE_PAYLOAD_JSON`, `APPROVAL_ID`, ו-`heartbeat-context` (Step 6). הוסף עליו:
|
||||||
|
|
||||||
|
**1.5א. אם `$PAPERCLIP_WAKE_PAYLOAD_JSON` מכיל comment חדש מחיים** — התייחס אליו ב-comment הראשון שלך ("ראיתי שביקשת X — מבצע Y") **לפני** עבודה רחבה. זה מבטיח שחיים יודע שקלטת.
|
||||||
|
|
||||||
|
**1.5ב. תמיד לקרוא `heartbeat-context`** — לא רק מה ש-skill ממליץ ("Prefer"). אצלנו ה-`attachments` המוחזרים חיוניים (חיים מעלה DOCX/PDF דרך comments). ראה §2.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
CONTEXT=$(~/legal-ai/scripts/pc.sh GET "/api/issues/$ISSUE_ID/heartbeat-context?wakeCommentId=$LATEST_COMMENT_ID")
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" | jq '[.[] | select(.authorUserId != null)] | .[-3:]'
|
ATTACHMENTS=$(echo "$CONTEXT" | jq '.attachments')
|
||||||
```
|
```
|
||||||
|
|
||||||
- אם יש comment מחיים (authorUserId, לא authorAgentId) שנכתב **אחרי** ה-comment האחרון שלך — **קרא אותו בתשומת לב**
|
**1.5ג. APPROVAL_ID flow** — אם חיים ענה על interaction (ראה `legal-ceo.md` §B/§C/§D), קרא תשובה דרך:
|
||||||
- אם ה-comment מכיל הוראות עבודה — **עקוב אחריהן**
|
```bash
|
||||||
- אם ה-comment מזכיר קובץ שהועלה — בדוק attachments (ראה 2c)
|
~/legal-ai/scripts/pc.sh GET "/api/issues/$PAPERCLIP_TASK_ID/interactions/$PAPERCLIP_APPROVAL_ID" | jq '{status, kind, response}'
|
||||||
- אם ה-comment מבקש להעביר לסוכן אחר — **עצור**, פרסם comment שמאשר, והעֵר את ה-CEO
|
```
|
||||||
|
**אסור** לפענח טקסט מ-comment חופשי כשיש APPROVAL_ID — זה הקלט הסטרוקטורלי.
|
||||||
|
|
||||||
## 2c. בדוק קבצים מצורפים
|
---
|
||||||
|
|
||||||
אם comment מחיים מזכיר קובץ או טיוטה:
|
## §2. קבצים מצורפים — דרך `heartbeat-context`, **לא psql**
|
||||||
|
|
||||||
|
ה-attachments זמינים ב-`$CONTEXT.attachments` (מ-§1.5ב):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PGPASSWORD="paperclip" psql -h 127.0.0.1 -p 54329 -U paperclip -d paperclip -c "
|
echo "$CONTEXT" | jq '.attachments[] | {filename, contentPath, contentType, byteSize}'
|
||||||
SELECT a.original_filename, a.content_type, a.object_key, a.byte_size
|
|
||||||
FROM issue_attachments ia
|
# נתיב מלא לקובץ:
|
||||||
JOIN assets a ON a.id = ia.asset_id
|
CONTENT_PATH=$(echo "$CONTEXT" | jq -r '.attachments[0].contentPath')
|
||||||
WHERE ia.issue_id = '{issue-id}'
|
FULL_PATH="/home/chaim/.paperclip/instances/default/data/storage/$CONTENT_PATH"
|
||||||
ORDER BY ia.created_at DESC LIMIT 5;"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- נתיב מלא לקובץ: `/home/chaim/.paperclip/instances/default/data/storage/{object_key}`
|
קבצי DOCX/PDF — קרא עם `Read` tool ב-`$FULL_PATH`.
|
||||||
- קבצי DOCX — קרא אותם עם `Read`
|
|
||||||
- השתמש בתוכן הקובץ כקלט לעבודתך
|
|
||||||
|
|
||||||
## 3. Checkout ועבודה
|
⚠️ **`psql` ישיר ל-`issue_attachments` — אסור.** ה-API הוא ה-source of truth (Gap #21).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3. self-recovery — `issue.released` bug
|
||||||
|
|
||||||
|
⚠️ **Paperclip quirk ידוע**: לאחר ש-issue מסומן `done`, מנגנון `issue.released` עלול להחזיר אותו ל-`todo` תוך ~30s, וגורם ל-wakeup חוזר על משימה שכבר בוצעה (תועד ב-`docs/paperclip-quirks.md §1`).
|
||||||
|
|
||||||
|
**לפני שמתחילים עבודה — בדוק שלא בוצעה כבר:**
|
||||||
|
|
||||||
|
1. **תוצרים בדיסק**: `Glob` על תיקיות output הצפויות (`{case_dir}/documents/research/*.md` לחוקר, `analysis-and-research.md` למנתח, וכו')
|
||||||
|
2. **תוצרים ב-DB**: דרך MCP — `precedent_list`, `get_claims`, `extract_appraiser_facts` (status=completed)
|
||||||
|
3. **comments קודמים** — חפש "הושלם בהצלחה" מסוף-מצב
|
||||||
|
|
||||||
|
**אם הכל קיים ותקין:** פרסם comment קצר ("אין שינוי — תוצרים קיימים מהריצה הקודמת"), `PATCH status=done`, צא נקי. **לא לעבוד פעמיים.**
|
||||||
|
|
||||||
|
**אם משהו חסר/שונה:** עבוד רק על מה שחסר.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4. דיווח — חובה!
|
||||||
|
|
||||||
|
**כל heartbeat שמסיים משימה:** comment + status + wake CEO. הסעיף הזה מתעד רק workarounds שלנו לא ב-skill.
|
||||||
|
|
||||||
|
### §4א. dual-comment workaround ל-`backtick trap`
|
||||||
|
|
||||||
|
**ל-body קצר (<500 תווים, בלי backticks/קוד/נתיבים)** — pattern רגיל:
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/issues/{issue-id}/comments" '{"body": "סיכום..."}'
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/checkout"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- עבוד על המשימה לפי ההוראות ב-AGENTS.md שלך
|
**ל-body ארוך עם markdown/backticks/נתיבים — חובה שתי פעולות נפרדות:**
|
||||||
- השתמש בכלים המשפטיים (legal-ai MCP)
|
|
||||||
|
|
||||||
### ⚠️ self-recovery — issue ב-`todo` עם תוצרים קיימים
|
1. כתוב את ה-JSON לקובץ זמני דרך **Write tool** (לא bash heredoc):
|
||||||
|
|
||||||
ל-Paperclip יש באג ידוע: לאחר ש-issue מתעדכן ל-`done`, מנגנון `issue.released` מחזיר אותו ל-`todo` תוך כ-30 שניות (תועד ב-`docs/paperclip-quirks.md §1`). זה גורם ל-wakeup חוזר של אותו סוכן על משימה שכבר בוצעה.
|
|
||||||
|
|
||||||
**לפני שאתה מתחיל עבודה — בדוק שהמשימה לא בוצעה כבר**:
|
|
||||||
|
|
||||||
1. **בדוק תוצרים בדיסק**: `Glob` על תיקיות ה-output הצפויות (`{case_dir}/documents/research/*.md` לחוקר, `analysis-and-research.md` למנתח, וכו')
|
|
||||||
2. **בדוק תוצרים ב-DB**: דרך MCP — `precedent_list`, `get_claims`, `extract_appraiser_facts` (status=completed)
|
|
||||||
3. **בדוק comments קודמים על ה-issue** — אם הסוכן הקודם פרסם "הושלם בהצלחה" מסוף-מצב
|
|
||||||
|
|
||||||
**אם הכל קיים ותקין**: אל תבצע עבודה כפולה. במקום זאת:
|
|
||||||
- פרסם comment קצר: "אין שינוי — כל התוצרים קיימים מהריצה הקודמת (X פריטים ב-DB, קובץ Y בדיסק). סוגר את ה-issue."
|
|
||||||
- `PATCH /api/issues/{id}` → `done`
|
|
||||||
- צא נקי
|
|
||||||
|
|
||||||
**אם משהו חסר/שונה**: עבוד על מה שחסר בלבד, לא על הכל מחדש.
|
|
||||||
|
|
||||||
## 4. דיווח — חובה!
|
|
||||||
|
|
||||||
**לפני שאתה מסיים, תמיד:**
|
|
||||||
|
|
||||||
### 4א. פרסם comment על ה-issue
|
|
||||||
|
|
||||||
**ל-body קצר (<500 תווים, בלי backticks/קוד/נתיבים):**
|
|
||||||
```bash
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \
|
|
||||||
-d '{"body": "סיכום העבודה..."}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**ל-body ארוך / markdown עם נתיבים בbacktick / קוד — חובה שתי פעולות נפרדות:**
|
|
||||||
|
|
||||||
1. כתוב את ה-JSON לקובץ זמני דרך **Write tool** (לא דרך bash heredoc):
|
|
||||||
```
|
```
|
||||||
Write(file_path="/tmp/comment-{issue-id}.json",
|
Write(file_path="/tmp/comment-{issue-id}.json",
|
||||||
content=json.dumps({"body": markdown_body}, ensure_ascii=False))
|
content=json.dumps({"body": markdown_body}, ensure_ascii=False))
|
||||||
```
|
```
|
||||||
|
|
||||||
2. אז `curl -d @file` שקורא את הקובץ ישירות — בלי shell expansion:
|
2. אז `pc.sh` עם `-d @file` שקורא את הקובץ ישירות:
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/issues/{issue-id}/comments" "" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" -d @/tmp/comment-{issue-id}.json
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \
|
|
||||||
-d @/tmp/comment-{issue-id}.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ למה לא bash heredoc / `python3 -c`:** backticks ב-markdown (`` `path/to/file` ``) ייפרשו על ידי bash כ-command substitution גם כשהם בתוך מחרוזת Python. תקבל שגיאת `Permission denied` מטעה (`bash` מנסה להריץ את הנתיב כפקודה). הפתרון של temp-file חוסם את כל ה-shell quoting traps. תועד ב-`docs/paperclip-quirks.md §2`.
|
⚠️ **למה לא bash heredoc / `python3 -c`:** backticks ב-markdown (`` `path/to/file` ``) ייפרשו על-ידי bash כ-command substitution גם בתוך מחרוזת Python. תקבל `Permission denied` מטעה. תועד ב-`docs/paperclip-quirks.md §2`.
|
||||||
|
|
||||||
### 4ב. קבע סטטוס — done או blocked
|
### §4ב. סטטוס: `done` או `blocked` — לא ביניים
|
||||||
|
|
||||||
**אם המשימה הושלמה בהצלחה** (כל המסמכים חולצו, כל הבדיקות עברו, אין חסימות):
|
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}' # הצליח
|
||||||
-H "Content-Type: application/json" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}' # נכשל / חסום
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**אם המשימה נכשלה או חסומה** (מסמך לא חולץ, timeout, חוסר מידע, שגיאה שלא ניתנת לפתרון):
|
**אסור** `done` עם כשל שלא טופל. אם משהו נכשל → `blocked` + comment עם פירוט.
|
||||||
```bash
|
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים issue כ-"done" אם יש כשל שלא טופל. "done" = הכל הושלם בהצלחה. אם משהו נכשל — "blocked".
|
|
||||||
|
|
||||||
### 4ג. העֵר את העוזר המשפטי (CEO) — חובה!
|
### §4ג. wake CEO לפי חברה
|
||||||
אחרי כל סיום משימה (done או blocked), **העֵר את העוזר המשפטי של החברה שלך** כדי שיבדוק תוצאות ויחליט על הצעד הבא:
|
|
||||||
|
|
||||||
**⚠️ בחר CEO לפי חברה:**
|
**⚠️ CEO שונה לכל חברה** (ראה §1). UUID hardcoded **אסור** — תמיד דרך `$PAPERCLIP_COMPANY_ID`:
|
||||||
| חברה | COMPANY_ID | CEO Agent ID |
|
|
||||||
|------|------------|-------------|
|
|
||||||
| רישוי ובניה (CMP) | `42a7acd0-...` | `752cebdd-6748-4a04-aacd-c7ab0294ef33` |
|
|
||||||
| היטלי השבחה (CMPA) | `8639e837-...` | `cdbfa8bc-3d61-41a4-a2e7-677ec7d34562` |
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# קבע CEO_ID לפי חברה:
|
|
||||||
if [ "$PAPERCLIP_COMPANY_ID" = "8639e837-4c9d-47fa-a76b-95788d651896" ]; then
|
if [ "$PAPERCLIP_COMPANY_ID" = "8639e837-4c9d-47fa-a76b-95788d651896" ]; then
|
||||||
CEO_ID="cdbfa8bc-3d61-41a4-a2e7-677ec7d34562"
|
CEO_ID="cdbfa8bc-3d61-41a4-a2e7-677ec7d34562" # CMPA
|
||||||
else
|
else
|
||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33"
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" \
|
||||||
-H "Content-Type: application/json" \
|
'{"source":"automation","triggerDetail":"system","reason":"סוכן [שם] סיים [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"סוכן [שמך] סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ כללי ברזל — Paperclip API:**
|
⚠️ **חובה `payload.issueId`** — בלי זה הסוכן מתעורר בלי הקשר (בלי תיק, בלי cwd).
|
||||||
1. **אסור** `INSERT INTO agent_wakeup_requests` — לא יוצר heartbeat_run, הסוכן לא יתעורר לעולם
|
⚠️ **wakeup לחברה אחרת נדחה** — `Agent key cannot access another company`.
|
||||||
2. **חובה** `payload.issueId` בכל wakeup — בלי זה הסוכן מתעורר בלי הקשר (בלי תיק, בלי cwd)
|
⚠️ **אסור** `INSERT INTO agent_wakeup_requests` ישיר — לא יוצר heartbeat_run, הסוכן לא מתעורר.
|
||||||
3. **agent JWT לא יכול להעיר סוכנים אחרים** — רק את עצמו. כדי להעיר סוכן אחר → צור issue + הקצה אליו (Paperclip מפעיל wakeup אוטומטי)
|
|
||||||
|
|
||||||
**נתיבי API:**
|
---
|
||||||
| פעולה | נתיב |
|
|
||||||
|-------|-------|
|
|
||||||
| פרסום comment | `POST /api/issues/{issue-id}/comments` |
|
|
||||||
| יצירת issue | `POST /api/companies/{company-id}/issues` |
|
|
||||||
| עדכון issue | `PATCH /api/issues/{issue-id}` |
|
|
||||||
| wakeup עצמי/CEO | `POST /api/agents/{agent-id}/wakeup` (עם payload!) |
|
|
||||||
|
|
||||||
## 5. התראת מייל — כשנדרשת תשובה אנושית
|
## §5. התראת מייל — כשנדרשת תשובה אנושית
|
||||||
|
|
||||||
**כשהתוצאה דורשת החלטה או תשובה של חיים**, שלח מייל:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 /home/chaim/legal-ai/scripts/notify.py \
|
python3 /home/chaim/legal-ai/scripts/notify.py \
|
||||||
@@ -199,22 +167,29 @@ python3 /home/chaim/legal-ai/scripts/notify.py \
|
|||||||
"תוכן ההודעה עם סיכום מה נדרש"
|
"תוכן ההודעה עם סיכום מה נדרש"
|
||||||
```
|
```
|
||||||
|
|
||||||
**מתי לשלוח — תמיד:**
|
**מתי לשלוח (תמיד):** סיום כל משימה (סיכום קצר), בקשת תוצאה/כיוון, QA fail, החלטה מוכנה לדפנה, מצב שדורש פעולה אנושית, שגיאה לא פתירה.
|
||||||
- **סיום כל משימה** — עם סיכום קצר של מה בוצע
|
|
||||||
- בקשה לקביעת תוצאה (דחייה/קבלה/חלקית)
|
|
||||||
- בקשה לאישור כיוון נימוק
|
|
||||||
- דוח QA שנכשל (צריך החלטה על תיקונים)
|
|
||||||
- החלטה מוכנה לביקורת דפנה
|
|
||||||
- כל מצב שדורש פעולה אנושית ולא יכול להתקדם לבד
|
|
||||||
- שגיאה שלא ניתן לפתור ללא התערבות
|
|
||||||
|
|
||||||
**מתי לא לשלוח:**
|
**מתי לא:** עדכוני סטטוס ביניים, שגיאות טכניות שאפשר לפתור לבד.
|
||||||
- עדכוני סטטוס ביניים (רק בסיום)
|
|
||||||
- שגיאות טכניות שאפשר לפתור לבד
|
|
||||||
|
|
||||||
## 6. Release
|
---
|
||||||
|
|
||||||
|
## §6. Release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/issues/{issue-id}/release"
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/release"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## נתיבי API — הפניה ל-skill הרשמי
|
||||||
|
|
||||||
|
| פעולה | איפה ב-skill |
|
||||||
|
|--------|---------------|
|
||||||
|
| Identity, inbox, pick work | Step 1, 3, 4 |
|
||||||
|
| Wake payload + APPROVAL handling | Authentication + Step 2 |
|
||||||
|
| Heartbeat-context, comments, attachments | Step 6 |
|
||||||
|
| Checkout (with the `checkedOutByHarness` skip) | Step 5 |
|
||||||
|
| Comment, status update, exit | Step 7-8 |
|
||||||
|
| Routines, workflows, references | `references/` ב-skill |
|
||||||
|
|
||||||
|
**שינויים project-specific מה-skill:** תועדו בקובץ זה (§0 pc.sh, §1 חברה, §2 attachments, §3 quirk, §4 dual-comment + CEO wakeup, §5 notify).
|
||||||
|
|||||||
@@ -170,11 +170,75 @@ tools:
|
|||||||
- **לא להמציא פסיקה** — אם יש אזכור במסמכי התיק, ניתן להתייחס. אם לא — נסח ללא הפניה
|
- **לא להמציא פסיקה** — אם יש אזכור במסמכי התיק, ניתן להתייחס. אם לא — נסח ללא הפניה
|
||||||
- שימוש במונחים מקובלים בפסיקה הישראלית (מתאים לחיפוש ב-nevo/law-mate)
|
- שימוש במונחים מקובלים בפסיקה הישראלית (מתאים לחיפוש ב-nevo/law-mate)
|
||||||
|
|
||||||
## שלב 5: חיפוש פנימי בקורפוס
|
## שלב 5: חיפוש בשלושת הקורפוסים — חובה, עם תיעוד queries
|
||||||
חפש תקדימים רלוונטיים בקורפוס הפנימי:
|
|
||||||
- `search_decisions` — בהחלטות קודמות של דפנה
|
**חובה לבצע** — לא הצעה. בלי השלב הזה הניתוח חסר תקדימי-עליון רלוונטיים, וה-writer לא יוכל לכתוב CREAC מלא. נבחן ב-QA.
|
||||||
- `find_similar_cases` — תיקים דומים
|
|
||||||
הוסף תוצאות רלוונטיות תחת כל סוגיה כ-"תקדימים מהקורפוס הפנימי".
|
### 5א. חיפוש בקורפוס הסמכותי (`search_precedent_library`) — חובה
|
||||||
|
|
||||||
|
לכל **טענת סף** ולכל **סוגיה מרכזית** שזיהית — הרץ לפחות שאילתה אחת ל-`search_precedent_library` עם פילטרים:
|
||||||
|
|
||||||
|
| סיווג תיק | practice_area |
|
||||||
|
|------------|---------------|
|
||||||
|
| 1xxx (רישוי ובניה) | `rishuy_uvniya` |
|
||||||
|
| 8xxx (היטל השבחה) | `histael_hashbacha` |
|
||||||
|
| 9xxx (פיצויים ס' 197) | `pitsuim_197` |
|
||||||
|
|
||||||
|
אם הסוגיה מאוזכרת ב-`appeal_subtype` ידוע (כמו "שימוש חורג", "חריגות בנייה", "סטייה ניכרת") — הוסף `appeal_subtype` לפילטר. צמצום מוקדם > הרחבה מאוחרת.
|
||||||
|
|
||||||
|
דוגמה:
|
||||||
|
```
|
||||||
|
search_precedent_library(
|
||||||
|
query="שימוש חורג מסחרי בייעוד נופש",
|
||||||
|
practice_area="rishuy_uvniya",
|
||||||
|
appeal_subtype="שימוש חורג",
|
||||||
|
limit=10
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5ב. חיפוש בקאנון של דפנה (`search_decisions`)
|
||||||
|
|
||||||
|
לכל סוגיה — הרץ `search_decisions` כדי למצוא החלטות קודמות של דפנה באותה קטגוריה. אם דפנה כבר הכריעה בסוגיה דומה — תקדם אישי הוא חלק חובה מההנמקה (חיסכון או הבחנה).
|
||||||
|
|
||||||
|
### 5ג. תיקים דומים (`find_similar_cases`)
|
||||||
|
|
||||||
|
לכל סוגיה מרכזית — הרץ `find_similar_cases` לזיהוי דפוסים מבניים דומים בארכיון.
|
||||||
|
|
||||||
|
### 5ד. תיעוד מחייב — סעיף "שאילתות לקורפוסים" ב-`analysis-and-research.md`
|
||||||
|
|
||||||
|
ב-artifact הסופי, חובה להופיע סעיף חדש בשם **"7א. שאילתות לקורפוסים — log מלא"**, עם הפורמט הבא:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 7א. שאילתות לקורפוסים — log מלא
|
||||||
|
|
||||||
|
### קורפוס סמכותי (search_precedent_library)
|
||||||
|
|
||||||
|
#### Q1 — סוגיה: [שם הסוגיה]
|
||||||
|
- **שאילתה:** "..."
|
||||||
|
- **פילטרים:** practice_area=..., appeal_subtype=...
|
||||||
|
- **תוצאות:** N
|
||||||
|
- **נבחרו:**
|
||||||
|
- `[case_number]` — [למה רלוונטי, איזה headnote תומך]
|
||||||
|
- **נדחו:**
|
||||||
|
- `[case_number]` — [למה לא רלוונטי]
|
||||||
|
- **0 results?** ציין מפורש + נמק (אין מה למצוא, או הפילטר צר מדי)
|
||||||
|
|
||||||
|
#### Q2 — ...
|
||||||
|
|
||||||
|
### קאנון דפנה (search_decisions)
|
||||||
|
|
||||||
|
#### Q1 — סוגיה: [שם]
|
||||||
|
- **שאילתה:** "..."
|
||||||
|
- **תוצאות:** N
|
||||||
|
- **תקדים אישי שזוהה:** [שם תיק] — חיסכון/הבחנה?
|
||||||
|
|
||||||
|
### תיקים דומים (find_similar_cases)
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**negative evidence חובה:** גם כששאילתה החזירה 0 תוצאות, חובה לתעד אותה. זה ההבדל בין "הקורפוס נסרק וריק" ל"הקורפוס לא נסרק". ה-QA יחזיר `needs_revision` אם הסעיף חסר או חסר queries.
|
||||||
|
|
||||||
|
**מינימום:** מספר queries ב-Q1+Q2+Q3 לקורפוס הסמכותי = מספר טענות סף + מספר סוגיות מרכזיות. אם זיהית 5 סוגיות + 2 טענות סף → לפחות 7 queries.
|
||||||
|
|
||||||
## שלב 6: בדיקת שלמות — לפני שמסיימים!
|
## שלב 6: בדיקת שלמות — לפני שמסיימים!
|
||||||
|
|
||||||
@@ -224,19 +288,11 @@ FROM documents d WHERE d.case_id = '{case_id}' AND d.doc_type IN ('appeal', 'res
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה (בדיקות שלב 6 + טענות + עובדות שמאי):**
|
**אם הכל עבר בהצלחה (בדיקות שלב 6 + טענות + עובדות שמאי):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם בדיקות שלב 6 נכשלו או חילוץ נכשל:**
|
**אם בדיקות שלב 6 נכשלו או חילוץ נכשל:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם ניסיון חוזר נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם ניסיון חוזר נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
5. **שלח מייל**:
|
5. **שלח מייל**:
|
||||||
@@ -255,11 +311,7 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"מנתח משפטי סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"מנתח משפטי סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|
||||||
@@ -337,8 +389,12 @@ X שאלות עומדות להכרעה:
|
|||||||
- סעיף X לחוק...
|
- סעיף X לחוק...
|
||||||
(הערה: התחל מלשון הטקסט הנורמטיבי. תקדים נדרש רק כשהטקסט עמום.)
|
(הערה: התחל מלשון הטקסט הנורמטיבי. תקדים נדרש רק כשהטקסט עמום.)
|
||||||
|
|
||||||
**תקדימים מהקורפוס הפנימי:**
|
**תקדימים מהקורפוס הסמכותי (search_precedent_library):**
|
||||||
- [אם נמצאו]
|
- [תקדים שנבחר עם citation, headnote, רלוונטיות]
|
||||||
|
- (חובה לפחות שאילתה אחת ב-Q1 בסעיף 7א — גם אם 0 תוצאות, יש לתעד שם)
|
||||||
|
|
||||||
|
**תקדימים מהקאנון של דפנה (search_decisions):**
|
||||||
|
- [אם נמצאו — חיסכון או הבחנה?]
|
||||||
|
|
||||||
**עמדת ועדת הערר:**
|
**עמדת ועדת הערר:**
|
||||||
[ימולא ע"י יו"ר הוועדה — עמדה/הנחיה לגבי סוגיה זו שתשמש את סוכן הכתיבה]
|
[ימולא ע"י יו"ר הוועדה — עמדה/הנחיה לגבי סוגיה זו שתשמש את סוכן הכתיבה]
|
||||||
@@ -362,6 +418,9 @@ X שאלות עומדות להכרעה:
|
|||||||
- **סדר דיון מומלץ**: הסדר המומלץ לדיון בסוגיות בהחלטה
|
- **סדר דיון מומלץ**: הסדר המומלץ לדיון בסוגיות בהחלטה
|
||||||
- **תלויות**: סוגיות שהכרעתן תלויה בהכרעה בסוגיה אחרת
|
- **תלויות**: סוגיות שהכרעתן תלויה בהכרעה בסוגיה אחרת
|
||||||
- **הערכה כללית**: לאן נוטה הניתוח ומהם הסיכויים הכלליים של הערר
|
- **הערכה כללית**: לאן נוטה הניתוח ומהם הסיכויים הכלליים של הערר
|
||||||
|
|
||||||
|
## 7א. שאילתות לקורפוסים — log מלא
|
||||||
|
[סעיף חובה לפי שלב 5ד — log כל קריאה ל-search_precedent_library, search_decisions, find_similar_cases. גם 0 results.]
|
||||||
```
|
```
|
||||||
|
|
||||||
## שלב 8: העמקת ניתוח (pass 2) — אחרי אישור כיוון
|
## שלב 8: העמקת ניתוח (pass 2) — אחרי אישור כיוון
|
||||||
@@ -373,10 +432,14 @@ X שאלות עומדות להכרעה:
|
|||||||
### 8א. אימות פסיקה
|
### 8א. אימות פסיקה
|
||||||
סרוק את עמדות היו"ר וזהה כל אזכור פסיקה (בג"ץ, עע"מ, עת"מ, ע"א, ערר וכו').
|
סרוק את עמדות היו"ר וזהה כל אזכור פסיקה (בג"ץ, עע"מ, עת"מ, ע"א, ערר וכו').
|
||||||
לכל פסק דין שמוזכר:
|
לכל פסק דין שמוזכר:
|
||||||
1. חפש בקורפוס הפנימי (`search_decisions`, `find_similar_cases`)
|
1. חפש ב**קורפוס הסמכותי** (`search_precedent_library`) — חובה ראשונה. שם נמצאות הלכות מאושרות עם supporting_quote מוכן לציטוט.
|
||||||
2. חפש במסמכי התיק (`search_case_documents`) — אולי מצוטט בכתבי הטענות
|
2. חפש בקאנון דפנה (`search_decisions`, `find_similar_cases`)
|
||||||
3. **אם נמצא** — חלץ ציטוט מדויק, הקשר, רלוונטיות
|
3. חפש במסמכי התיק (`search_case_documents`) — אולי מצוטט בכתבי הטענות
|
||||||
4. **אם לא נמצא** — סמן: "דורש אימות חיצוני" + נסח הנחיות חיפוש
|
4. **אם נמצא ב-precedent_library** — צטט citation+supporting_quote מדויקים מהקורפוס.
|
||||||
|
5. **אם נמצא רק במסמכי התיק** — סמן: "מקור: כתבי טענות, דורש אימות מול הקורפוס".
|
||||||
|
6. **אם לא נמצא בכלל** — סמן: "דורש אימות חיצוני" + נסח הנחיות חיפוש.
|
||||||
|
|
||||||
|
הוסף לסעיף "7א. שאילתות לקורפוסים" כל query נוסף שהורצה ב-pass 2.
|
||||||
|
|
||||||
הוסף לכל סוגיה תת-סעיף:
|
הוסף לכל סוגיה תת-סעיף:
|
||||||
|
|
||||||
@@ -419,11 +482,7 @@ X שאלות עומדות להכרעה:
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"מנתח משפטי סיים העמקת ניתוח (pass 2) [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"מנתח משפטי סיים העמקת ניתוח (pass 2) [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אם ה-API מחזיר שגיאה — אל תיגע ב-DB.** `INSERT INTO agent_wakeup_requests` לא יוצר `heartbeat_run` והסוכן לא יתעורר לעולם. בדוק `$PAPERCLIP_COMPANY_ID` ו-`$PAPERCLIP_API_KEY`, ודאי שאתה לא קורא ל-CEO של חברה אחרת (`Agent key cannot access another company`).
|
**⚠️ אם ה-API מחזיר שגיאה — אל תיגע ב-DB.** `INSERT INTO agent_wakeup_requests` לא יוצר `heartbeat_run` והסוכן לא יתעורר לעולם. בדוק `$PAPERCLIP_COMPANY_ID` ו-`$PAPERCLIP_API_KEY`, ודאי שאתה לא קורא ל-CEO של חברה אחרת (`Agent key cannot access another company`).
|
||||||
|
|
||||||
## כללים קריטיים
|
## כללים קריטיים
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ tools:
|
|||||||
- mcp__legal-ai__precedent_library_list
|
- mcp__legal-ai__precedent_library_list
|
||||||
- mcp__legal-ai__halacha_review
|
- mcp__legal-ai__halacha_review
|
||||||
- mcp__legal-ai__halachot_pending
|
- mcp__legal-ai__halachot_pending
|
||||||
|
- mcp__legal-ai__extract_appraiser_facts
|
||||||
|
- mcp__legal-ai__write_interim_draft
|
||||||
|
- mcp__legal-ai__export_interim_draft
|
||||||
---
|
---
|
||||||
|
|
||||||
# עוזר משפטי — מנהל תהליך כתיבת החלטות
|
# עוזר משפטי — מנהל תהליך כתיבת החלטות
|
||||||
@@ -92,10 +95,7 @@ tools:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# שלב 1: יצירת issue
|
# שלב 1: יצירת issue
|
||||||
ISSUE_ID=$(curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
ISSUE_ID=$(~/legal-ai/scripts/pc.sh POST "/api/companies/$PAPERCLIP_COMPANY_ID/issues" '{"title": "[ערר CASE_NUMBER] ....", "description": "...", "parentId": "'$PAPERCLIP_TASK_ID'", "assigneeAgentId": "..."}' \
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues" \
|
|
||||||
-d '{"title": "[ערר CASE_NUMBER] ....", "description": "...", "parentId": "'$PAPERCLIP_TASK_ID'", "assigneeAgentId": "..."}' \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||||
|
|
||||||
# שלב 2 (חובה!): קישור ל-case number בעוזר המשפטי
|
# שלב 2 (חובה!): קישור ל-case number בעוזר המשפטי
|
||||||
@@ -223,7 +223,9 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
|
|
||||||
**מתי:** כשיש טענות מחולצות + מחקר תקדימים, אבל אין תוצאה עדיין
|
**מתי:** כשיש טענות מחולצות + מחקר תקדימים, אבל אין תוצאה עדיין
|
||||||
|
|
||||||
פרסם comment ב-Paperclip:
|
**שיטה — dual dispatch:** קודם פרסם comment עם הסיכום המלא (לתיעוד), ואז צור interaction עם כפתורים (לחיים).
|
||||||
|
|
||||||
|
#### B.1 פרסם comment עם הסיכום
|
||||||
|
|
||||||
```
|
```
|
||||||
## סיכום תיק {case_number} — מוכן להחלטה
|
## סיכום תיק {case_number} — מוכן להחלטה
|
||||||
@@ -259,135 +261,151 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
- כלל: ...
|
- כלל: ...
|
||||||
- עובדות: ...
|
- עובדות: ...
|
||||||
- שאלה: ...
|
- שאלה: ...
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**מה התוצאה הצפויה?**
|
|
||||||
1. 🔴 **דחייה** — הערר נדחה
|
|
||||||
2. 🟡 **קבלה חלקית** — מתקבל עם תנאים
|
|
||||||
3. 🟢 **קבלה מלאה** — הערר מתקבל
|
|
||||||
|
|
||||||
@chaim — הגב עם מספר (1/2/3) + הערות אם יש
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**אחרי פרסום ה-comment:** עדכן את ה-issue הראשי ל-`status=in_review` (ראה "כלל קריטי: ניהול סטטוס issue" בראש הסעיף).
|
#### B.2 צור interaction לבחירת תוצאה + טיפול בטענות
|
||||||
|
|
||||||
לאחר שחיים בחר תוצאה, שאל אותו לסמן טיפול בכל טענה:
|
```bash
|
||||||
|
~/legal-ai/scripts/pc.sh POST "/api/issues/$PAPERCLIP_TASK_ID/interactions" '{
|
||||||
```
|
"kind": "ask_user_questions",
|
||||||
## טיפול בטענות — {case_number}
|
"idempotencyKey": "outcome:'"$PAPERCLIP_TASK_ID"':v1",
|
||||||
|
"title": "תוצאה וטיפול בטענות — {case_number}",
|
||||||
סמן לכל טענה את סוג הטיפול:
|
"summary": "ראה את הסיכום ב-comment לעיל. שתי שאלות מובנות.",
|
||||||
|
"continuationPolicy": "wake_assignee",
|
||||||
| # | טענה | טיפול |
|
"payload": {
|
||||||
|---|------|-------|
|
"version": 1,
|
||||||
| 1 | {טענה 1} | דיון מלא / קיבוץ / דילוג |
|
"submitLabel": "המשך לכיוונים",
|
||||||
| 2 | {טענה 2} | דיון מלא / קיבוץ / דילוג |
|
"questions": [
|
||||||
| 3 | {טענה 3} | דיון מלא / קיבוץ / דילוג |
|
{
|
||||||
| ... | ... | ... |
|
"id": "outcome",
|
||||||
|
"prompt": "מה התוצאה?",
|
||||||
**הסבר:**
|
"selectionMode": "single",
|
||||||
- **דיון מלא** — ניתוח סילוגיסטי מלא (כלל → עובדות → מסקנה)
|
"required": true,
|
||||||
- **קיבוץ** — טענות שמכוונות לאותה נקודה ייאגדו יחד
|
"options": [
|
||||||
- **דילוג** — "לא מצאנו ממש" או "אין צורך להכריע נוכח מסקנתנו"
|
{"id":"reject", "label":"דחייה", "description":"הערר נדחה"},
|
||||||
|
{"id":"partial","label":"קבלה חלקית","description":"מתקבל עם תנאים"},
|
||||||
@chaim — סמן בטבלה והחזר
|
{"id":"accept", "label":"קבלה מלאה","description":"הערר מתקבל"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "claims_treatment",
|
||||||
|
"prompt": "אילו טענות לדון בנפרד? (multi)",
|
||||||
|
"selectionMode": "multi",
|
||||||
|
"helpText": "סמן רק טענות שצריכות דיון מלא. השאר → קיבוץ או דילוג.",
|
||||||
|
"options": [
|
||||||
|
{"id":"claim_1","label":"{טענה 1 מקוצר}"},
|
||||||
|
{"id":"claim_2","label":"{טענה 2 מקוצר}"},
|
||||||
|
{"id":"claim_3","label":"{טענה 3 מקוצר}"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
**אחרי פרסום ה-comment:** עדכן את ה-issue הראשי ל-`status=in_review`.
|
**אחרי יצירת ה-interaction:** עדכן את ה-issue הראשי ל-`status=in_review` (ראה "כלל קריטי: ניהול סטטוס issue" בראש הסעיף). חיים יקבל UI עם dropdowns וכפתורי radio במקום להקליד מספרים.
|
||||||
|
|
||||||
|
⚠️ **`idempotencyKey`** — חובה. אם תתעורר פעמיים, Paperclip לא יוצר 2 interactions זהים.
|
||||||
|
|
||||||
**מתי לחזור אחורה:** אם הסיכום לא מצליח לנסח שאלות כסילוגיזמים מכווצים — ייתכן שחסר מידע עובדתי או נורמטיבי. חזור למנתח/חוקר להשלמה.
|
**מתי לחזור אחורה:** אם הסיכום לא מצליח לנסח שאלות כסילוגיזמים מכווצים — ייתכן שחסר מידע עובדתי או נורמטיבי. חזור למנתח/חוקר להשלמה.
|
||||||
|
|
||||||
### שלב C: קליטת תוצאה וכיוונים סילוגיסטיים
|
### שלב C: קליטת תוצאה וכיוונים סילוגיסטיים
|
||||||
|
|
||||||
**מתי:** חיים הגיב עם מספר תוצאה + טיפול בטענות
|
**מתי:** התעוררת עם `$PAPERCLIP_APPROVAL_ID` שמצביע על interaction מ-§B (תשובת תוצאה+טענות).
|
||||||
|
|
||||||
0. **החזר את ה-issue הראשי ל-`status=in_progress`** (קיבלת קלט והמשכת לעבוד).
|
0. **החזר את ה-issue הראשי ל-`status=in_progress`** (קיבלת קלט והמשכת לעבוד).
|
||||||
1. קרא את ה-comment של חיים
|
1. **קרא את תשובת חיים מה-API** (לא מ-comment חופשי):
|
||||||
2. זהה את הבחירה (1=rejected, 2=partial, 3=accepted)
|
```bash
|
||||||
3. הרץ `set_outcome(case_number, outcome, reasoning)`
|
~/legal-ai/scripts/pc.sh GET "/api/issues/$PAPERCLIP_TASK_ID/interactions/$PAPERCLIP_APPROVAL_ID" \
|
||||||
4. **חשוב סילוגיסטית** על 2-3 כיוונים לנימוק — אתה כבר Claude, אתה יודע את הטענות והתקדימים. בנה כל כיוון כסילוגיזם מלא.
|
| jq '{status, payload: .response}'
|
||||||
|
```
|
||||||
|
- תשובת `outcome`: `reject` / `partial` / `accept` (זהה ל-1/2/3 הישן)
|
||||||
|
- תשובת `claims_treatment`: array של claim IDs לדיון מלא
|
||||||
|
2. הרץ `set_outcome(case_number, outcome, reasoning)`
|
||||||
|
3. **חשוב סילוגיסטית** על 2-3 כיוונים לנימוק — אתה כבר Claude, אתה יודע את הטענות והתקדימים. בנה כל כיוון כסילוגיזם מלא.
|
||||||
|
|
||||||
> **הערה טכנית:** אל תקרא ל-`brainstorm_directions` — זה מפעיל Claude בתוך Claude ולוקח יותר מדי זמן.
|
> **הערה טכנית:** אל תקרא ל-`brainstorm_directions` — זה מפעיל Claude בתוך Claude ולוקח יותר מדי זמן.
|
||||||
|
|
||||||
5. פרסם comment עם **סדר סוגיות מוצע**:
|
4. פרסם comment קצר עם **סדר סוגיות מוצע** (לתיעוד thread):
|
||||||
|
|
||||||
```
|
```
|
||||||
## כיוונים אפשריים לנימוק — {outcome_hebrew}
|
## כיוונים לנימוק — {outcome_hebrew}
|
||||||
|
|
||||||
### סדר הסוגיות המוצע
|
### סדר הסוגיות המוצע
|
||||||
1. {שאלת סף — אם רלוונטית}
|
1. {שאלת סף — אם רלוונטית}
|
||||||
2. {הסוגיה המכריעה}
|
2. {הסוגיה המכריעה}
|
||||||
3. {סוגיות נוספות לפי חוזק}
|
3. {סוגיות נוספות לפי חוזק}
|
||||||
|
|
||||||
---
|
(הכיוונים המלאים — בinteraction למטה)
|
||||||
|
|
||||||
### כיוון 1: {title}
|
|
||||||
|
|
||||||
**כלל (הנחה עליונה):**
|
|
||||||
{הוראת תכנית / סעיף חוק / הלכה פסוקה}
|
|
||||||
|
|
||||||
**עובדות (הנחה תחתונה):**
|
|
||||||
{העובדות הספציפיות של הערר שנבחנות לאור הכלל}
|
|
||||||
|
|
||||||
**מסקנה:**
|
|
||||||
{התוצאה שנובעת מהחלת הכלל על העובדות}
|
|
||||||
|
|
||||||
**תקדימים תומכים:** {precedents}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### כיוון 2: {title}
|
|
||||||
|
|
||||||
**כלל (הנחה עליונה):**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**עובדות (הנחה תחתונה):**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**מסקנה:**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**תקדימים תומכים:** {precedents}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### כיוון 3: {title}
|
|
||||||
|
|
||||||
**כלל (הנחה עליונה):**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**עובדות (הנחה תחתונה):**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**מסקנה:**
|
|
||||||
{...}
|
|
||||||
|
|
||||||
**תקדימים תומכים:** {precedents}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
@chaim — איזה כיוון מועדף? (1/2/3)
|
|
||||||
אפשר גם לשלב כיוונים או להוסיף הערות.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**אחרי פרסום ה-comment:** עדכן את ה-issue הראשי ל-`status=in_review`.
|
5. צור **interaction לבחירת כיוון** עם detailsMarkdown מלא:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/legal-ai/scripts/pc.sh POST "/api/issues/$PAPERCLIP_TASK_ID/interactions" '{
|
||||||
|
"kind": "ask_user_questions",
|
||||||
|
"idempotencyKey": "direction:'"$PAPERCLIP_TASK_ID"':v1",
|
||||||
|
"title": "בחירת כיוון לנימוק — {case_number}",
|
||||||
|
"summary": "3 כיוונים סילוגיסטיים. בחר אחד או שלב.",
|
||||||
|
"continuationPolicy": "wake_assignee",
|
||||||
|
"payload": {
|
||||||
|
"version": 1,
|
||||||
|
"submitLabel": "אישור כיוון — להעברה לכותב",
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"id": "direction",
|
||||||
|
"prompt": "איזה כיוון מועדף?",
|
||||||
|
"selectionMode": "single",
|
||||||
|
"required": true,
|
||||||
|
"helpText": "ניתן לשלב כיוונים בהערות ב-comment נפרד אחרי הבחירה.",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"id": "direction_1",
|
||||||
|
"label": "כיוון 1: {title}",
|
||||||
|
"description": "כלל: {הוראת תכנית/סעיף חוק/הלכה}\nעובדות: {ספציפיות הערר}\nמסקנה: {התוצאה}\nתקדימים: {precedents}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "direction_2",
|
||||||
|
"label": "כיוון 2: {title}",
|
||||||
|
"description": "כלל: {...}\nעובדות: {...}\nמסקנה: {...}\nתקדימים: {precedents}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "direction_3",
|
||||||
|
"label": "כיוון 3: {title}",
|
||||||
|
"description": "כלל: {...}\nעובדות: {...}\nמסקנה: {...}\nתקדימים: {precedents}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ ה-`description` של כל option בעברית. ה-`label` קצר (3-4 מילים), ה-`description` הוא הסילוגיזם המלא — חיים רואה הכל בלי להקליד.
|
||||||
|
|
||||||
|
**אחרי יצירת ה-interaction:** עדכן את ה-issue הראשי ל-`status=in_review`.
|
||||||
|
|
||||||
**מתי לחזור אחורה:** אם לא ניתן לבנות סילוגיזם מלא (חסר כלל, חסרות עובדות, או המסקנה לא נובעת) — חזור לחוקר תקדימים או למנתח להשלמת החסר.
|
**מתי לחזור אחורה:** אם לא ניתן לבנות סילוגיזם מלא (חסר כלל, חסרות עובדות, או המסקנה לא נובעת) — חזור לחוקר תקדימים או למנתח להשלמת החסר.
|
||||||
|
|
||||||
### שלב D: אישור כיוון והפעלת כתיבה
|
### שלב D: אישור כיוון והפעלת כתיבה
|
||||||
|
|
||||||
**מתי:** חיים הגיב עם בחירת כיוון
|
**מתי:** התעוררת עם `$PAPERCLIP_APPROVAL_ID` שמצביע על interaction מ-§C (תשובת כיוון).
|
||||||
|
|
||||||
0. **החזר את ה-issue הראשי ל-`status=in_progress`** (קיבלת קלט והמשכת לעבוד).
|
0. **החזר את ה-issue הראשי ל-`status=in_progress`** (קיבלת קלט והמשכת לעבוד).
|
||||||
1. קרא את ה-comment של חיים
|
1. **קרא את תשובת חיים מה-API:**
|
||||||
2. זהה כיוון (1/2/3) + הערות נוספות
|
```bash
|
||||||
|
~/legal-ai/scripts/pc.sh GET "/api/issues/$PAPERCLIP_TASK_ID/interactions/$PAPERCLIP_APPROVAL_ID" \
|
||||||
|
| jq '{status, response: .response}'
|
||||||
|
```
|
||||||
|
- `response.direction` יחזיר `direction_1` / `direction_2` / `direction_3`
|
||||||
|
- אם יש הערות נוספות — חיים יוסיף ב-comment נפרד; קרא את ה-comments האחרונים
|
||||||
|
2. זהה את הכיוון מהתשובה (1/2/3 → לפי המספר ב-id)
|
||||||
3. **אימות שלמות chair_directions** — לפני שליחה לכותב, ודא:
|
3. **אימות שלמות chair_directions** — לפני שליחה לכותב, ודא:
|
||||||
- [ ] טיפול בטענות (דיון מלא / קיבוץ / דילוג) מוגדר לכל טענה
|
- [ ] טיפול בטענות (דיון מלא / קיבוץ / דילוג) מוגדר לכל טענה (מ-§B)
|
||||||
- [ ] כיוון סילוגיסטי נבחר ומאושר
|
- [ ] כיוון סילוגיסטי נבחר ומאושר (מ-§C — interaction status=`answered`)
|
||||||
- [ ] סדר סוגיות מוגדר
|
- [ ] סדר סוגיות מוגדר
|
||||||
- [ ] תקן ביקורת מצוין
|
- [ ] תקן ביקורת מצוין
|
||||||
- אם חסר פריט כלשהו — **שאל את חיים** לפני שממשיכים
|
- אם חסר פריט כלשהו — צור interaction חדש (`request_confirmation` או `ask_user_questions`) **לפני** שממשיכים. אסור לקרוא לחיים בcomment חופשי.
|
||||||
4. הרץ `approve_direction(case_number, direction_index, additional_notes)`
|
4. הרץ `approve_direction(case_number, direction_index, additional_notes)`
|
||||||
5. עדכן סטטוס: `case_update(status=direction_approved)`
|
5. עדכן סטטוס: `case_update(status=direction_approved)`
|
||||||
6. צור issue חדש ב-Paperclip:
|
6. צור issue חדש ב-Paperclip:
|
||||||
@@ -396,7 +414,7 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
- תיאור: "כיוון אושר. בצע pass 2: אמת פסיקה מעמדות היו"ר, העמק עובדות לאור הכיוון שנבחר."
|
- תיאור: "כיוון אושר. בצע pass 2: אמת פסיקה מעמדות היו"ר, העמק עובדות לאור הכיוון שנבחר."
|
||||||
7. פרסם comment: "כיוון אושר. הועבר למנתח להעמקת ניתוח לפני כתיבה."
|
7. פרסם comment: "כיוון אושר. הועבר למנתח להעמקת ניתוח לפני כתיבה."
|
||||||
|
|
||||||
**מתי לחזור אחורה:** אם חיים שינה דעתו לגבי התוצאה או הכיוון, או אם חסר מידע — חזור לשלב B או C בהתאם.
|
**מתי לחזור אחורה:** אם חיים דחה את ה-interaction (`status=rejected`) או שינה דעתו לגבי התוצאה או הכיוון, או אם חסר מידע — חזור לשלב B או C בהתאם וצור interaction חדש עם `idempotencyKey` מעודכן (לדוגמה `:v2`).
|
||||||
|
|
||||||
### שלב D2: אחרי העמקת ניתוח (pass 2)
|
### שלב D2: אחרי העמקת ניתוח (pass 2)
|
||||||
|
|
||||||
@@ -474,6 +492,72 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
- השתמש ב-`revise_draft` בלבד במצב ג'.
|
- השתמש ב-`revise_draft` בלבד במצב ג'.
|
||||||
- אם המשתמש ביקש שינוי מאסיבי (שכתוב מלא של בלוק) — עדיף להציע לו לעבוד על זה בעריכה נוספת מצדו ולא לייצר revisions ארוכים.
|
- אם המשתמש ביקש שינוי מאסיבי (שכתוב מלא של בלוק) — עדיף להציע לו לעבוד על זה בעריכה נוספת מצדו ולא לייצר revisions ארוכים.
|
||||||
|
|
||||||
|
### שלב H: טיוטת ביניים (לבקשת חיים, לפני דיון והכרעה)
|
||||||
|
|
||||||
|
**מתי:** חיים מבקש בקומנט "טיוטת ביניים" / "interim draft" / "טיוטה לפני דיון" / "תכין לי את הטיוטה עם טענות הצדדים". בכל שלב לפני שיש תוצאה (בד"כ כשהתיק ב-`research_complete` או `analyst_verified`).
|
||||||
|
|
||||||
|
**מטרה:** ייצור מסמך עבודה לחיים עם פתיחה ניטרלית, רקע, תכניות+היתרים, טענות הצדדים, והליכים — **בלי דיון והכרעה**. חיים יכתוב את בלוק י בעצמו ואז נמשיך לזרימה הרגילה (QA + ייצוא סופי).
|
||||||
|
|
||||||
|
**זה side-quest, לא חלק מהזרימה B-F.** אל תשנה `cases.status`. אל תייצר issues לסוכני משנה. הכלים `write_interim_draft` ו-`export_interim_draft` עושים הכל בעצמם.
|
||||||
|
|
||||||
|
**זרימה (~5-10 דקות):**
|
||||||
|
|
||||||
|
1. פרסם comment קצר: "מתחיל יצירת טיוטת ביניים — אעדכן בסיום." עדכן את ה-issue הראשי ל-`status=in_progress`.
|
||||||
|
|
||||||
|
2. **חילוץ עובדות שמאיות** (אם תיק 8xxx/9xxx ויש מסמכי שומה):
|
||||||
|
```
|
||||||
|
mcp__legal-ai__extract_appraiser_facts(case_number="...")
|
||||||
|
```
|
||||||
|
⚠️ אם מחזיר `status="sides_missing"` → דווח לחיים שאין תיוג `appraiser_side` במסמכי השומה (`document_update` עם `appraiser_side` בערכים `committee`/`appellant`/`deciding`). עצור עד שיתוקן.
|
||||||
|
|
||||||
|
אם הטבלה כבר מלאה — `write_interim_draft` ידלג על ההרצה אוטומטית, אז גם בלי הצעד הזה זה יעבוד.
|
||||||
|
|
||||||
|
3. **כתיבת 5 הבלוקים:**
|
||||||
|
```
|
||||||
|
mcp__legal-ai__write_interim_draft(
|
||||||
|
case_number="...",
|
||||||
|
instructions="לבלוק ה (פתיחה): נוסח ניטרלי לחלוטין — 'לפנינו ערר על שומה מכרעת...' + הגדרות 'להלן' בלבד. אין לרמוז על תוצאת הדיון, אין מילות שיפוט, אין אזכור 'דין הערר להידחות/להתקבל'. רק זיהוי הצדדים, השומה המכרעת, המקרקעין והגורם המחליט."
|
||||||
|
)
|
||||||
|
```
|
||||||
|
הכלי כותב ל-DB את בלוקים ה (פתיחה), ו (רקע), ט (תכניות+היתרים מורחב), ז (טענות), ח (הליכים). מחזיר `word_count` לכל בלוק.
|
||||||
|
|
||||||
|
4. **ייצוא DOCX:**
|
||||||
|
```
|
||||||
|
mcp__legal-ai__export_interim_draft(case_number="...")
|
||||||
|
```
|
||||||
|
מייצר `data/cases/{case_number}/exports/טיוטת-ביניים-v{N}.docx`, מעדכן `active_draft_path`.
|
||||||
|
|
||||||
|
5. **דווח לחיים** (כולל מייל דרך `scripts/notify.py`):
|
||||||
|
```
|
||||||
|
## טיוטת ביניים מוכנה — ערר {case_number}
|
||||||
|
|
||||||
|
📄 **קובץ:** `data/cases/{case_number}/exports/טיוטת-ביניים-v{N}.docx`
|
||||||
|
|
||||||
|
### מה כלול
|
||||||
|
| בלוק | כותרת | מילים |
|
||||||
|
|------|-------|-------|
|
||||||
|
| ה | פתיחה (ניטרלית) | {N} |
|
||||||
|
| ו | רקע עובדתי | {N} |
|
||||||
|
| ט | תכניות + היתרים | {N} |
|
||||||
|
| ז | טענות הצדדים | {N} |
|
||||||
|
| ח | הליכים | {N} |
|
||||||
|
| **סה"כ** | | **{N}** |
|
||||||
|
|
||||||
|
### סתירות שמאיות שזוהו
|
||||||
|
{אם יש — רשימה קצרה: "תכנית X — שמאי A קבע ..., שמאי B קבע ...". אם אין — "לא זוהו סתירות בין שמאים."}
|
||||||
|
|
||||||
|
### מה הלאה
|
||||||
|
הטיוטה מוכנה לעבודה. כשתסיים לכתוב את בלוק י, חזור ב-comment ונמשיך
|
||||||
|
לשלב F (QA + ייצוא סופי).
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **סטטוס issue הראשי:** עדכן ל-`in_review` (ממתין לחיים שיכתוב את בלוק י).
|
||||||
|
|
||||||
|
**אזהרות:**
|
||||||
|
- אל תייצא DOCX סופי (`export_docx`) — זה לא תחליף לטיוטת ביניים.
|
||||||
|
- אל תפעיל את שלב B (סיכום + שאלת תוצאה) במקביל — חיים מחליט מתי לעבור לזרימה הראשית.
|
||||||
|
- אם בלוק ח חסר (אין פרוטוקול דיון/סיור) — ציין זאת בדוח. הכלי כותב מה שיש, אבל המשתמש צריך לדעת אם חסר.
|
||||||
|
|
||||||
## מפת סטטוסים
|
## מפת סטטוסים
|
||||||
|
|
||||||
**סטטוסים של התיק (`cases.status`) — כל סטטוס מתאים לפעולה אחת בדיוק:**
|
**סטטוסים של התיק (`cases.status`) — כל סטטוס מתאים לפעולה אחת בדיוק:**
|
||||||
@@ -592,22 +676,18 @@ case_prefix="${case_number:0:1}"
|
|||||||
|
|
||||||
0. **החזר את ה-issue הראשי ל-`status=in_progress`** — אם ה-issue ב-`in_review` (כי המתנת לחיים) או ב-`blocked` (כי Paperclip חסם אוטומטית), הראשון דבר: עדכן ל-`in_progress` כדי לסמן שאתה עובד עליו.
|
0. **החזר את ה-issue הראשי ל-`status=in_progress`** — אם ה-issue ב-`in_review` (כי המתנת לחיים) או ב-`blocked` (כי Paperclip חסם אוטומטית), הראשון דבר: עדכן ל-`in_progress` כדי לסמן שאתה עובד עליו.
|
||||||
|
|
||||||
1. **קרא את ה-comments האחרונים** על ה-issue שצוין ב-prompt:
|
1. **קרא את ההקשר המלא** — issue + ancestors + project + goal + comments + attachments בקריאה אחת (ראה `HEARTBEAT.md §1.7`):
|
||||||
```bash
|
```bash
|
||||||
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
CONTEXT=$(~/legal-ai/scripts/pc.sh GET "/api/issues/$ISSUE_ID/heartbeat-context")
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" | jq '[.[] | select(.authorUserId != null)] | .[-3:]'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **בדוק attachments** — אם חיים ציין קובץ שהועלה:
|
2. **בדוק attachments** — אם חיים ציין קובץ שהועלה, הוא כבר ב-`$CONTEXT.attachments`:
|
||||||
```bash
|
```bash
|
||||||
PGPASSWORD="paperclip" psql -h 127.0.0.1 -p 54329 -U paperclip -d paperclip -c "
|
echo "$CONTEXT" | jq '.attachments[] | {filename, contentPath, contentType, byteSize}'
|
||||||
SELECT a.original_filename, a.content_type, a.object_key
|
|
||||||
FROM issue_attachments ia
|
|
||||||
JOIN assets a ON a.id = ia.asset_id
|
|
||||||
WHERE ia.issue_id = '{issue-id}'
|
|
||||||
ORDER BY ia.created_at DESC LIMIT 5;"
|
|
||||||
```
|
```
|
||||||
נתיב מלא לקובץ: `/home/chaim/.paperclip/instances/default/data/storage/{object_key}`
|
נתיב מלא לקובץ: `/home/chaim/.paperclip/instances/default/data/storage/$(echo $CONTEXT | jq -r '.attachments[0].contentPath')`
|
||||||
|
|
||||||
|
⚠️ **אסור** psql ישיר ל-`issue_attachments` — ה-API הוא ה-source of truth.
|
||||||
|
|
||||||
3. **אם יש טיוטה/קובץ — קרא אותו מילה במילה.** חפש בתוכו:
|
3. **אם יש טיוטה/קובץ — קרא אותו מילה במילה.** חפש בתוכו:
|
||||||
- הוראות עריכה (טקסט כמו "צריך לערוך", "להוסיף", "חסר", "הוראות כתיבה")
|
- הוראות עריכה (טקסט כמו "צריך לערוך", "להוסיף", "חסר", "הוראות כתיבה")
|
||||||
@@ -658,34 +738,35 @@ case_prefix="${case_number:0:1}"
|
|||||||
## נתיבי API — חובה!
|
## נתיבי API — חובה!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# קרא comments על issue
|
# קרא comments על issue (אבל בד"כ עדיף heartbeat-context — ראה HEARTBEAT.md §1.7)
|
||||||
curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh GET "/api/issues/{issue-id}/comments" | jq '.[-1].body'
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" | jq '.[-1].body'
|
|
||||||
|
|
||||||
# פרסם comment
|
# פרסם comment
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/issues/{issue-id}/comments" '{"body": "..."}'
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \
|
|
||||||
-d '{"body": "..."}'
|
|
||||||
|
|
||||||
# צור issue חדש (עם הקצאה לסוכן → מפעיל wakeup אוטומטי!)
|
# צור issue חדש (עם הקצאה לסוכן → מפעיל wakeup אוטומטי!)
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/companies/42a7acd0-30c5-4cbd-ac97-7424f65df294/issues" \
|
||||||
-H "Content-Type: application/json" \
|
'{"title":"...","projectId":"25c1b4a1-2c0e-4a2d-9938-8ae56ccda6f1","assigneeAgentId":"{agent-id}","description":"...","status":"todo"}'
|
||||||
"$PAPERCLIP_API_URL/api/companies/42a7acd0-30c5-4cbd-ac97-7424f65df294/issues" \
|
|
||||||
-d '{"title":"...","projectId":"25c1b4a1-2c0e-4a2d-9938-8ae56ccda6f1","assigneeAgentId":"{agent-id}","description":"...","status":"todo"}'
|
|
||||||
|
|
||||||
# עדכן issue
|
# עדכן issue
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
# צור interaction מובנה לחיים (ראה §B/§C למעלה למבנה payload)
|
||||||
-d '{"status": "done"}'
|
~/legal-ai/scripts/pc.sh POST "/api/issues/{issue-id}/interactions" '{"kind":"...","payload":{...}}'
|
||||||
|
|
||||||
|
# קרא תשובת interaction (כשהתעוררת עם $PAPERCLIP_APPROVAL_ID)
|
||||||
|
~/legal-ai/scripts/pc.sh GET "/api/issues/{issue-id}/interactions/$PAPERCLIP_APPROVAL_ID" | jq '.'
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ agent JWT לא יכול להעיר סוכנים אחרים ישירות.** כדי להעיר סוכן → **צור issue חדש + הקצה אליו** (Paperclip מפעיל wakeup אוטומטי על assignment).
|
**⚠️ agent JWT לא יכול להעיר סוכנים אחרים ישירות.** כדי להעיר סוכן → **צור issue חדש + הקצה אליו** (Paperclip מפעיל wakeup אוטומטי על assignment).
|
||||||
|
|
||||||
חפש ב-comment של חיים:
|
## מתי להשתמש בinteraction לעומת comment
|
||||||
- מספר (1/2/3) → בחירה
|
|
||||||
- "כיוון" + מספר → אישור כיוון
|
| מצב | פתרון |
|
||||||
- טבלת טיפול בטענות → סימון claim_handling
|
|------|--------|
|
||||||
- שאלה → ענה
|
| נדרשת בחירה מובנית מחיים (תוצאה, כיוון, אישור) | **interaction** (`ask_user_questions` / `request_confirmation`) — UI עם כפתורים |
|
||||||
- הערה → שלב בתהליך
|
| הצעת עץ משימות לאישור | **interaction** (`suggest_tasks`) |
|
||||||
|
| עדכון סטטוס/תיעוד מסע (לא דורש פעולה) | **comment** רגיל |
|
||||||
|
| הסבר ארוך + שאלת בחירה | **dual** — comment עם הסבר + interaction עם options (ראה §B) |
|
||||||
|
|
||||||
|
**אסור:** "@chaim — ענה 1/2/3 בcomment". זה anti-pattern. תמיד interaction עם options.
|
||||||
|
|||||||
@@ -122,19 +122,11 @@ tools:
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
### העֵר את העוזר המשפטי (CEO) — חובה!
|
### העֵר את העוזר המשפטי (CEO) — חובה!
|
||||||
@@ -146,11 +138,7 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"מייצא טיוטה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"מייצא טיוטה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|
||||||
|
|||||||
@@ -92,19 +92,11 @@ tools:
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה:**
|
**אם הכל עבר בהצלחה:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם נכשלו תיקונים קריטיים או יש markers `[?]` רבים:**
|
**אם נכשלו תיקונים קריטיים או יש markers `[?]` רבים:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
### העֵר את העוזר המשפטי (CEO) — חובה!
|
### העֵר את העוזר המשפטי (CEO) — חובה!
|
||||||
@@ -117,10 +109,6 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"מגיה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"מגיה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|||||||
@@ -79,6 +79,29 @@ tools:
|
|||||||
- סעיפים 1, 2, 3... ללא איפוס בין בלוקים
|
- סעיפים 1, 2, 3... ללא איפוס בין בלוקים
|
||||||
- ללא כפילויות במספור
|
- ללא כפילויות במספור
|
||||||
|
|
||||||
|
### 7א. שלמות חיפוש בקורפוסים (corpus_queries_logged) — critical
|
||||||
|
|
||||||
|
ה-analyst וה-researcher חייבים לתעד queries לקורפוסים שלהם. בלי תיעוד — אין דרך לוודא שתקדימי עליון רלוונטיים לא הוחמצו.
|
||||||
|
|
||||||
|
בדוק:
|
||||||
|
1. **קיום סעיף "שאילתות לקורפוסים"**:
|
||||||
|
- ב-`{case_dir}/documents/research/analysis-and-research.md` — סעיף **7א** (לפי שלב 5ד של ה-analyst)
|
||||||
|
- ב-`{case_dir}/documents/research/precedent-research.md` — סעיף **ז** (לפי שלב 2ב.4 של ה-researcher)
|
||||||
|
- אם חסר באחד מהם — `corpus_queries_logged = fail` (critical, חוסם המשך).
|
||||||
|
|
||||||
|
2. **מספר queries מינימלי לקורפוס הסמכותי (`search_precedent_library`):**
|
||||||
|
- `analyst >= (מספר טענות סף + מספר סוגיות מרכזיות)`
|
||||||
|
- `researcher >= מספר סוגיות מרכזיות`
|
||||||
|
- חישוב: ספור את הסוגיות בסעיף 6 של `analysis-and-research.md`. מתחת לסף → `fail`.
|
||||||
|
|
||||||
|
3. **negative evidence מתועד:** גם 0-result query חייבת להופיע. אם מצאת queries שכולן 0-result — לא fail; פשוט תיעוד שהקורפוס דליל בנושא.
|
||||||
|
|
||||||
|
4. **אצליבה הצלבה (cross-check):**
|
||||||
|
- הרץ `mcp__legal-ai__precedent_library_list(practice_area=X, search="<keyword מרכזי מהתיק>")` עם practice_area של התיק.
|
||||||
|
- אם החזיר תוצאות שלא מופיעות בסעיף "נבחרו" או "נדחו" של ה-analyst/researcher → `corpus_queries_logged = warning` (לא חוסם, אבל דווח לחיים).
|
||||||
|
|
||||||
|
חומרה: **critical** — בלי queries מתועדות אין דרך לאמת שלא הוחמצה הלכה מחייבת.
|
||||||
|
|
||||||
### 7. עמידה במתודולוגיה (methodology_compliance)
|
### 7. עמידה במתודולוגיה (methodology_compliance)
|
||||||
ראה `docs/decision-methodology.md` לעקרונות המלאים. בדוק:
|
ראה `docs/decision-methodology.md` לעקרונות המלאים. בדוק:
|
||||||
- לכל סוגיה בבלוק י — ניתן לזהות מבנה סילוגיסטי: כלל + עובדות + מסקנה?
|
- לכל סוגיה בבלוק י — ניתן לזהות מבנה סילוגיסטי: כלל + עובדות + מסקנה?
|
||||||
@@ -137,6 +160,7 @@ tools:
|
|||||||
| משקלות | warning | מדווח, לא חוסם |
|
| משקלות | warning | מדווח, לא חוסם |
|
||||||
| כפילות | warning | מדווח, לא חוסם |
|
| כפילות | warning | מדווח, לא חוסם |
|
||||||
| מספור | warning | מדווח, לא חוסם |
|
| מספור | warning | מדווח, לא חוסם |
|
||||||
|
| **שאילתות לקורפוסים** | **critical** | **חוסם ייצוא** |
|
||||||
| מתודולוגיה | critical | חוסם ייצוא |
|
| מתודולוגיה | critical | חוסם ייצוא |
|
||||||
| **קול דפנה** | **critical** | **חוסם ייצוא** |
|
| **קול דפנה** | **critical** | **חוסם ייצוא** |
|
||||||
|
|
||||||
@@ -173,19 +197,11 @@ tools:
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
### העֵר את העוזר המשפטי (CEO) — חובה!
|
### העֵר את העוזר המשפטי (CEO) — חובה!
|
||||||
@@ -197,10 +213,6 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"בודק איכות סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"בודק איכות סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|||||||
@@ -85,21 +85,76 @@ tools:
|
|||||||
- **האם זה תקדם מהקאנון של דפנה?** (בדוק `docs/daphna-precedent-network.md` — אם כן, ציין שזה התקדם המועדף שלה לסוגיה)
|
- **האם זה תקדם מהקאנון של דפנה?** (בדוק `docs/daphna-precedent-network.md` — אם כן, ציין שזה התקדם המועדף שלה לסוגיה)
|
||||||
4. הפק הפניות (`extract_references`)
|
4. הפק הפניות (`extract_references`)
|
||||||
|
|
||||||
### שלב 2ב: בדיקה מצטלבת מול הקאנון של דפנה
|
### שלב 2ב: חיפוש מובנה בשלושת הקורפוסים — חובה, עם תיעוד queries
|
||||||
אחרי שאספת את הפסיקה הרלוונטית בתיק:
|
|
||||||
1. **לכל סוגיה משפטית** בתיק — בדוק ב-`daphna-precedent-network.md`:
|
**חובה לבצע** — לא הצעה. הניתוח קודם הראה (ערר 1200-25) שאם הקורפוס לא נסרק במפורש, מפספסים תקדימי עליון רלוונטיים שיושבים בו. ה-QA יחזיר `needs_revision` אם סעיף ה-queries חסר.
|
||||||
- האם יש תקדם מועדף של דפנה לסוגיה?
|
|
||||||
- האם הוא הוצג בכתבי הטענות? אם לא — סמן כתקדם שיש להוסיף
|
**שלושת הקורפוסים — אל תבלבל:**
|
||||||
2. **תקדמים אישיים**: `search_decisions` בקטגוריה זהה לתיק. אם דפנה כבר הכריעה בסוגיה דומה:
|
- `search_precedent_library` = פסיקה חיצונית סמכותית עם הלכות מאושרות (עליון/מנהלי/ועדות ערר אחרות) + supporting_quote מוכן.
|
||||||
|
- `search_decisions` = החלטות דפנה (style_corpus) — הקאנון האישי שלה.
|
||||||
|
- `precedent_search_library` = ציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
|
||||||
|
|
||||||
|
#### 2ב.1 — קורפוס סמכותי (`search_precedent_library`) — חובה
|
||||||
|
|
||||||
|
לכל **סוגיה משפטית מרכזית** בתיק — הרץ לפחות שאילתה אחת עם פילטרים:
|
||||||
|
|
||||||
|
| סיווג תיק | practice_area |
|
||||||
|
|------------|---------------|
|
||||||
|
| 1xxx (רישוי ובניה) | `rishuy_uvniya` |
|
||||||
|
| 8xxx (היטל השבחה) | `histael_hashbacha` |
|
||||||
|
| 9xxx (פיצויים ס' 197) | `pitsuim_197` |
|
||||||
|
|
||||||
|
אם הסוגיה ב-`appeal_subtype` ידוע (כמו "שימוש חורג", "סטייה ניכרת") — הוסף `appeal_subtype` לפילטר.
|
||||||
|
|
||||||
|
```
|
||||||
|
search_precedent_library(
|
||||||
|
query="...",
|
||||||
|
practice_area="rishuy_uvniya",
|
||||||
|
appeal_subtype="שימוש חורג",
|
||||||
|
limit=10
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2ב.2 — קאנון דפנה (`search_decisions`)
|
||||||
|
|
||||||
|
לכל סוגיה — בדוק אם דפנה כבר הכריעה:
|
||||||
- אם תוצאה דומה: תקדם לחיסכון דוקטרינרי ("כפי שקבענו ב-X")
|
- אם תוצאה דומה: תקדם לחיסכון דוקטרינרי ("כפי שקבענו ב-X")
|
||||||
- אם תוצאה הפוכה: ציין כי **חובה** הבחנה (distinguishing)
|
- אם תוצאה הפוכה: ציין כי **חובה** הבחנה (distinguishing)
|
||||||
3. **קורפוס פסיקה סמכותית**: `search_precedent_library` — חיפוש סמנטי בהלכות שאושרו ע"י דפנה (פסיקת עליון/מנהלי/ועדות ערר אחרות). מחזיר rule_statement + supporting_quote + citation מוכנים לציטוט בבלוק י. אם הצדדים הפנו לפסק דין שלא בקורפוס — הוסף אותו דרך `precedent_attach` (לתיק) או דרך ממשק ההעלאה ב-`/precedents` (לקורפוס הקבוע).
|
|
||||||
4. **דווח** איזה תקדמים מהקאנון רלוונטיים, איזה תקדמים אישיים נמצאו, ואילו הלכות מהקורפוס הסמכותי תומכות.
|
|
||||||
|
|
||||||
**שלושת המקורות — אל תבלבל:**
|
#### 2ב.3 — בדיקה מצטלבת מול `daphna-precedent-network.md`
|
||||||
- `search_decisions` = החלטות דפנה (style_corpus).
|
|
||||||
- `search_precedent_library` = פסיקה חיצונית סמכותית עם הלכות מאושרות.
|
לכל סוגיה — בדוק במסמך:
|
||||||
- `precedent_search_library` = ציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
|
- האם יש תקדם מועדף של דפנה?
|
||||||
|
- האם הוצג בכתבי הטענות? אם לא — סמן כתקדם שיש להוסיף.
|
||||||
|
|
||||||
|
#### 2ב.4 — תיעוד מחייב — סעיף "שאילתות לקורפוסים" ב-`precedent-research.md`
|
||||||
|
|
||||||
|
חובה להופיע סעיף בשם **"ז. שאילתות לקורפוסים — log מלא"** עם:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## ז. שאילתות לקורפוסים — log מלא
|
||||||
|
|
||||||
|
### קורפוס סמכותי (search_precedent_library)
|
||||||
|
|
||||||
|
#### Q1 — סוגיה: [שם]
|
||||||
|
- **שאילתה:** "..."
|
||||||
|
- **פילטרים:** practice_area=..., appeal_subtype=...
|
||||||
|
- **תוצאות:** N
|
||||||
|
- **נבחרו:** [case_number] — headnote/למה רלוונטי
|
||||||
|
- **נדחו:** [case_number] — למה לא
|
||||||
|
- **0 results?** ציין מפורש + נמק
|
||||||
|
|
||||||
|
#### Q2 — ...
|
||||||
|
|
||||||
|
### קאנון דפנה (search_decisions)
|
||||||
|
#### Q1 — ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**negative evidence חובה:** גם 0 results נרשם. זה ההבדל בין "נסרק וריק" ל"לא נסרק".
|
||||||
|
|
||||||
|
**מינימום:** queries לקורפוס הסמכותי = מספר סוגיות מרכזיות שזוהו.
|
||||||
|
|
||||||
|
5. **דווח** איזה תקדמים מהקאנון רלוונטיים, איזה תקדמים אישיים נמצאו, ואילו הלכות מהקורפוס הסמכותי תומכות.
|
||||||
|
|
||||||
### שלב 3: מיפוי תכנית
|
### שלב 3: מיפוי תכנית
|
||||||
1. קרא הוראות התכנית **במלואן** — לא רק את הסעיף הנטען
|
1. קרא הוראות התכנית **במלואן** — לא רק את הסעיף הנטען
|
||||||
@@ -158,19 +213,11 @@ python3 /home/chaim/legal-ai/scripts/notify.py \
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
### העֵר את העוזר המשפטי (CEO) — חובה!
|
### העֵר את העוזר המשפטי (CEO) — חובה!
|
||||||
@@ -182,11 +229,7 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"חוקר תקדימים סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"חוקר תקדימים סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|
||||||
|
|||||||
@@ -210,19 +210,11 @@ case_update(case_number, status="drafted")
|
|||||||
|
|
||||||
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
**אם הכל עבר בהצלחה (כל בדיקות השלב הקודם עברו, אין כשל בפלט):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "done"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "done"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
**אם בדיקות נכשלו, חסר פלט, או חסר מידע קריטי:**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh PATCH "/api/issues/{issue-id}" '{"status": "blocked"}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/issues/{issue-id}" \
|
|
||||||
-d '{"status": "blocked"}'
|
|
||||||
```
|
|
||||||
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
**אסור** לסיים `done` עם פלט חסר — אם משהו נכשל, סטטוס = `blocked` + comment עם פירוט.
|
||||||
|
|
||||||
### העֵר את העוזר המשפטי (CEO) — חובה!
|
### העֵר את העוזר המשפטי (CEO) — חובה!
|
||||||
@@ -234,11 +226,7 @@ else
|
|||||||
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
CEO_ID="752cebdd-6748-4a04-aacd-c7ab0294ef33" # CMP — רישוי ובניה
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" '{"source":"automation","triggerDetail":"system","reason":"כותב החלטה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'```
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"$PAPERCLIP_API_URL/api/agents/$CEO_ID/wakeup" \
|
|
||||||
-d '{"source":"automation","triggerDetail":"system","reason":"כותב החלטה סיים משימה [issue-id] בסטטוס [done/blocked]","payload":{"issueId":"[issue-id]","mutation":"agent_completion"}}'
|
|
||||||
```
|
|
||||||
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
**⚠️ אסור להשתמש ב-INSERT INTO agent_wakeup_requests ישירות!** הכנסה ישירה ל-DB יוצרת רק את הבקשה בלי heartbeat_run — והסוכן לא יתעורר לעולם. **תמיד להשתמש ב-API בלבד.**
|
||||||
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
**⚠️ אסור לקבע UUID של CEO** — UUID שונה לכל חברה. תמיד דרך `$PAPERCLIP_COMPANY_ID`. wakeup לחברה אחרת נדחה: `Agent key cannot access another company`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"migrationNoticeShown": true
|
"migrationNoticeShown": true,
|
||||||
|
"currentTag": "legal-ai",
|
||||||
|
"lastSwitched": "2026-05-03T20:31:48.957Z",
|
||||||
|
"branchTagMapping": {}
|
||||||
}
|
}
|
||||||
@@ -1155,13 +1155,228 @@
|
|||||||
],
|
],
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"subtasks": []
|
"subtasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "14",
|
||||||
|
"title": "Upgrade: speed up halacha+metadata extraction",
|
||||||
|
"description": "Halacha extraction on long rulings is slow (5-15 min for typical court ruling, 30-50 min for a 207-chunk appeals committee decision). Root cause: each chunk spawns a separate `claude -p` subprocess (5-10 sec startup overhead each), Hebrew prompts on cold cache run 30-90 sec, and there's no prompt-cache sharing between chunks. Acceleration options to evaluate later when speed becomes a real blocker.\n\nOptions (each can be combined):\n\n1. Concurrency 3 -> 6 in halacha_extractor.CHUNK_CONCURRENCY. ~2x faster wall-clock. Cost: 6x ~300MB RSS = 1.8GB peak — verify on Nautilus headroom.\n\n2. Larger chunks 12K -> 18-25K chars (CHUNK_TARGET_CHARS in claims_extractor.py / halacha_extractor.py). Fewer waves. Risk: timeout on cold cache (currently 1800s ceiling), and may degrade extraction precision for very long sections.\n\n3. Anthropic SDK direct with 5-min ephemeral prompt caching on the static instruction prefix (already wired the parameter as system= in claude_session.query). Estimated 5-10x faster because cache reads are ~10% of cold cost. Costs ~$0.30-2 per long ruling on Sonnet 4.6. Chair previously rejected this path for ALL traffic ('we work only with claude session'). Compromise: SDK only for the precedent-library corpus build (static, one-time), claude session for live decision drafting (interactive, frequent).\n\n4. Two-tier prompt: a short 'classification' pass with claude -p deciding which chunks contain halachot, then deep extraction only on positive chunks. Could cut total LLM time by 40-60% on rulings with lots of factual chapters.\n\n5. Already implemented (Apr 3, 2026): skip non-extractable sections — only run on chunks where section_type IN (legal_analysis, ruling, conclusion); fallback to all chunks when chunker labels nothing. So that win is already banked.\n\nRe-evaluate when: a chair drops a 200K+ char ruling into the queue and the wait becomes painful, OR when the precedent-library has 50+ pending entries and bulk processing matters.",
|
||||||
|
"details": "",
|
||||||
|
"testStrategy": "",
|
||||||
|
"status": "deferred",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "low",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-03T16:03:07.222Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "15",
|
||||||
|
"title": "Backfill multimodal — החלטה על rollout מורחב לאחר A/B עם דפנה",
|
||||||
|
"description": "תזכורת לבדוק עם דפנה אם voyage-multimodal-3 על 8174-24 + 8137-24 עוזר בפועל, ולהחליט אם להריץ backfill על שאר הקורפוס (~236 docs, ~17,700 pages, ~2 שעות זמן API, ~350MB disk).",
|
||||||
|
"details": "תאריך יעד מומלץ: ~2026-05-10 (שבוע מהיום, 2026-05-03).\n\nקריטריונים להחלטה (אם מתקיים אחד — להריץ rollout):\n • דפנה זיהתה לפחות פעמיים ערך מוסף ב-8174-24 או 8137-24 (תקדים שלא הייתה מוצאת בלי image side, או חתימה/טבלה/תרשים שצף ב-top results)\n • היא ביקשה במפורש להפעיל על תיק נוסף ספציפי\n • היא מבקשת לעבור ל-search מצטלב (search_decisions, find_similar_cases) מעבר לתיק הנוכחי\n\nאם דפנה לא ראתה ערך — להחליט: לבטל / לכוונן MULTIMODAL_TEXT_WEIGHT (0.5 → 0.55-0.65) / לחכות עוד שבוע.\n\nאם החליטו להריץ — סדר עדיפויות:\n 1. שמאי-heavy: 8xxx (היטל השבחה) ו-9xxx (פיצויים) — שם הערך הגדול ביותר\n 2. תיקי 1xxx (רישוי ובניה) אחרון\n\nהרצה:\n CONTAINER=$(sudo docker ps --format '{{.Names}}' | grep gyjo | head -1)\n sudo docker cp scripts/multimodal_backfill.py $CONTAINER:/tmp/\n sudo docker cp scripts/backfill_chunk_pages.py $CONTAINER:/tmp/\n sudo docker exec $CONTAINER python /tmp/multimodal_backfill.py 8xxx-yy 9xxx-yy ...\n sudo docker exec $CONTAINER python /tmp/backfill_chunk_pages.py 8xxx-yy 9xxx-yy ...\n\nרפרנסים:\n • docs/voyage-upgrades-plan.md סעיף 'שלב C — voyage-multimodal-3 (✅ בוצע)'\n • commits 242f668..d12cdb1 על main\n • זיכרון: project_multimodal_stage_c.md, feedback_hybrid_retrieval_rrf.md",
|
||||||
|
"testStrategy": "",
|
||||||
|
"status": "pending",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "low",
|
||||||
|
"subtasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "16",
|
||||||
|
"title": "[Paperclip Gap 1] runtime_config ריק — חסרים graceSec/cooldownSec/maxConcurrentRuns",
|
||||||
|
"description": "runtime_config = '{}' לכל 14 הסוכנים. מסתבר שעיקר ההגדרות החשובות (timeoutSec=3600, maxTurnsPerRun=500) יושבות ב-adapter_config ולא ב-runtime_config — אז המצב פחות חמור. אבל graceSec/cooldownSec/maxConcurrentRuns עדיין חסרים.",
|
||||||
|
"details": "תיקון לניתוח המקורי שגוי בעקבות בדיקה ב-DB:\n\nמה שכן יש לנו (ב-adapter_config, לא runtime_config):\n- timeoutSec: 3600 (לכל הסוכנים)\n- maxTurnsPerRun: 500 (לכל הסוכנים)\n- model + effort=high (לכל הסוכנים)\n- paperclipSkillSync.desiredSkills (5/7 סוכנים — חסר אצל הגהת מסמכים ומנתח משפטי)\n\nמה שבאמת חסר ב-runtime_config:\n- heartbeat.graceSec — זמן grace לפני SIGKILL אחרי timeout. מהקוד: Math.max(1, graceSec)*1000. אם לא מוגדר → 1ms grace. בעיה אם הסוכן נחתך באמצע commit ל-DB.\n- heartbeat.cooldownSec — default ביצירה חדשה: 10. אצלנו לא מוגדר.\n- heartbeat.maxConcurrentRuns — default מ-AGENT_DEFAULT_MAX_CONCURRENT_RUNS (כנראה 1).\n- heartbeat.wakeOnDemand — default=true בקוד. אצלנו לא מוגדר אבל בפועל true.\n- heartbeat.enabled — default=false (timer off). זה הרצוי אצלנו.\n\nפעולה (Phase 1):\n1. עדכון runtime_config של כל סוכן: { heartbeat: { graceSec: 60, cooldownSec: 10, maxConcurrentRuns: 1, wakeOnDemand: true } }\n2. בעיקר graceSec — בלעדיו commit באמצע יכול להיכשל\n3. cooldownSec=10 (זהה לdefault ב-UI ליצירת agent חדש)\n\nהשפעה: minimal — רוב המקרים עובדים עם defaults. graceSec הוא העיקר.",
|
||||||
|
"testStrategy": "1. SELECT name, runtime_config->'heartbeat' FROM agents → לראות שכל סוכן מקבל graceSec/cooldownSec/maxConcurrentRuns/wakeOnDemand.\n2. בדיקה: סוכן ארוך נחתך ב-timeout — לבדוק שהיתה הזדמנות לציין graceful shutdown ב-30-60 שניות",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T07:47:02.008Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "17",
|
||||||
|
"title": "[Paperclip Gap 2] תקציבים = 0 לכל הסוכנים — אין budget enforcement",
|
||||||
|
"description": "budget_monthly_cents = 0 ו-spent_monthly_cents = 0 לכל 14 הסוכנים. Paperclip מציע cost control מובנה — אנחנו מתעלמים.",
|
||||||
|
"details": "ממצא: SELECT name, budget_monthly_cents, spent_monthly_cents FROM agents → הכל אפס.\n\nסיכון: לולאה חבויה יכולה לשרוף מאות $. אין auto-pause ב-80% spend (דפוס ש-CEO HEARTBEAT הרשמי מצפה לו).\n\nפעולה (Phase 3):\n1. מדידה: כמה כל סוכן באמת מוציא בחודש כיום (דרך לוגי claude-code, או Anthropic dashboard).\n2. הגדרת budget_monthly_cents סביר לכל סוכן (כותב Opus ≫ מנתח Sonnet).\n3. בדיקה שהמנגנון מפסיק כשמגיעים ל-100%.\n\nשאלה לחיים לפני ביצוע: באיזו רזולוציה למדוד? לפי Anthropic invoice, או לפי טוקנים בלוגים של claude_session?",
|
||||||
|
"testStrategy": "בדיקה ידנית: להגדיר budget קטן לסוכן ניסוי (1 cent), לעורר אותו על משימה, לוודא שמתעורר ונחסם. לעקוב ב-spent_monthly_cents.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "high",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T10:18:08.046Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "18",
|
||||||
|
"title": "[Paperclip Gap 3] חסר X-Paperclip-Run-Id header בקריאות API",
|
||||||
|
"description": "ה-skill הרשמי קובע: 'You MUST include -H X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID on ALL API requests that modify issues'. ב-HEARTBEAT.md שלנו אין זכר לכך.",
|
||||||
|
"details": "ממצא: grep -n 'X-Paperclip-Run-Id' .claude/agents/ → 0 hits. כל curl ב-checkout/comments/PATCH issues — בלי הheader.\n\nסיכון: audit trail שבור. שאלה 'איזו ריצה שינתה את ה-issue X?' אין לה תשובה ב-DB.\n\nפעולה (Phase 1):\n1. עדכון .claude/agents/HEARTBEAT.md — דוגמאות ה-curl יכללו את הheader\n2. עדכון 6 קבצי הסוכנים (legal-ceo.md, legal-analyst.md, legal-researcher.md, legal-writer.md, legal-qa.md, legal-exporter.md) — כל מקום שיש curl POST/PATCH\n3. בדיקה שיש env var $PAPERCLIP_RUN_ID זמין בכל heartbeat",
|
||||||
|
"testStrategy": "בלוגי Paperclip (heartbeat_runs טבלה) לראות שהפעולות שלנו מקושרות ל-run_id. SELECT * FROM activity_log WHERE run_id IS NOT NULL ORDER BY created_at DESC LIMIT 10.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "high",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T08:49:44.646Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19",
|
||||||
|
"title": "[Paperclip Gap 4] לא משתמשים ב-/api/issues/{id}/interactions לאישורים",
|
||||||
|
"description": "Paperclip מציע API מובנה לאישור/שאלות (request_confirmation, ask_user_questions, suggest_tasks) עם idempotency keys ו-auto-wake. אנחנו עדיין כותבים 'חיים, מה לעשות?' כ-comment חופשי.",
|
||||||
|
"details": "סוגי interaction:\n- ask_user_questions — שאלות מובנות\n- request_confirmation — yes/no עם idempotency key (confirmation:{issueId}:plan:{revisionId})\n- suggest_tasks — הצעת עץ משימות\n- continuationPolicy: wake_assignee — wake אוטומטי על מענה\n- supersedeOnUserComment: true — בטל אם חיים עונה\n\nסיכון: אין UI מובנה לחיים (כפתורים), רק טקסט. אם הסוכן מתעורר פעמיים — שתי שאלות זהות.\n\nפעולה (Phase 2):\n1. בlegal-ceo.md — להחליף 'אם חיים לא הגדיר outcome: שאל בcomment' ב-request_confirmation\n2. בbrainstorm_directions — suggest_tasks במקום רשימת bullet\n3. בlegal-qa.md — request_confirmation לאישור export\n\nשאלה לחיים: האם תרצה לראות UI חדש או להישאר ב-Markdown comments?",
|
||||||
|
"testStrategy": "יצירת request_confirmation מסוכן ניסוי, בדיקה ב-UI שמופיעים כפתורי אישור/דחייה, בדיקה שהסוכן מתעורר אוטומטית עם PAPERCLIP_APPROVAL_ID env.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"16",
|
||||||
|
"17",
|
||||||
|
"18"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T11:18:59.050Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "20",
|
||||||
|
"title": "[Paperclip Gap 5] לא משתמשים ב-PAPERCLIP_WAKE_PAYLOAD_JSON fast-path",
|
||||||
|
"description": "בwake שמכוון ל-issue ספציפי, ה-env var מכיל כבר issue summary + comments חדשים דחוסים. ה-skill הרשמי אומר 'skip Steps 1-4 entirely'. שלנו תמיד fetcher גם ה-API.",
|
||||||
|
"details": "ממצא: HEARTBEAT.md סעיפים 2-2c תמיד פונים ל-API גם אם ה-payload כבר מכיל את הכל.\n\nתועלת: חיסכון 3-4 קריאות API לכל ריצה. בwakeups תכופים (CEO על comments) — חיסכון ניכר.\n\nפעולה (Phase 2):\n1. הוספה ל-HEARTBEAT.md בראש הסעיפים: 'אם $PAPERCLIP_WAKE_PAYLOAD_JSON קיים — קרא אותו ראשון. רק אם fallbackFetchNeeded:true או חסר הקשר רחב — fetch'.\n2. דוגמה לפענוח JSON: jq עם key paths\n3. בדיקה איזה wake reasons בכלל מקבלים payload (כנראה comment-driven בלבד)",
|
||||||
|
"testStrategy": "בWakeup דרך API עם payload, לבדוק בלוגי הסוכן שאין fetch לcomments. timeit על מספר ריצות לפני/אחרי.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"18"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T09:15:46.339Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "21",
|
||||||
|
"title": "[Paperclip Gap 6] שאילתות psql ישירות ל-issue_attachments — שובר אבסטרקציה",
|
||||||
|
"description": "HEARTBEAT.md סעיף 2c משתמש ב-psql ישיר ל-issue_attachments + assets. אם schema ישתנה (כפי שצפוי בעדכוני Paperclip) — כל הסוכנים נשברים.",
|
||||||
|
"details": "ממצא: 6 קבצי סוכן + HEARTBEAT.md מכילים PGPASSWORD=paperclip psql ... FROM issue_attachments ia JOIN assets a.\n\nסיכון: breakage בעדכון Paperclip. כפילות לוגיקה (copy-paste בכל סוכן).\n\nפעולה (Phase 2):\n1. בדיקה אם קיים endpoint רשמי /api/issues/{id}/attachments (curl + grep ב-server/src/routes)\n2. אם כן — להחליף את כל ה-psql\n3. אם לא — להעביר את ה-psql למקום יחיד: helper ב-mcp-server (mcp__legal-ai__list_issue_attachments tool)\n4. אופציה ג: לפתוח issue ב-paperclipai/paperclip לבקש endpoint\n\nתלוי במחקר API.",
|
||||||
|
"testStrategy": "אחרי החלפה: grep -rn 'issue_attachments' .claude/agents/ → 0 hits. בדיקה שסוכן עדיין רואה attachments בריצה.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T09:28:18.058Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "22",
|
||||||
|
"title": "[Paperclip Gap 7] לא משתמשים ב-/api/issues/{id}/heartbeat-context",
|
||||||
|
"description": "Endpoint רשמי שמחזיר issue + ancestors + goal/project + comment cursor בקריאה אחת. אנחנו עושים 3 קריאות נפרדות.",
|
||||||
|
"details": "ה-skill הרשמי: 'Prefer GET /api/issues/{issueId}/heartbeat-context first. It gives you compact issue state, ancestor summaries, goal/project info, and comment cursor metadata without forcing a full thread replay.'\n\nשלנו: HEARTBEAT.md סעיפים 2 + 2b → שלוש קריאות (inbox-lite, issue, comments).\n\nפעולה (Phase 2):\n1. הוספת endpoint כצעד 6 ב-HEARTBEAT.md לפני 'Do the work'\n2. הסרת קריאות מיותרות שכבר ב-context\n3. שמירת comment cursor (after={last-seen-id}) לקריאות עוקבות",
|
||||||
|
"testStrategy": "בדיקה שהendpoint מחזיר את כל המידע הדרוש. ספירת קריאות API לפני/אחרי בריצה אמיתית.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T09:28:14.247Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "23",
|
||||||
|
"title": "[Paperclip Gap 8+11] HEARTBEAT.md ארוך + אין שימוש ב-skills של Paperclip",
|
||||||
|
"description": "HEARTBEAT.md שלנו 220 שורות (vs upstream 85). Paperclip מציע 8 skills מוכנים (paperclip, paperclip-create-agent, וכו') שאנחנו לא משתמשים באף אחד.",
|
||||||
|
"details": "תיקון לניתוח: מסתבר ש-CEO + 4 סוכנים אחרים כן משתמשים ב-paperclipSkillSync עם 4 paperclip skills (paperclip, paperclip-create-agent, paperclip-create-plugin, para-memory-files). חסר אצל: הגהת מסמכים ומנתח משפטי (skills_count=0).\n\nממצא: ls skills/ ב-paperclip repo → 8 skills. שלנו: 0 skills של Paperclip בשימוש.\n\nרלוונטיים לנו:\n- paperclip — API patterns + heartbeat checklist (יכול להחליף חלק מ-HEARTBEAT.md)\n- paperclip-create-agent — אם נוסיף סוכן\n- paperclip-create-plugin — לעדכוני plugin-legal-ai\n- paperclip-converting-plans-to-tasks — יכול להחליף brainstorm_directions\n- diagnose-why-work-stopped — לתחזוקה\n\nפעולה (Phase 3):\n1. קריאת skills/paperclip/SKILL.md מלא\n2. הזרקת skill לסביבת הסוכנים (כנראה דרך CLI: paperclipai agent local-cli)\n3. שכתוב HEARTBEAT.md לפי הדפוס: project-specific only, delegation לskill הרשמי לכלל ה-API\n4. יעד: ~120 שורות ב-HEARTBEAT.md שלנו\n\nשאלה לחיים: האם להזריק skills כסימלינקים ל-symlinks קיימים, או דרך paperclipai CLI?",
|
||||||
|
"testStrategy": "אחרי שכתוב: סוכן ניסוי קורא את HEARTBEAT.md + paperclip skill, מבצע heartbeat מלא בלי שגיאות. השוואת אורך לפני/אחרי.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"16",
|
||||||
|
"17",
|
||||||
|
"18",
|
||||||
|
"19",
|
||||||
|
"20",
|
||||||
|
"21",
|
||||||
|
"22"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T16:44:27.553Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "24",
|
||||||
|
"title": "[Paperclip Gap 9] לבדוק bootstrapPromptTemplate deprecated באף סוכן",
|
||||||
|
"description": "מ-docs/agents-runtime.md: 'bootstrapPromptTemplate is deprecated... should be migrated to the managed instructions bundle system.' לבדוק האם adapter_config שלנו משתמש בזה.",
|
||||||
|
"details": "פעולה (Phase 1):\n1. SELECT name, adapter_config->'promptTemplate' as pt, adapter_config->'bootstrapPromptTemplate' as bpt FROM agents WHERE adapter_type = 'claude_local';\n2. אם בשימוש אצל סוכן כלשהו — מיגרציה למבנה החדש\n3. ייעוד: לבדוק תיעוד managed instructions bundle ב-paperclip docs\n\nהערה: זה כנראה לא ישפיע אצלנו (אנחנו משתמשים ב-symlinks ל-AGENTS.md/HEARTBEAT.md ישירות) — אבל חובה לוודא.",
|
||||||
|
"testStrategy": "SELECT הנ\"ל. אם 0 שורות מחזירות bpt לא-NULL — סגור את המשימה.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "high",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T08:19:27.766Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "25",
|
||||||
|
"title": "[Paperclip Gap 10] סוכנים מוכפלים בין 2 חברות — אין סנכרון",
|
||||||
|
"description": "14 שורות = 7 סוכנים × 2 חברות (1xxx, 8xxx). כל שינוי בהגדרות הסוכן צריך להיעשות פעמיים. אין מנגנון סנכרון או הורשה.",
|
||||||
|
"details": "ממצא: SELECT name, COUNT(*) FROM agents GROUP BY name → 2 לכל אחד.\n\nסיכון: drift בין החברות. שינוי runtime_config ל-CEO של 1xxx יכול לפספס את CEO של 8xxx.\n\nפעולה (Phase 3):\n1. בדיקה: האם Paperclip תומך ב-shared agents או chainOfCommand? (לקרוא docs/companies/)\n2. אם כן — מיגרציה למבנה משותף\n3. אם לא — סקריפט סנכרון: scripts/sync_agents_across_companies.py שמעתיק כל שינוי מחברה לחברה\n\nשאלה לחיים: בעתיד אם יהיו עוד סוגי ערר (10xxx?) — להוסיף עוד חברה או להשאיר 2?",
|
||||||
|
"testStrategy": "אם סקריפט: dry-run שמראה הבדלים בין 2 ה-CEOs. ואז apply ולוודא runtime_config זהה.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [
|
||||||
|
"16"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T09:52:14.263Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "26",
|
||||||
|
"title": "[Paperclip Gap 12] עדכון @paperclipai/plugin-sdk + capabilities חדשות",
|
||||||
|
"description": "ה-plugin שלנו: @paperclipai/plugin-sdk@^2026.325.0, apiVersion: 1, minimumHostVersion: 2026.325.0. ה-host: 2026.428.0. ייתכן capabilities חדשות (issue.interactions.create, וכו').",
|
||||||
|
"details": "פעולה (Phase 4 — אחרי שדרוג Paperclip stable):\n1. cd /home/chaim/plugin-legal-ai && npm view @paperclipai/plugin-sdk version\n2. אם חדשה: npm install @paperclipai/plugin-sdk@latest\n3. קריאת adapter-plugin.md המעודכן ב-paperclip repo\n4. בדיקה אם apiVersion: 2 קיים\n5. הוספת capabilities חדשות אם רלוונטי (בעיקר issue.interactions.create אחרי gap #4)\n6. npm run build && reinstall plugin\n\nתלוי בgap #19 (interactions API) — אם אנחנו רוצים שהplugin יוכל ליצור interactions, חייב capability חדש.",
|
||||||
|
"testStrategy": "אחרי npm install: בדיקה ש-plugin עולה ב-Paperclip בלי last_error. SELECT status, last_error FROM plugins WHERE plugin_key='marcusgroup.legal-ai'.",
|
||||||
|
"status": "pending",
|
||||||
|
"dependencies": [
|
||||||
|
"27",
|
||||||
|
"19"
|
||||||
|
],
|
||||||
|
"priority": "low",
|
||||||
|
"subtasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "27",
|
||||||
|
"title": "[Paperclip Phase 4] שדרוג Paperclip לגרסה stable הבאה (לא 2026.428.0)",
|
||||||
|
"description": "כרגע אנחנו על 2026.428.0 — הגרסה היציבה האחרונה. כשיופיע stable חדש (כנראה 2026.5xx.x), לבצע שדרוג מבוקר.",
|
||||||
|
"details": "טריגר: npm view paperclipai dist-tags.latest מחזיר משהו ≠ 2026.428.0.\n\nפעולה:\n1. קריאת releases/v2026.5xx.x.md ב-GitHub\n2. בדיקת שינויים שעלולים להשפיע (CUSTOMIZATIONS.md סעיפים: hebrew, RTL, plugin driver, heartbeat)\n3. גיבוי: pg_dump של paperclip DB + cp -r ~/.npm/_npx/43414d9b790239bb /tmp/\n4. pm2 stop paperclip\n5. rm -rf ~/.npm/_npx/43414d9b790239bb\n6. npx paperclipai@latest run (יוריד גרסה חדשה)\n7. הרצה מחדש: ~/.paperclip/hebrew/apply-hebrew.sh && ~/.paperclip/issue-link-fix/apply-issue-link-fix.sh\n8. pm2 restart paperclip\n9. בדיקה ב-pc.nautilus.marcusgroup.org: עברית + plugin פעיל + סוכן מתעורר על comment\n\nתלוי בלי dependencies (יכול להיות מבוצע בכל עת אחרי שיש stable חדש).",
|
||||||
|
"testStrategy": "אחרי שדרוג: cat ~/.npm/_npx/43414d9b790239bb/node_modules/paperclipai/package.json | grep version → גרסה חדשה. UI עברית. test wakeup על issue.",
|
||||||
|
"status": "pending",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "low",
|
||||||
|
"subtasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "28",
|
||||||
|
"title": "[Paperclip Auxiliary] להפעיל skill-sync ל-2 סוכנים שפיספסו",
|
||||||
|
"description": "הגהת מסמכים ומנתח משפטי לא קיבלו אף פעם revision מסוג skill-sync (לעומת 5 האחרים שכן). לבצע sync.",
|
||||||
|
"details": "ממצא: בדיקה ב-agent_config_revisions:\n- עוזר משפטי: 3 skill-sync revisions (יש 7 skills)\n- חוקר תקדימים: 3 (יש 5)\n- מייצא טיוטה: 5 (יש 5)\n- בודק איכות: 1 (יש 5)\n- כותב החלטה: 1 (יש 5)\n- הגהת מסמכים: 0 (יש 0) ❌\n- מנתח משפטי: 0 (יש 0) ❌\n\nאופציות:\n1. UI: agent settings → 'sync skills'\n2. API: POST /api/agents/{id}/skills-sync (לאתר)\n3. CLI: paperclipai agent skill-sync (לבדוק אם קיים)\n4. SQL ידני (לא מומלץ — דורף revision tracking)\n\nSkills להעתקה (לפי בודק איכות):\n- paperclipai/paperclip/paperclip\n- paperclipai/paperclip/paperclip-create-agent\n- paperclipai/paperclip/paperclip-create-plugin\n- paperclipai/paperclip/para-memory-files\n- (אופציונלי) local/eba6210d5a/legal-decision",
|
||||||
|
"testStrategy": "SELECT name, jsonb_array_length(adapter_config->'paperclipSkillSync'->'desiredSkills') FROM agents WHERE name IN ('הגהת מסמכים', 'מנתח משפטי') → 4-5. revision חדש ב-agent_config_revisions עם source='skill-sync'.",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [],
|
||||||
|
"updatedAt": "2026-05-04T09:46:32.092Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "29",
|
||||||
|
"title": "[legal-ai UI] מסך הגדרות סוכנים — הצגה + עריכה + שמירה",
|
||||||
|
"description": "מסך אדמין ב-legal-ai UI שמציג את כל הגדרות הסוכנים (model, timeout, runtime_config, skills, budget) ומאפשר עריכה ושמירה. מונע SQL ישיר.",
|
||||||
|
"details": "מטרה: ממשק אדמין מרכזי במקום שעריכה תהיה רק ב-UI של Paperclip + SQL ישיר + CUSTOMIZATIONS.md.\n\nשדות (לכל סוכן × 2 חברות):\n1. adapter_config: model, effort, timeoutSec, maxTurnsPerRun, extraArgs[], paperclipSkillSync.desiredSkills[]\n2. runtime_config.heartbeat: graceSec, cooldownSec, wakeOnDemand, maxConcurrentRuns, enabled, intervalSec\n3. budget_monthly_cents (לקראת gap #2)\n4. status / pause_reason (קריאה + כפתור pause/resume)\n\nאופציות מימוש:\nA. עמוד חדש ב-legal-ai/web-ui (Next.js 16) — קורא Paperclip DB דרך FastAPI endpoint חדש (/api/admin/paperclip-agents)\nB. קריאה ל-Paperclip API (/api/companies/{id}/agents) — REST טהור, פחות שדות זמינים\nC. iframe ל-Paperclip UI — שטחי\n\nהמלצה: A. שולט מלא + ולידציה משפטית (timeoutSec >= 1800 כי OCR).\n\nתלוי ב: gap #25 (סוכנים מוכפלים) — אם נעבור לshared, המסך יתאים.\n\nשאלות פתוחות לחיים:\n- auth: מי יכול לגשת? (כיום אין auth ב-legal-ai)\n- bulk edit ל-2 חברות יחד או נפרד?\n- חשיפת skill marketplace (להוסיף/להוריד skills) או רק קריאה?",
|
||||||
|
"testStrategy": "1. עמוד עולה ב-/admin/agents בlegal-ai UI. 2. עריכת timeoutSec ושמירה → SELECT ב-DB מאמת. 3. revision חדש ב-agent_config_revisions עם source מתאים.",
|
||||||
|
"status": "pending",
|
||||||
|
"dependencies": [
|
||||||
|
"16",
|
||||||
|
"17",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lastModified": "2026-05-03T10:19:15.134Z",
|
"lastModified": "2026-05-04T16:44:27.554Z",
|
||||||
"taskCount": 13,
|
"taskCount": 29,
|
||||||
"completedCount": 12,
|
"completedCount": 23,
|
||||||
"tags": [
|
"tags": [
|
||||||
"legal-ai"
|
"legal-ai"
|
||||||
]
|
]
|
||||||
|
|||||||
20
CLAUDE.md
20
CLAUDE.md
@@ -48,6 +48,7 @@
|
|||||||
| [`docs/corpus-analysis.md`](docs/corpus-analysis.md) | ניתוח שיטתי של 24 החלטות — מפת תוכן, דפוסי דיון תכנוני, פערים | **לפני כל כתיבת החלטה** |
|
| [`docs/corpus-analysis.md`](docs/corpus-analysis.md) | ניתוח שיטתי של 24 החלטות — מפת תוכן, דפוסי דיון תכנוני, פערים | **לפני כל כתיבת החלטה** |
|
||||||
| [`docs/product-specification.md`](docs/product-specification.md) | איפיון מוצר מלא — personas, תהליכים עסקיים, דרישות | להתמצאות עסקית/מוצרית |
|
| [`docs/product-specification.md`](docs/product-specification.md) | איפיון מוצר מלא — personas, תהליכים עסקיים, דרישות | להתמצאות עסקית/מוצרית |
|
||||||
| [`docs/new-company-setup-guide.md`](docs/new-company-setup-guide.md) | מדריך הקמת חברה חדשה (CMPA) — skills, corpus, style analysis | לפני הוספת חברה/סוג ערר חדש |
|
| [`docs/new-company-setup-guide.md`](docs/new-company-setup-guide.md) | מדריך הקמת חברה חדשה (CMPA) — skills, corpus, style analysis | לפני הוספת חברה/סוג ערר חדש |
|
||||||
|
| [`skills/new-company-setup/SKILL.md`](skills/new-company-setup/SKILL.md) | **Blueprint טכני מלא להוספת חברה** — 11 שלבים מסודרים (companies, agents, runtime/adapter, skills, instructions, code, mappings) + checklist 10 מלכודות מ-Gap analysis #16-#28 | **חובה לפני הוספת חברה** (יותר actionable מ-doc) |
|
||||||
| [`docs/audit-report.md`](docs/audit-report.md) | דוח audit של המערכת | רקע כללי |
|
| [`docs/audit-report.md`](docs/audit-report.md) | דוח audit של המערכת | רקע כללי |
|
||||||
| [`docs/case-migration-tracker.md`](docs/case-migration-tracker.md) | מעקב מיגרציה של תיקים קיימים | לצורך מעקב |
|
| [`docs/case-migration-tracker.md`](docs/case-migration-tracker.md) | מעקב מיגרציה של תיקים קיימים | לצורך מעקב |
|
||||||
| [`docs/case-deletion-runbook.md`](docs/case-deletion-runbook.md) | runbook מלא למחיקת תיק — legal-ai DB + disk + Paperclip + Gitea, FK ordering, fallback ל-SQL ישיר | לפני reset שלם של תיק (מבחן, מחיקה בטעות) |
|
| [`docs/case-deletion-runbook.md`](docs/case-deletion-runbook.md) | runbook מלא למחיקת תיק — legal-ai DB + disk + Paperclip + Gitea, FK ordering, fallback ל-SQL ישיר | לפני reset שלם של תיק (מבחן, מחיקה בטעות) |
|
||||||
@@ -160,6 +161,25 @@
|
|||||||
- ה-CEO קורא את ה-comment, מחליט על ניתוב, ויוצר issue לסוכן המתאים
|
- ה-CEO קורא את ה-comment, מחליט על ניתוב, ויוצר issue לסוכן המתאים
|
||||||
- כל הסוכנים חייבים לקרוא comments אחרונים לפני שהם מתחילים לעבוד (HEARTBEAT שלבים 2b-2c)
|
- כל הסוכנים חייבים לקרוא comments אחרונים לפני שהם מתחילים לעבוד (HEARTBEAT שלבים 2b-2c)
|
||||||
|
|
||||||
|
### קריאות API — תמיד דרך helper, לעולם לא `curl` ישיר
|
||||||
|
- **bash (סוכנים):** `~/legal-ai/scripts/pc.sh <METHOD> <PATH> [BODY_JSON]` — מוסיף Authorization, X-Paperclip-Run-Id, Content-Type, base URL. ראה `HEARTBEAT.md §0`.
|
||||||
|
- **Python (FastAPI):** `from web.paperclip_api import pc_request; await pc_request("POST", "/api/...", json={...})` — שימוש ב-board API key.
|
||||||
|
- **אסור** `curl ... $PAPERCLIP_API_URL` ישיר ב-bash; **אסור** `httpx.AsyncClient` ישיר ל-Paperclip ב-Python.
|
||||||
|
- **למה:** ה-skill הרשמי דורש `X-Paperclip-Run-Id` בכל קריאה משנה issue. אצלנו ה-audit trail עבד ממילא דרך JWT claims (`runId: runIdHeader || claims.run_id`), אבל ה-helper מבטיח עקביות + תאימות ל-board API keys (long-lived) שלא נושאות JWT claims.
|
||||||
|
|
||||||
|
### Cross-company agent sync — אחרי כל שינוי הגדרות
|
||||||
|
- יש 14 סוכנים = 7 × 2 חברות (CMP=1xxx, CMPA=8xxx). Paperclip מחייב `agents.company_id NOT NULL` — אין shared agents.
|
||||||
|
- **Master = CMP (1xxx)**, **Mirror = CMPA (8xxx)**.
|
||||||
|
- אחרי כל שינוי ב-`adapter_config`, `runtime_config`, `budget_monthly_cents`, או skills של סוכן ב-master (UI, SQL, או API), **חובה להריץ:**
|
||||||
|
```bash
|
||||||
|
PAPERCLIP_BOARD_API_KEY=$(...infisical...) \
|
||||||
|
python ~/legal-ai/scripts/sync_agents_across_companies.py --verify # לבדיקה
|
||||||
|
PAPERCLIP_BOARD_API_KEY=$(...) \
|
||||||
|
python ~/legal-ai/scripts/sync_agents_across_companies.py --apply # לסנכרן
|
||||||
|
```
|
||||||
|
- הסקריפט מסנן local skills שלא קיימים ב-CMPA (מציג אזהרה), משתמש ב-API (לא DB ישיר), יוצר revisions, idempotent.
|
||||||
|
- שאלות ה-skill הרשמי של Paperclip — `paperclip` skill תחת `paperclipai/paperclip`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## עקרונות כתיבה קריטיים
|
## עקרונות כתיבה קריטיים
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
| Script | Type | Purpose | Scheduled |
|
| Script | Type | Purpose | Scheduled |
|
||||||
|--------|------|---------|-----------|
|
|--------|------|---------|-----------|
|
||||||
|
| `pc.sh` | bash | **wrapper לכל קריאות Paperclip API מסוכנים** — מוסיף Authorization, X-Paperclip-Run-Id (audit trail), Content-Type, base URL. תחביר: `pc.sh <METHOD> <PATH> [BODY_JSON]`. אסור `curl` ישיר ל-`$PAPERCLIP_API_URL`. ראה `HEARTBEAT.md §0`. counterpart ב-Python: `web/paperclip_api.py`. | נקרא ע"י סוכנים |
|
||||||
|
| `sync_missing_agent_skills.py` | python | סקריפט "אל-כשל" להוספת `paperclipSkillSync` ל-`הגהת מסמכים` ו-`מנתח משפטי` שפיספסו את ה-sync ההיסטורי (Gap #28). תומך `--verify`/`--dry-run`/`--apply`. גיבוי אוטומטי ל-`agents-pre-skill-sync-*.sql`. דורש `PAPERCLIP_BOARD_API_KEY` (Infisical /paperclip ב-nautilus env). idempotent. | חד-פעמי (בוצע 2026-05-04). שמור לרפרנס |
|
||||||
|
| `sync_agents_across_companies.py` | python | **סנכרון סוכנים מ-CMP (1xxx, master) ל-CMPA (8xxx, mirror)** — Gap #25. משווה adapter_config (model/timeout/instructions/skills/etc), runtime_config (heartbeat), ושדות top-level (budget/metadata/icon/title/role). מסנן אוטומטית local skills שלא קיימים ב-mirror. לוגיקת subset (mirror יכול להחזיק יותר skills כי ה-API מוסיף required runtime skills). תומך `--verify`/`--dry-run`/`--apply [--only NAME]`. גיבוי אוטומטי. דורש `PAPERCLIP_BOARD_API_KEY`. **להריץ אחרי כל שינוי הגדרות ב-CMP.** | ידני אחרי כל שינוי |
|
||||||
| `auto-sync-cases.sh` | bash | סנכרון תיקי ערר ל-Gitea — רץ כל דקה | `* * * * *` (cron) |
|
| `auto-sync-cases.sh` | bash | סנכרון תיקי ערר ל-Gitea — רץ כל דקה | `* * * * *` (cron) |
|
||||||
| `backup-db.sh` | bash | גיבוי PostgreSQL יומי ל-`data/backups/` (gzip) | לתזמן: `0 2 * * *` |
|
| `backup-db.sh` | bash | גיבוי PostgreSQL יומי ל-`data/backups/` (gzip) | לתזמן: `0 2 * * *` |
|
||||||
| `restore-db.sh` | bash | שחזור DB מגיבוי (companion ל-backup-db.sh) | ידני |
|
| `restore-db.sh` | bash | שחזור DB מגיבוי (companion ל-backup-db.sh) | ידני |
|
||||||
|
|||||||
52
scripts/pc.sh
Executable file
52
scripts/pc.sh
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# pc.sh — Paperclip API wrapper for agents.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# pc.sh <method> <path> [body_json] [extra_curl_args...]
|
||||||
|
#
|
||||||
|
# Adds:
|
||||||
|
# - Authorization: Bearer $PAPERCLIP_API_KEY
|
||||||
|
# - X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID (audit trail; falls back to JWT claims if empty)
|
||||||
|
# - Content-Type: application/json (when body provided)
|
||||||
|
# - Base URL: $PAPERCLIP_API_URL
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ~/legal-ai/scripts/pc.sh GET "/api/agents/me/inbox-lite"
|
||||||
|
# ~/legal-ai/scripts/pc.sh POST "/api/issues/$ISSUE_ID/checkout"
|
||||||
|
# ~/legal-ai/scripts/pc.sh POST "/api/issues/$ISSUE_ID/comments" '{"body":"שלום"}'
|
||||||
|
# ~/legal-ai/scripts/pc.sh PATCH "/api/issues/$ISSUE_ID" '{"status":"done"}'
|
||||||
|
# ~/legal-ai/scripts/pc.sh DELETE "/api/issues/$ISSUE_ID"
|
||||||
|
#
|
||||||
|
# Sourcing as a function (optional):
|
||||||
|
# source ~/legal-ai/scripts/pc.sh && pc POST "/api/issues/$ISSUE_ID/checkout"
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
pc() {
|
||||||
|
local method="${1:-}"
|
||||||
|
local path="${2:-}"
|
||||||
|
local body="${3:-}"
|
||||||
|
if [ $# -ge 3 ]; then shift 3; else shift "$#"; fi
|
||||||
|
|
||||||
|
if [ -z "$method" ] || [ -z "$path" ]; then
|
||||||
|
echo "usage: pc.sh <METHOD> <PATH> [BODY_JSON] [extra curl args...]" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
: "${PAPERCLIP_API_URL:?PAPERCLIP_API_URL not set}"
|
||||||
|
: "${PAPERCLIP_API_KEY:?PAPERCLIP_API_KEY not set}"
|
||||||
|
|
||||||
|
local args=(-s -X "$method"
|
||||||
|
-H "Authorization: Bearer $PAPERCLIP_API_KEY"
|
||||||
|
-H "X-Paperclip-Run-Id: ${PAPERCLIP_RUN_ID:-}")
|
||||||
|
|
||||||
|
if [ -n "$body" ]; then
|
||||||
|
args+=(-H "Content-Type: application/json" -d "$body")
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl "${args[@]}" "$@" "${PAPERCLIP_API_URL}${path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# When invoked directly (not sourced), forward args to pc().
|
||||||
|
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||||
|
pc "$@"
|
||||||
|
fi
|
||||||
382
scripts/sync_agents_across_companies.py
Normal file
382
scripts/sync_agents_across_companies.py
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""sync_agents_across_companies.py — Mirror agent configs from CMP (1xxx) to CMPA (8xxx).
|
||||||
|
|
||||||
|
Gap #25: Paperclip enforces ``agents.company_id NOT NULL``, so we have 14
|
||||||
|
agents (7 × 2 companies). Without sync, settings drift between the master
|
||||||
|
(CMP, 1xxx) and the mirror (CMPA, 8xxx). This script copies the relevant
|
||||||
|
fields one-way: CMP → CMPA.
|
||||||
|
|
||||||
|
Design: "אל-כשל" — backup before apply, idempotent, dry-run by default,
|
||||||
|
clear field-level diff, rollback path printed on failure.
|
||||||
|
|
||||||
|
Synced fields:
|
||||||
|
- adapter_config.{model, effort, timeoutSec, maxTurnsPerRun,
|
||||||
|
instructionsBundleMode, instructionsRootPath,
|
||||||
|
instructionsEntryFile, instructionsFilePath,
|
||||||
|
dangerouslySkipPermissions, extraArgs, cwd}
|
||||||
|
- adapter_config.paperclipSkillSync.desiredSkills (filtered for skills
|
||||||
|
that exist in the mirror company — local skills like
|
||||||
|
``local/eba6210d5a/legal-decision`` only exist in CMP)
|
||||||
|
- runtime_config (full replace — heartbeat config)
|
||||||
|
- budget_monthly_cents
|
||||||
|
- metadata, icon, title, role
|
||||||
|
|
||||||
|
Not synced (intentionally per-company):
|
||||||
|
- id, company_id, name, reports_to, default_environment_id
|
||||||
|
- adapter_type, agent_api_keys
|
||||||
|
- status, pause_reason, paused_at, last_heartbeat_at
|
||||||
|
- spent_monthly_cents (separate usage)
|
||||||
|
- permissions (per-company access policies)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python sync_agents_across_companies.py --verify # show drift only
|
||||||
|
python sync_agents_across_companies.py --dry-run # show plan
|
||||||
|
python sync_agents_across_companies.py --apply # backup + apply
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
PAPERCLIP_BOARD_API_KEY (Infisical: /paperclip @ nautilus)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
PAPERCLIP_DB_URL = os.environ.get(
|
||||||
|
"PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip"
|
||||||
|
)
|
||||||
|
PAPERCLIP_API_URL = os.environ.get("PAPERCLIP_API_URL", "http://localhost:3100")
|
||||||
|
PAPERCLIP_BOARD_API_KEY = os.environ.get("PAPERCLIP_BOARD_API_KEY", "")
|
||||||
|
|
||||||
|
BACKUP_DIR = Path("/home/chaim/.paperclip/instances/default/data/backups/manual")
|
||||||
|
|
||||||
|
CMP_COMPANY_ID = "42a7acd0-30c5-4cbd-ac97-7424f65df294" # MASTER (1xxx)
|
||||||
|
CMPA_COMPANY_ID = "8639e837-4c9d-47fa-a76b-95788d651896" # MIRROR (8xxx)
|
||||||
|
|
||||||
|
# adapter_config keys to sync (top-level only; paperclipSkillSync handled separately)
|
||||||
|
ADAPTER_CONFIG_SYNC_KEYS = [
|
||||||
|
"model", "effort", "timeoutSec", "maxTurnsPerRun",
|
||||||
|
"instructionsBundleMode", "instructionsRootPath", "instructionsEntryFile", "instructionsFilePath",
|
||||||
|
"dangerouslySkipPermissions", "extraArgs", "cwd",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Top-level agent fields to sync
|
||||||
|
TOP_LEVEL_SYNC_FIELDS = [
|
||||||
|
"budget_monthly_cents", "metadata", "icon", "title", "role",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg: str) -> None:
|
||||||
|
print(f"❌ {msg}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_agents(conn: asyncpg.Connection, company_id: str) -> list[dict[str, Any]]:
|
||||||
|
rows = await conn.fetch(
|
||||||
|
"""
|
||||||
|
SELECT id::text, name, role, title, icon,
|
||||||
|
adapter_type, adapter_config, runtime_config, metadata,
|
||||||
|
budget_monthly_cents
|
||||||
|
FROM agents
|
||||||
|
WHERE company_id = $1::uuid
|
||||||
|
ORDER BY name
|
||||||
|
""",
|
||||||
|
company_id,
|
||||||
|
)
|
||||||
|
out = []
|
||||||
|
for r in rows:
|
||||||
|
d = dict(r)
|
||||||
|
# asyncpg returns jsonb as str; parse
|
||||||
|
for k in ("adapter_config", "runtime_config", "metadata"):
|
||||||
|
if isinstance(d.get(k), str):
|
||||||
|
d[k] = json.loads(d[k]) if d[k] else None
|
||||||
|
out.append(d)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_company_skills(conn: asyncpg.Connection, company_id: str) -> set[str]:
|
||||||
|
rows = await conn.fetch(
|
||||||
|
"SELECT key FROM company_skills WHERE company_id = $1::uuid",
|
||||||
|
company_id,
|
||||||
|
)
|
||||||
|
return {r["key"] for r in rows}
|
||||||
|
|
||||||
|
|
||||||
|
def _get(d: dict | None, key: str, default=None):
|
||||||
|
return d.get(key, default) if isinstance(d, dict) else default
|
||||||
|
|
||||||
|
|
||||||
|
def compute_diff(master: dict, mirror: dict, mirror_skills: set[str]) -> dict[str, Any]:
|
||||||
|
"""Return a dict describing what would change in mirror to match master.
|
||||||
|
Empty dict = in sync."""
|
||||||
|
diff: dict[str, Any] = {}
|
||||||
|
|
||||||
|
# Top-level fields
|
||||||
|
for field in TOP_LEVEL_SYNC_FIELDS:
|
||||||
|
if master.get(field) != mirror.get(field):
|
||||||
|
diff[field] = {"from": mirror.get(field), "to": master.get(field)}
|
||||||
|
|
||||||
|
# adapter_config (per key)
|
||||||
|
m_ac = master.get("adapter_config") or {}
|
||||||
|
r_ac = mirror.get("adapter_config") or {}
|
||||||
|
ac_changes = {}
|
||||||
|
for key in ADAPTER_CONFIG_SYNC_KEYS:
|
||||||
|
if _get(m_ac, key) != _get(r_ac, key):
|
||||||
|
ac_changes[key] = {"from": _get(r_ac, key), "to": _get(m_ac, key)}
|
||||||
|
if ac_changes:
|
||||||
|
diff["adapter_config"] = ac_changes
|
||||||
|
|
||||||
|
# paperclipSkillSync.desiredSkills — compare as a SUBSET check.
|
||||||
|
# The Paperclip API auto-adds company-level required runtime skills
|
||||||
|
# (e.g. paperclip-dev) to the desiredSkills list, so the mirror can
|
||||||
|
# legitimately have MORE skills than master. We only need master's
|
||||||
|
# filtered skills to be a subset of mirror's actual list.
|
||||||
|
master_desired = list((_get(m_ac, "paperclipSkillSync") or {}).get("desiredSkills") or [])
|
||||||
|
mirror_desired = list((_get(r_ac, "paperclipSkillSync") or {}).get("desiredSkills") or [])
|
||||||
|
master_filtered = [s for s in master_desired if s in mirror_skills]
|
||||||
|
skipped = [s for s in master_desired if s not in mirror_skills]
|
||||||
|
missing_in_mirror = set(master_filtered) - set(mirror_desired)
|
||||||
|
if missing_in_mirror:
|
||||||
|
diff["paperclipSkillSync.desiredSkills"] = {
|
||||||
|
"from": mirror_desired,
|
||||||
|
"to": master_filtered,
|
||||||
|
"missing_in_mirror": sorted(missing_in_mirror),
|
||||||
|
"skipped_unavailable_in_mirror": skipped,
|
||||||
|
}
|
||||||
|
|
||||||
|
# runtime_config (full replace)
|
||||||
|
if (master.get("runtime_config") or {}) != (mirror.get("runtime_config") or {}):
|
||||||
|
diff["runtime_config"] = {"from": mirror.get("runtime_config"), "to": master.get("runtime_config")}
|
||||||
|
|
||||||
|
return diff
|
||||||
|
|
||||||
|
|
||||||
|
def backup_agents_table() -> Path:
|
||||||
|
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
||||||
|
out = BACKUP_DIR / f"agents-pre-cross-company-sync-{stamp}.sql"
|
||||||
|
env = {**os.environ, "PGPASSWORD": "paperclip"}
|
||||||
|
subprocess.run(
|
||||||
|
["pg_dump", "-h", "127.0.0.1", "-p", "54329", "-U", "paperclip",
|
||||||
|
"-d", "paperclip", "-t", "agents", "--data-only", "-f", str(out)],
|
||||||
|
check=True, env=env,
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _short(value, max_len=80) -> str:
|
||||||
|
s = json.dumps(value, ensure_ascii=False, default=str) if not isinstance(value, str) else value
|
||||||
|
if len(s) > max_len:
|
||||||
|
return s[:max_len] + "..."
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def print_diff(agent_name: str, diff: dict, master_id: str, mirror_id: str) -> None:
|
||||||
|
if not diff:
|
||||||
|
print(f" ✓ {agent_name:14s} — in sync (no changes)")
|
||||||
|
return
|
||||||
|
print(f" ⚠ {agent_name:14s} — {len(diff)} change(s): master={master_id[:8]}… → mirror={mirror_id[:8]}…")
|
||||||
|
for key, change in diff.items():
|
||||||
|
if key == "adapter_config":
|
||||||
|
for ac_key, ac_change in change.items():
|
||||||
|
print(f" adapter_config.{ac_key}: {_short(ac_change['from'])} → {_short(ac_change['to'])}")
|
||||||
|
elif key == "paperclipSkillSync.desiredSkills":
|
||||||
|
print(f" paperclipSkillSync.desiredSkills: {len(change['from'])} → {len(change['to'])} skills")
|
||||||
|
for s in change.get("skipped_unavailable_in_mirror", []):
|
||||||
|
print(f" (skipped, not in mirror company: {s})")
|
||||||
|
elif key == "runtime_config":
|
||||||
|
print(f" runtime_config: full replace")
|
||||||
|
print(f" from: {_short(change['from'], 100)}")
|
||||||
|
print(f" to: {_short(change['to'], 100)}")
|
||||||
|
else:
|
||||||
|
print(f" {key}: {_short(change['from'])} → {_short(change['to'])}")
|
||||||
|
|
||||||
|
|
||||||
|
async def call_patch(agent_id: str, body: dict) -> tuple[int, dict]:
|
||||||
|
if not PAPERCLIP_BOARD_API_KEY:
|
||||||
|
fail("PAPERCLIP_BOARD_API_KEY not set")
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {PAPERCLIP_BOARD_API_KEY}",
|
||||||
|
"X-Paperclip-Run-Id": "",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
url = f"{PAPERCLIP_API_URL}/api/agents/{agent_id}"
|
||||||
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
|
resp = await client.patch(url, headers=headers, json=body)
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
data = {"raw": resp.text[:500]}
|
||||||
|
return resp.status_code, data
|
||||||
|
|
||||||
|
|
||||||
|
async def call_skill_sync(agent_id: str, desired_skills: list[str]) -> tuple[int, dict]:
|
||||||
|
if not PAPERCLIP_BOARD_API_KEY:
|
||||||
|
fail("PAPERCLIP_BOARD_API_KEY not set")
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {PAPERCLIP_BOARD_API_KEY}",
|
||||||
|
"X-Paperclip-Run-Id": "",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
url = f"{PAPERCLIP_API_URL}/api/agents/{agent_id}/skills/sync"
|
||||||
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
|
resp = await client.post(url, headers=headers, json={"desiredSkills": desired_skills})
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
data = {"raw": resp.text[:500]}
|
||||||
|
return resp.status_code, data
|
||||||
|
|
||||||
|
|
||||||
|
async def apply_diff(mirror_id: str, agent_name: str, diff: dict) -> list[str]:
|
||||||
|
"""Apply the computed diff to the mirror agent. Returns list of error strings."""
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
# Build PATCH body for top-level + adapter_config (skills handled separately)
|
||||||
|
patch_body: dict[str, Any] = {}
|
||||||
|
for field in TOP_LEVEL_SYNC_FIELDS:
|
||||||
|
if field in diff:
|
||||||
|
# snake_case → camelCase for the API
|
||||||
|
api_key = {
|
||||||
|
"budget_monthly_cents": "budgetMonthlyCents",
|
||||||
|
"metadata": "metadata",
|
||||||
|
"icon": "icon",
|
||||||
|
"title": "title",
|
||||||
|
"role": "role",
|
||||||
|
}[field]
|
||||||
|
patch_body[api_key] = diff[field]["to"]
|
||||||
|
if "adapter_config" in diff:
|
||||||
|
patch_body["adapterConfig"] = {k: v["to"] for k, v in diff["adapter_config"].items()}
|
||||||
|
if "runtime_config" in diff:
|
||||||
|
patch_body["runtimeConfig"] = diff["runtime_config"]["to"]
|
||||||
|
|
||||||
|
if patch_body:
|
||||||
|
status, data = await call_patch(mirror_id, patch_body)
|
||||||
|
if status >= 400:
|
||||||
|
errors.append(f"PATCH HTTP {status}: {json.dumps(data)[:300]}")
|
||||||
|
else:
|
||||||
|
print(f" ✓ PATCH applied ({len(patch_body)} top-level keys)")
|
||||||
|
|
||||||
|
# Skills via dedicated endpoint (creates 'skill-sync' revision)
|
||||||
|
if "paperclipSkillSync.desiredSkills" in diff:
|
||||||
|
desired = diff["paperclipSkillSync.desiredSkills"]["to"]
|
||||||
|
status, data = await call_skill_sync(mirror_id, desired)
|
||||||
|
if status >= 400:
|
||||||
|
errors.append(f"skills/sync HTTP {status}: {json.dumps(data)[:300]}")
|
||||||
|
else:
|
||||||
|
print(f" ✓ skills/sync applied ({len(desired)} skills)")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
p = argparse.ArgumentParser()
|
||||||
|
g = p.add_mutually_exclusive_group(required=True)
|
||||||
|
g.add_argument("--verify", action="store_true", help="Show current drift, no changes")
|
||||||
|
g.add_argument("--dry-run", action="store_true", help="Show what would change")
|
||||||
|
g.add_argument("--apply", action="store_true", help="Backup + apply changes")
|
||||||
|
p.add_argument("--only", help="Sync only the named agent (e.g., 'עוזר משפטי')")
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
||||||
|
try:
|
||||||
|
master_agents = await fetch_agents(conn, CMP_COMPANY_ID)
|
||||||
|
mirror_agents = await fetch_agents(conn, CMPA_COMPANY_ID)
|
||||||
|
mirror_skills = await fetch_company_skills(conn, CMPA_COMPANY_ID)
|
||||||
|
finally:
|
||||||
|
await conn.close()
|
||||||
|
|
||||||
|
mirror_by_name = {a["name"]: a for a in mirror_agents}
|
||||||
|
|
||||||
|
print(f"\n=== Master (CMP, 1xxx): {len(master_agents)} agents ===")
|
||||||
|
print(f"=== Mirror (CMPA, 8xxx): {len(mirror_agents)} agents ===")
|
||||||
|
print(f"=== Mirror has {len(mirror_skills)} local skills available ===\n")
|
||||||
|
|
||||||
|
print(f"=== Drift report ===")
|
||||||
|
plan: list[tuple[dict, dict, dict]] = [] # (master, mirror, diff)
|
||||||
|
for m in master_agents:
|
||||||
|
if args.only and m["name"] != args.only:
|
||||||
|
continue
|
||||||
|
mirror = mirror_by_name.get(m["name"])
|
||||||
|
if not mirror:
|
||||||
|
print(f" ⚠ {m['name']:14s} — NOT FOUND in mirror (skipping; we never auto-create)")
|
||||||
|
continue
|
||||||
|
if m["adapter_type"] != mirror["adapter_type"]:
|
||||||
|
print(f" ⚠ {m['name']:14s} — adapter_type mismatch ({m['adapter_type']} vs {mirror['adapter_type']}) — SKIPPING")
|
||||||
|
continue
|
||||||
|
diff = compute_diff(m, mirror, mirror_skills)
|
||||||
|
print_diff(m["name"], diff, m["id"], mirror["id"])
|
||||||
|
if diff:
|
||||||
|
plan.append((m, mirror, diff))
|
||||||
|
|
||||||
|
if args.verify:
|
||||||
|
print(f"\n(verify mode — exiting without changes)")
|
||||||
|
print(f"\nSummary: {len(plan)} agent(s) need sync, {len(master_agents) - len(plan)} in sync")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not plan:
|
||||||
|
print(f"\n✓ All agents in sync — nothing to do.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"\n(dry-run mode — exiting without changes)\nRe-run with --apply to execute.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# APPLY
|
||||||
|
print(f"\n=== Backup ===")
|
||||||
|
backup_path = backup_agents_table()
|
||||||
|
print(f" ✓ {backup_path}")
|
||||||
|
|
||||||
|
print(f"\n=== Applying ({len(plan)} agents) ===")
|
||||||
|
all_errors: list[str] = []
|
||||||
|
for master, mirror, diff in plan:
|
||||||
|
print(f"\n → {master['name']} ({mirror['id']})")
|
||||||
|
errors = await apply_diff(mirror["id"], master["name"], diff)
|
||||||
|
if errors:
|
||||||
|
for e in errors:
|
||||||
|
print(f" ❌ {e}")
|
||||||
|
all_errors.extend([f"{master['name']}: {e}" for e in errors])
|
||||||
|
|
||||||
|
if all_errors:
|
||||||
|
print(f"\n=== ⚠️ {len(all_errors)} error(s) ===")
|
||||||
|
print(f"Rollback option: psql ... -f {backup_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"\n=== ✓ Sync complete — re-running --verify to confirm ===\n")
|
||||||
|
# Re-verify
|
||||||
|
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
||||||
|
try:
|
||||||
|
master_agents = await fetch_agents(conn, CMP_COMPANY_ID)
|
||||||
|
mirror_agents = await fetch_agents(conn, CMPA_COMPANY_ID)
|
||||||
|
mirror_skills = await fetch_company_skills(conn, CMPA_COMPANY_ID)
|
||||||
|
finally:
|
||||||
|
await conn.close()
|
||||||
|
mirror_by_name = {a["name"]: a for a in mirror_agents}
|
||||||
|
|
||||||
|
still_drifting = 0
|
||||||
|
for m in master_agents:
|
||||||
|
mirror = mirror_by_name.get(m["name"])
|
||||||
|
if not mirror or m["adapter_type"] != mirror["adapter_type"]:
|
||||||
|
continue
|
||||||
|
diff = compute_diff(m, mirror, mirror_skills)
|
||||||
|
if diff:
|
||||||
|
still_drifting += 1
|
||||||
|
print(f" ⚠ {m['name']:14s} — STILL has {len(diff)} change(s) after apply (review!)")
|
||||||
|
|
||||||
|
if still_drifting == 0:
|
||||||
|
print(f" ✓ All {len(master_agents)} agents in sync.")
|
||||||
|
else:
|
||||||
|
print(f"\n⚠️ {still_drifting} agents still drifting — investigate.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
191
scripts/sync_missing_agent_skills.py
Normal file
191
scripts/sync_missing_agent_skills.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""sync_missing_agent_skills.py — One-shot fix for Gap #28.
|
||||||
|
|
||||||
|
Adds the missing paperclipSkillSync to הגהת מסמכים and מנתח משפטי
|
||||||
|
in both companies (1xxx CMP, 8xxx CMPA). Idempotent: safe to re-run.
|
||||||
|
|
||||||
|
Design: "אל-כשל" — backup, dry-run mode, idempotent, clear errors.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python sync_missing_agent_skills.py --dry-run # show plan only
|
||||||
|
python sync_missing_agent_skills.py --apply # actually do it
|
||||||
|
python sync_missing_agent_skills.py --verify # check current state
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
PAPERCLIP_DB_URL = os.environ.get(
|
||||||
|
"PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip"
|
||||||
|
)
|
||||||
|
PAPERCLIP_API_URL = os.environ.get("PAPERCLIP_API_URL", "http://localhost:3100")
|
||||||
|
PAPERCLIP_BOARD_API_KEY = os.environ.get("PAPERCLIP_BOARD_API_KEY", "")
|
||||||
|
|
||||||
|
BACKUP_DIR = Path("/home/chaim/.paperclip/instances/default/data/backups/manual")
|
||||||
|
|
||||||
|
PAPERCLIP_BASE_SKILLS = [
|
||||||
|
"paperclipai/paperclip/paperclip",
|
||||||
|
"paperclipai/paperclip/paperclip-create-agent",
|
||||||
|
"paperclipai/paperclip/paperclip-create-plugin",
|
||||||
|
"paperclipai/paperclip/para-memory-files",
|
||||||
|
]
|
||||||
|
|
||||||
|
CMP_COMPANY_ID = "42a7acd0-30c5-4cbd-ac97-7424f65df294" # 1xxx — רישוי ובניה
|
||||||
|
CMPA_COMPANY_ID = "8639e837-4c9d-47fa-a76b-95788d651896" # 8xxx — היטלי השבחה
|
||||||
|
|
||||||
|
# Per-agent + per-company desired skills
|
||||||
|
PLAN: dict[tuple[str, str], list[str]] = {
|
||||||
|
# (agent_name, company_id) -> desired skills
|
||||||
|
("מנתח משפטי", CMP_COMPANY_ID): PAPERCLIP_BASE_SKILLS + ["local/eba6210d5a/legal-decision"],
|
||||||
|
("מנתח משפטי", CMPA_COMPANY_ID): PAPERCLIP_BASE_SKILLS, # CMPA has no local skills
|
||||||
|
("הגהת מסמכים", CMP_COMPANY_ID): PAPERCLIP_BASE_SKILLS,
|
||||||
|
("הגהת מסמכים", CMPA_COMPANY_ID): PAPERCLIP_BASE_SKILLS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg: str) -> None:
|
||||||
|
print(f"❌ {msg}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_targets() -> list[dict[str, Any]]:
|
||||||
|
"""Return rows for the agents we plan to update."""
|
||||||
|
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
||||||
|
try:
|
||||||
|
rows = await conn.fetch(
|
||||||
|
"""
|
||||||
|
SELECT a.id, a.name, a.company_id::text as company_id,
|
||||||
|
COALESCE(
|
||||||
|
jsonb_array_length(a.adapter_config->'paperclipSkillSync'->'desiredSkills'),
|
||||||
|
0
|
||||||
|
) as current_skill_count
|
||||||
|
FROM agents a
|
||||||
|
WHERE a.name IN ('מנתח משפטי', 'הגהת מסמכים')
|
||||||
|
ORDER BY a.name, a.company_id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await conn.close()
|
||||||
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def backup_agents_table() -> Path:
|
||||||
|
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
||||||
|
out = BACKUP_DIR / f"agents-pre-skill-sync-{stamp}.sql"
|
||||||
|
env = {**os.environ, "PGPASSWORD": "paperclip"}
|
||||||
|
subprocess.run(
|
||||||
|
["pg_dump", "-h", "127.0.0.1", "-p", "54329", "-U", "paperclip",
|
||||||
|
"-d", "paperclip", "-t", "agents", "--data-only", "-f", str(out)],
|
||||||
|
check=True, env=env,
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
async def call_skill_sync(agent_id: str, desired_skills: list[str]) -> tuple[int, dict[str, Any]]:
|
||||||
|
"""Call POST /api/agents/{id}/skills/sync with the desired skills list."""
|
||||||
|
if not PAPERCLIP_BOARD_API_KEY:
|
||||||
|
fail("PAPERCLIP_BOARD_API_KEY not set — needed for /api/agents/.../skills/sync")
|
||||||
|
url = f"{PAPERCLIP_API_URL}/api/agents/{agent_id}/skills/sync"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {PAPERCLIP_BOARD_API_KEY}",
|
||||||
|
"X-Paperclip-Run-Id": "",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
body = {"desiredSkills": desired_skills}
|
||||||
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
|
resp = await client.post(url, headers=headers, json=body)
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
data = {"raw": resp.text[:500]}
|
||||||
|
return resp.status_code, data
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
p = argparse.ArgumentParser()
|
||||||
|
g = p.add_mutually_exclusive_group(required=True)
|
||||||
|
g.add_argument("--dry-run", action="store_true", help="Show plan, do not apply")
|
||||||
|
g.add_argument("--apply", action="store_true", help="Actually call the skill-sync API")
|
||||||
|
g.add_argument("--verify", action="store_true", help="Show current state only")
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
targets = await fetch_targets()
|
||||||
|
if len(targets) != 4:
|
||||||
|
fail(f"Expected 4 target rows (2 agents × 2 companies), got {len(targets)}")
|
||||||
|
|
||||||
|
# Build a map for plan
|
||||||
|
by_key = {(r["name"], r["company_id"]): r for r in targets}
|
||||||
|
|
||||||
|
print(f"\n=== Targets in DB ({len(targets)} rows) ===")
|
||||||
|
for r in targets:
|
||||||
|
company_label = "1xxx CMP" if r["company_id"] == CMP_COMPANY_ID else "8xxx CMPA"
|
||||||
|
print(f" {r['name']:14s} | {company_label} | id={r['id']} | currently {r['current_skill_count']} skills")
|
||||||
|
|
||||||
|
print(f"\n=== Plan ===")
|
||||||
|
for (agent_name, company_id), desired in PLAN.items():
|
||||||
|
company_label = "1xxx CMP" if company_id == CMP_COMPANY_ID else "8xxx CMPA"
|
||||||
|
target = by_key.get((agent_name, company_id))
|
||||||
|
if not target:
|
||||||
|
print(f" ❌ {agent_name} in {company_label}: NOT FOUND in DB")
|
||||||
|
continue
|
||||||
|
print(f" {agent_name:14s} | {company_label} | will set {len(desired)} skills:")
|
||||||
|
for s in desired:
|
||||||
|
print(f" - {s}")
|
||||||
|
|
||||||
|
if args.verify:
|
||||||
|
print("\n(verify mode — exiting without changes)")
|
||||||
|
return
|
||||||
|
if args.dry_run:
|
||||||
|
print("\n(dry-run mode — exiting without changes)\nRe-run with --apply to execute.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# APPLY mode
|
||||||
|
print(f"\n=== Backup ===")
|
||||||
|
backup_path = backup_agents_table()
|
||||||
|
print(f" ✓ Backed up agents table → {backup_path}")
|
||||||
|
|
||||||
|
print(f"\n=== Applying skill-sync via API ===")
|
||||||
|
failures = []
|
||||||
|
for (agent_name, company_id), desired in PLAN.items():
|
||||||
|
target = by_key.get((agent_name, company_id))
|
||||||
|
if not target:
|
||||||
|
failures.append(f"{agent_name} in {company_id}: not found")
|
||||||
|
continue
|
||||||
|
status, data = await call_skill_sync(target["id"], desired)
|
||||||
|
if status >= 400:
|
||||||
|
failures.append(f"{agent_name} ({company_id[:8]}...): HTTP {status} — {json.dumps(data)[:200]}")
|
||||||
|
print(f" ❌ {agent_name} ({target['id']}): HTTP {status}")
|
||||||
|
else:
|
||||||
|
new_count = len(data.get("desiredSkills") or data.get("skills") or [])
|
||||||
|
print(f" ✓ {agent_name} ({target['id']}): HTTP {status} (now {new_count or len(desired)} skills)")
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print(f"\n=== ⚠️ {len(failures)} failures ===")
|
||||||
|
for f in failures:
|
||||||
|
print(f" - {f}")
|
||||||
|
print(f"\nRollback: psql ... -f {backup_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
print(f"\n=== Post-apply verification ===")
|
||||||
|
final = await fetch_targets()
|
||||||
|
for r in final:
|
||||||
|
company_label = "1xxx CMP" if r["company_id"] == CMP_COMPANY_ID else "8xxx CMPA"
|
||||||
|
emoji = "✓" if r["current_skill_count"] >= 4 else "❌"
|
||||||
|
print(f" {emoji} {r['name']:14s} | {company_label} | now {r['current_skill_count']} skills")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
316
skills/new-company-setup/SKILL.md
Normal file
316
skills/new-company-setup/SKILL.md
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
---
|
||||||
|
name: new-company-setup
|
||||||
|
description: מדריך מלא להוספת חברה (board) חדשה במערכת legal-ai + Paperclip — יוצר את כל הרכיבים הנדרשים: companies row ב-Paperclip, 7 סוכנים (CEO + 6 specialists), runtime/adapter config, paperclipSkillSync, instructionsBundleMode, budget, plugin_state mappings, ועדכון קוד legal-ai. השתמש ב-skill זה כאשר המשתמש מבקש להוסיף סוג ערר חדש (למשל 5xxx, 7xxx) או להפריד תחום קיים לחברה משלו. ה-skill מכיל את כל ההגדרות שנקבעו ב-Gaps #16, #17, #19, #21, #22, #24, #25, #28 — אסור להחסיר שלב.
|
||||||
|
---
|
||||||
|
|
||||||
|
# הקמת חברה חדשה — Blueprint מלא
|
||||||
|
|
||||||
|
> **קונטקסט**: עד 2026-05-04 יש לנו 2 חברות (CMP=1xxx רישוי, CMPA=8xxx היטל השבחה + 9xxx פיצויים). הוספת חברה שלישית (לדוגמה 5xxx, 7xxx) דורשת 11 שלבים בסדר מסוים. ה-skill הזה מכיל את כל הלקחים מ-Gap analysis ועדכוני 2026-04 → 2026-05.
|
||||||
|
|
||||||
|
## רקע — ארכיטקטורה דו-חברתית
|
||||||
|
מקור החברות: Paperclip מחייב `agents.company_id NOT NULL` — אין shared agents. לכן כל סוג ערר מקבל company משלו ב-Paperclip, עם סט מלא של 7 סוכנים. החברה הראשונה (CMP) היא **master** — שינויים בה מסונכרנים אוטומטית ל-mirrors דרך `scripts/sync_agents_across_companies.py`.
|
||||||
|
|
||||||
|
**מודל מומלץ לחברה חדשה**: להפוך אותה ל-mirror של CMP במבנה — כל הסוכנים זהים, רק `company_id`, `id`, `reports_to` שונים. ככה הסקריפט הקיים יסנכרן אוטומטית.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ לפני שמתחילים — checklist הבנה
|
||||||
|
|
||||||
|
לפני שמרצים אף פקודה, ודא שאתה יודע:
|
||||||
|
|
||||||
|
- [ ] **מספרי תיקים** של החברה החדשה (לדוגמה: 5xxx, 7xxx) — חייב להיות disjoint מ-1xxx/8xxx/9xxx
|
||||||
|
- [ ] **שם בעברית** של הוועדה (לדוגמה: "ועדת ערר לתכנון ובניה צפון")
|
||||||
|
- [ ] **prefix לidentifiers** של issues (לדוגמה: `CMPN`)
|
||||||
|
- [ ] **`appeal_type` tag** — מחרוזת קצרה לניתוב (לדוגמה: `licensing_north`)
|
||||||
|
- [ ] **המודלים והעלויות** — האם זהה ל-CMP (Opus opus-4-6 ל-CEO+writer, Sonnet sonnet-4-6 לאחרים)?
|
||||||
|
- [ ] **גישה ל-Infisical** ל-`PAPERCLIP_BOARD_API_KEY` (`/paperclip` ב-nautilus env)
|
||||||
|
- [ ] **PostgreSQL access** ל-Paperclip DB (`localhost:54329`, user `paperclip`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 1 — יצירת `companies` row ב-Paperclip DB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO companies (
|
||||||
|
id, name, issue_prefix, status,
|
||||||
|
attachment_max_bytes,
|
||||||
|
require_board_approval_for_new_agents,
|
||||||
|
hire_approval_required
|
||||||
|
) VALUES (
|
||||||
|
gen_random_uuid(),
|
||||||
|
'ועדת ערר {שם}', -- בעברית
|
||||||
|
'CMPN', -- 4 תווים אנגלית, ייחודי
|
||||||
|
'active',
|
||||||
|
10485760, -- 10MB default
|
||||||
|
false, -- ברירת מחדל מ-2026.428.0 (Gap docs)
|
||||||
|
false
|
||||||
|
)
|
||||||
|
RETURNING id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**שמור את ה-UUID** — תצטרך אותו ב-כל השלבים הבאים. נקרא לו `$NEW_COMPANY_ID`.
|
||||||
|
|
||||||
|
⚠️ אל תיצור project ראשוני ידנית — Paperclip יוצר אוטומטית כשהחברה נשמרת.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 2 — יצירת 7 סוכנים
|
||||||
|
|
||||||
|
צור את הסוכנים בסדר הבא (ה-CEO ראשון, כי בכל הסוכנים `reports_to = CEO_id`):
|
||||||
|
|
||||||
|
| # | name (עברית) | role | model | budget_cents |
|
||||||
|
|---|---------------|------|-------|---------------|
|
||||||
|
| 1 | עוזר משפטי | `ceo` | claude-opus-4-6 | 1500 |
|
||||||
|
| 2 | מנתח משפטי | `researcher` | claude-opus-4-6 | 1500 |
|
||||||
|
| 3 | חוקר תקדימים | `researcher` | claude-sonnet-4-6 | 1500 |
|
||||||
|
| 4 | כותב החלטה | `engineer` | claude-opus-4-6 | 1500 |
|
||||||
|
| 5 | בודק איכות | `qa` | claude-sonnet-4-6 | 1500 |
|
||||||
|
| 6 | מייצא טיוטה | `engineer` | claude-sonnet-4-6 | 1500 |
|
||||||
|
| 7 | הגהת מסמכים | `engineer` | claude-opus-4-6 | 1500 |
|
||||||
|
|
||||||
|
### דרך 1 — sync from master (מומלץ)
|
||||||
|
|
||||||
|
הדרך הקלה ביותר: צור 7 סוכנים ב-CMPN עם **שמות זהים** ל-CMP, ואז הרץ `sync_agents_across_companies.py` שיעתיק את כל ההגדרות.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- לכל אחד מ-7 הסוכנים (שנה את name ו-role בכל פעם):
|
||||||
|
INSERT INTO agents (
|
||||||
|
id, company_id, name, role, adapter_type,
|
||||||
|
adapter_config, runtime_config, budget_monthly_cents,
|
||||||
|
permissions, status
|
||||||
|
) VALUES (
|
||||||
|
gen_random_uuid(),
|
||||||
|
'{NEW_COMPANY_ID}'::uuid,
|
||||||
|
'עוזר משפטי', -- שנה ב-7 שורות
|
||||||
|
'ceo', -- שנה לפי הטבלה למעלה
|
||||||
|
'claude_local',
|
||||||
|
'{}'::jsonb, -- ייטען בשלב 4
|
||||||
|
'{}'::jsonb, -- ייטען בשלב 4
|
||||||
|
1500,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'idle'
|
||||||
|
)
|
||||||
|
RETURNING id, name;
|
||||||
|
```
|
||||||
|
|
||||||
|
שמור את 7 ה-UUIDs לטבלה לעיון מהיר.
|
||||||
|
|
||||||
|
### עדכון `reports_to` (אחרי שיש לך CEO_id)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE agents
|
||||||
|
SET reports_to = '{CEO_id}'::uuid
|
||||||
|
WHERE company_id = '{NEW_COMPANY_ID}'::uuid
|
||||||
|
AND name <> 'עוזר משפטי';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 3 — סנכרון מ-CMP (master) דרך הסקריפט
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PAPERCLIP_BOARD_API_KEY=$(mcp__infisical__get-secret \
|
||||||
|
projectId=9a77b161-f70c-4dd3-9d67-b7ab850cef51 \
|
||||||
|
environmentSlug=nautilus secretPath=/ \
|
||||||
|
secretName=PAPERCLIP_BOARD_API_KEY) \
|
||||||
|
python ~/legal-ai/scripts/sync_agents_across_companies.py --verify
|
||||||
|
```
|
||||||
|
|
||||||
|
**אם הסקריפט לא תומך ב-mirror החדש**, יש לעדכן אותו:
|
||||||
|
1. פתח `scripts/sync_agents_across_companies.py`
|
||||||
|
2. השתמש בdict structure: master_company → list of mirrors. או הוסף flag `--target-company`
|
||||||
|
3. הרץ `--apply` להעתיק את כל ההגדרות מ-CMP ל-CMPN
|
||||||
|
|
||||||
|
**מה הסקריפט מסנכרן** (אוטומטית):
|
||||||
|
- `adapter_config`: model, effort, timeoutSec=3600, maxTurnsPerRun=500, instructionsBundleMode=external, instructionsRootPath/EntryFile, dangerouslySkipPermissions, extraArgs (`--agent legal-{role}`), cwd
|
||||||
|
- `runtime_config.heartbeat`: graceSec=60, cooldownSec=10, wakeOnDemand=true, maxConcurrentRuns (CEO=2, others=1)
|
||||||
|
- `budget_monthly_cents` (1500)
|
||||||
|
- `metadata`, `icon`, `title`
|
||||||
|
|
||||||
|
**מה לא מסונכרן** (חייב לעשות ידנית בהמשך):
|
||||||
|
- `paperclipSkillSync.desiredSkills` — ראה שלב 4
|
||||||
|
- `permissions` — לפי policy של החברה
|
||||||
|
- local skills (אם החברה החדשה צריכה custom skills)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 4 — Paperclip Skills
|
||||||
|
|
||||||
|
הסקריפט מ-שלב 3 כולל כבר את ה-`paperclipSkillSync.desiredSkills` (מסונן לפי skills זמינים ב-mirror). אבל ה-mirror החדש **לא יקבל local skills** של CMP אם הם לא קיימים גם בו.
|
||||||
|
|
||||||
|
### 4א. יצירת company_skills ל-CMPN
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- העתק את 6 ה-paperclip skills הסטנדרטיים מ-CMP ל-CMPN
|
||||||
|
INSERT INTO company_skills (company_id, key, slug, name, description, markdown, source_type, trust_level, compatibility, file_inventory)
|
||||||
|
SELECT
|
||||||
|
'{NEW_COMPANY_ID}'::uuid,
|
||||||
|
key, slug, name, description, markdown, source_type, trust_level, compatibility, file_inventory
|
||||||
|
FROM company_skills
|
||||||
|
WHERE company_id = '42a7acd0-30c5-4cbd-ac97-7424f65df294' -- CMP
|
||||||
|
AND key LIKE 'paperclipai/paperclip/%';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4ב. אם החברה צריכה local skills
|
||||||
|
|
||||||
|
החלט אילו local skills (`local/.../legal-decision`, `local/.../attach-precedents`) רלוונטיות — תלוי בסוג הערר.
|
||||||
|
לדוגמה, חברה ל-"היטלי השבחה צפון" כנראה לא תצריך `attach-precedents` של CMP אלא local skill משלה.
|
||||||
|
|
||||||
|
### 4ג. הפעלת skills/sync לכל סוכן
|
||||||
|
|
||||||
|
הרץ `scripts/sync_missing_agent_skills.py` עם adaptation לחברה החדשה (העתק את הקובץ ושנה את `CMPA_COMPANY_ID` ל-NEW_COMPANY_ID + רשימת ה-skills הרצויה).
|
||||||
|
|
||||||
|
⚠️ **חובה דרך API** (`POST /api/agents/{id}/skills/sync`) — לא דרך SQL ישיר! ה-API יוצר revision מסוג `skill-sync` שנדרש לlogging. SQL ישיר לא יוצר revision.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 5 — Symlinks ל-instructions (managed by Paperclip)
|
||||||
|
|
||||||
|
לכל סוכן Paperclip צופה לקבצי הוראות בנתיב:
|
||||||
|
`~/.paperclip/instances/default/companies/{COMPANY_ID}/agents/{AGENT_ID}/instructions/`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NEW_COMPANY_ID="..."
|
||||||
|
LEGAL_AI_AGENTS=/home/chaim/legal-ai/.claude/agents
|
||||||
|
|
||||||
|
for ROW in \
|
||||||
|
"ceo:legal-ceo.md" \
|
||||||
|
"analyst:legal-analyst.md" \
|
||||||
|
"researcher:legal-researcher.md" \
|
||||||
|
"writer:legal-writer.md" \
|
||||||
|
"qa:legal-qa.md" \
|
||||||
|
"exporter:legal-exporter.md" \
|
||||||
|
"proofreader:legal-proofreader.md"; do
|
||||||
|
ROLE="${ROW%%:*}"
|
||||||
|
FILE="${ROW##*:}"
|
||||||
|
AGENT_ID=$(PGPASSWORD=paperclip psql -h 127.0.0.1 -p 54329 -U paperclip -d paperclip -tAc \
|
||||||
|
"SELECT id FROM agents WHERE company_id='$NEW_COMPANY_ID'::uuid AND adapter_config->>'extraArgs' LIKE '%legal-$ROLE%' LIMIT 1")
|
||||||
|
DEST=~/.paperclip/instances/default/companies/$NEW_COMPANY_ID/agents/$AGENT_ID/instructions/
|
||||||
|
mkdir -p $DEST
|
||||||
|
ln -sf "$LEGAL_AI_AGENTS/$FILE" "$DEST/AGENTS.md"
|
||||||
|
ln -sf "$LEGAL_AI_AGENTS/HEARTBEAT.md" "$DEST/HEARTBEAT.md"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
**אימות:** `ls -la ~/.paperclip/instances/default/companies/$NEW_COMPANY_ID/agents/*/instructions/` — צריך לראות 14 symlinks (7 agents × 2 קבצים).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 6 — עדכון `web/paperclip_client.py`
|
||||||
|
|
||||||
|
הקובץ מכיל 3 dicts שצריכים את החברה החדשה:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# COMPANIES dict
|
||||||
|
COMPANIES = {
|
||||||
|
"licensing": "42a7acd0-30c5-4cbd-ac97-7424f65df294",
|
||||||
|
"betterment": "8639e837-4c9d-47fa-a76b-95788d651896",
|
||||||
|
"{appeal_type_new}": "{NEW_COMPANY_ID}", # ← חדש
|
||||||
|
}
|
||||||
|
|
||||||
|
# CEO_AGENTS dict — נדרש ל-wakeup routing
|
||||||
|
CEO_AGENTS = {
|
||||||
|
COMPANIES["licensing"]: "752cebdd-...",
|
||||||
|
COMPANIES["betterment"]: "cdbfa8bc-...",
|
||||||
|
COMPANIES["{appeal_type_new}"]: "{CEO_ID_NEW}", # ← חדש
|
||||||
|
}
|
||||||
|
|
||||||
|
# _FALLBACK_APPEAL_TYPE_TO_COMPANY — ניתוב לפי tag עברי/אנגלי
|
||||||
|
_FALLBACK_APPEAL_TYPE_TO_COMPANY = {
|
||||||
|
# קיימים...
|
||||||
|
"{שם בעברית}": COMPANIES["{appeal_type_new}"],
|
||||||
|
"{english_tag}": COMPANIES["{appeal_type_new}"],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ אחרי השינוי — **deploy** ל-Coolify (FastAPI container חי במכולה — שינוי קוד דורש rebuild). ראה `legal-ai/CLAUDE.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 7 — `tag_company_mappings` ב-legal-ai DB
|
||||||
|
|
||||||
|
ה-FastAPI ראשית מנסה לקרוא ניתוב מ-DB, רק אז fallback ל-dict הקבוע (שלב 6). הוסף mapping:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ב-legal-ai DB (port 5433)
|
||||||
|
INSERT INTO tag_company_mappings (tag, company_id) VALUES
|
||||||
|
('{שם עברי}', '{NEW_COMPANY_ID}'),
|
||||||
|
('{english_tag}', '{NEW_COMPANY_ID}');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 8 — עדכון `HEARTBEAT.md` §1
|
||||||
|
|
||||||
|
הסעיף §1 מכיל טבלה של חברות + CEO IDs. הוסף שורה חדשה:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| ועדת ערר {שם} (CMPN) | `{NEW_COMPANY_ID}` | {סוג} | **{Nxxx}** | `{CEO_ID_NEW}` |
|
||||||
|
```
|
||||||
|
|
||||||
|
ובסעיף §4ג (CEO wakeup), עדכן את ה-`if` להוסיף אופציה שלישית לחברה החדשה.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 9 — עדכון `legal-ai/CLAUDE.md`
|
||||||
|
|
||||||
|
הקובץ מכיל את אותה טבלה. עדכן בקטעים:
|
||||||
|
- "סוגי עררים" (אם קיים)
|
||||||
|
- "Paperclip — כללי אינטגרציה קריטיים" → "ניתוב comments דרך CEO"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 10 — Hebrew translation (אם נדרש)
|
||||||
|
|
||||||
|
אם שם החברה מופיע ב-UI, ייתכן שצריך תרגום ב-`~/.paperclip/hebrew/translate-he.js`. בד"כ לא נדרש — שמות בעברית כבר.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# אחרי שינויים בHebrew file:
|
||||||
|
~/.paperclip/hebrew/apply-hebrew.sh
|
||||||
|
# ⚠️ לא דורש pm2 restart — UI client-side fix.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## שלב 11 — בדיקה end-to-end
|
||||||
|
|
||||||
|
1. **CEO מתעורר על comment**: צור issue test בחברה החדשה, פרסם comment, ודא ש-CEO רץ.
|
||||||
|
2. **plugin marcusgroup.legal-ai רואה את החברה**: ב-Paperclip UI → Settings → Plugins → marcusgroup.legal-ai → ודא שהחברה החדשה ב-installed companies.
|
||||||
|
3. **MCP tools פועלים**: דרך Claude Code, הרץ `mcp__legal-ai__case_create` עם appeal_type של החברה החדשה.
|
||||||
|
4. **Sync script עובד**: `python scripts/sync_agents_across_companies.py --verify` — לא צריך drift.
|
||||||
|
5. **Budget enforcement**: צור cost_event מבחן, ודא ש-spent_monthly_cents מתעדכן.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ מלכודות מתועדות (מ-Gap analysis 2026-04 → 2026-05)
|
||||||
|
|
||||||
|
מבחן בכל שלב מאפשר תפיסת issues שתועדו בעבר:
|
||||||
|
|
||||||
|
| # | מלכודת | פתרון |
|
||||||
|
|---|---------|--------|
|
||||||
|
| 1 | סוכנים בלי `paperclipSkillSync` | ראה שלב 4ג (POST /api/agents/{id}/skills/sync, לא SQL) |
|
||||||
|
| 2 | `runtime_config = '{}'` (default → graceSec=1ms!) | ראה שלב 3 (סקריפט מסנכרן `heartbeat.graceSec=60`) |
|
||||||
|
| 3 | `budget_monthly_cents = 0` | ראה שלב 2 (insert עם 1500) |
|
||||||
|
| 4 | `instructionsBundleMode` חסר | ראה שלב 3 (סקריפט מסנכרן `external` + Root + EntryFile) |
|
||||||
|
| 5 | `bootstrapPromptTemplate` deprecated | אין אצלנו — דלג |
|
||||||
|
| 6 | drift בין חברות | ראה שלב 3 — סנכרון אוטומטי כל שינוי הגדרות |
|
||||||
|
| 7 | CEO לא מתעורר על comment | ודא ש-`reports_to` עודכן ושיש symlinks ל-AGENTS.md (שלב 5) |
|
||||||
|
| 8 | `psql` ישיר ל-`issue_attachments` | אסור — ראה `HEARTBEAT.md §2` (heartbeat-context API) |
|
||||||
|
| 9 | curl ישיר ל-Paperclip API | אסור — תמיד `pc.sh` (`HEARTBEAT.md §0`) |
|
||||||
|
| 10 | "@chaim — ענה 1/2/3 בcomment" | אסור — interactions API (`legal-ceo.md §B/§C/§D`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## רפרנסים
|
||||||
|
|
||||||
|
- [`docs/new-company-setup-guide.md`](../../docs/new-company-setup-guide.md) — היסטוריית הקמת CMPA (חברה שנייה, 2026-04)
|
||||||
|
- [`scripts/sync_agents_across_companies.py`](../../scripts/sync_agents_across_companies.py) — אוטומציה לסנכרון
|
||||||
|
- [`scripts/sync_missing_agent_skills.py`](../../scripts/sync_missing_agent_skills.py) — תבנית להפעלת skills/sync
|
||||||
|
- [`~/.paperclip/CUSTOMIZATIONS.md`](../../../.paperclip/CUSTOMIZATIONS.md) — כל ההתאמות הפעילות (סעיפים: agents runtime, instructions, budgets, interactions, skill-sync)
|
||||||
|
- [`HEARTBEAT.md`](../../.claude/agents/HEARTBEAT.md) — §1 טבלת חברות (לעדכן בשלב 8)
|
||||||
|
- [`legal-ai/CLAUDE.md`](../../CLAUDE.md) — Paperclip integration rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## גרסה
|
||||||
|
- 2026-05-04 — גרסה ראשונה (אחרי Gap #16-#28)
|
||||||
Reference in New Issue
Block a user