docs+heartbeat: paperclip quirks + temp-file pattern + self-recovery
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
Two latent issues surfaced today while watching the case 8174-24
end-to-end run, both worth documenting and engineering around because
they will recur on every future case.
Bug 1 — issue.released flips done→todo
After an agent successfully PATCHes its issue to "done", Paperclip's
internal issue.released action reverts the status to "todo" within
~30 seconds. This triggers a fresh wakeup of the same agent on a
task that is already complete.
Reproduced on CMPA-18 (30/04/26):
18:14:57 agent PATCH → status: done
18:15:35 Paperclip → issue.released → status: todo
18:15:54 new researcher run started
The fix at the right altitude (Paperclip itself) is outside our repo.
Mitigation in HEARTBEAT.md §3 — when an agent boots and finds the
issue in `todo` while expected outputs (file, DB rows) already exist,
it must short-circuit: post a "no change" comment, PATCH back to done,
and exit. Costs ~$0.20 per false wakeup but breaks the loop.
Bug 2 — Bash backtick trap on long comment bodies
Researcher agent built a curl pipeline like:
curl ... -d "$(python3 -c "body = '''...
📁 קובץ מחקר: `/path/to/file.md`
'''")"
The backticks around the file path (markdown convention) get
evaluated by the OUTER bash $(...) as command substitution. Bash
then tries to exec /path/to/file.md, which is not executable, and
prints "Permission denied" — a misleading error since the actual
file ownership is fine. The curl itself succeeded; only the bash
prelude noised up the log.
Fix in HEARTBEAT.md §4א: long bodies must go via Write→tempfile
then `curl -d @file`. Avoids every shell quoting edge case.
Files:
• docs/paperclip-quirks.md — new. Full writeup of both bugs plus
two prior known-quirks (CEO auto-block in_progress, INSERT vs
API for wakeups). Each section: what happens, empirical evidence
from logs, impact, workaround, status.
• .claude/agents/HEARTBEAT.md — added the self-recovery section to
§3 and the temp-file pattern to §4א. The temp-file pattern is the
canonical answer for any agent posting markdown comments —
applies to all 7 agents in this skill set.
• CLAUDE.md — referenced the new doc from the docs index.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,11 +85,30 @@ curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|||||||
- עבוד על המשימה לפי ההוראות ב-AGENTS.md שלך
|
- עבוד על המשימה לפי ההוראות ב-AGENTS.md שלך
|
||||||
- השתמש בכלים המשפטיים (legal-ai MCP)
|
- השתמש בכלים המשפטיים (legal-ai MCP)
|
||||||
|
|
||||||
|
### ⚠️ self-recovery — issue ב-`todo` עם תוצרים קיימים
|
||||||
|
|
||||||
|
ל-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. דיווח — חובה!
|
||||||
|
|
||||||
**לפני שאתה מסיים, תמיד:**
|
**לפני שאתה מסיים, תמיד:**
|
||||||
|
|
||||||
### 4א. פרסם comment על ה-issue
|
### 4א. פרסם comment על ה-issue
|
||||||
|
|
||||||
|
**ל-body קצר (<500 תווים, בלי backticks/קוד/נתיבים):**
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -97,6 +116,24 @@ curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
|||||||
-d '{"body": "סיכום העבודה..."}'
|
-d '{"body": "סיכום העבודה..."}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**ל-body ארוך / markdown עם נתיבים בbacktick / קוד — חובה שתי פעולות נפרדות:**
|
||||||
|
|
||||||
|
1. כתוב את ה-JSON לקובץ זמני דרך **Write tool** (לא דרך bash heredoc):
|
||||||
|
```
|
||||||
|
Write(file_path="/tmp/comment-{issue-id}.json",
|
||||||
|
content=json.dumps({"body": markdown_body}, ensure_ascii=False))
|
||||||
|
```
|
||||||
|
|
||||||
|
2. אז `curl -d @file` שקורא את הקובץ ישירות — בלי shell expansion:
|
||||||
|
```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 @/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`.
|
||||||
|
|
||||||
### 4ב. קבע סטטוס — done או blocked
|
### 4ב. קבע סטטוס — done או blocked
|
||||||
|
|
||||||
**אם המשימה הושלמה בהצלחה** (כל המסמכים חולצו, כל הבדיקות עברו, אין חסימות):
|
**אם המשימה הושלמה בהצלחה** (כל המסמכים חולצו, כל הבדיקות עברו, אין חסימות):
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
| [`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 שלם של תיק (מבחן, מחיקה בטעות) |
|
||||||
|
| [`docs/paperclip-quirks.md`](docs/paperclip-quirks.md) | מלכודות ידועות ב-Paperclip — `issue.released` ש-flips done→todo, bash backtick trap, CEO auto-block, wakeup דרך DB | לפני שמייחסים באג בסוכן ל-skill — לבדוק קודם אם זה Paperclip-side |
|
||||||
| [`docs/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה |
|
| [`docs/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה |
|
||||||
| [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית |
|
| [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית |
|
||||||
| [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** |
|
| [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** |
|
||||||
|
|||||||
157
docs/paperclip-quirks.md
Normal file
157
docs/paperclip-quirks.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Paperclip Quirks — מלכודות ידועות
|
||||||
|
|
||||||
|
> **הקשר:** מה ש-Paperclip עושה בעצמו, מתחת לרגליהם של הסוכנים שלנו, ושאנחנו צריכים לעקוף אותו או לחיות איתו.
|
||||||
|
>
|
||||||
|
> כל מלכודת מתועדת עם:
|
||||||
|
> 1. מה קורה בפועל
|
||||||
|
> 2. ראיה אמפירית מתוך לוגים
|
||||||
|
> 3. ההשפעה על הצינור שלנו
|
||||||
|
> 4. עקיפה / תיקון / קבלה
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. `issue.released` הופך `done` ל-`todo`
|
||||||
|
|
||||||
|
### מה קורה
|
||||||
|
|
||||||
|
לאחר שסוכן מבצע `PATCH /api/issues/{id}` עם `status: done`, **Paperclip מבצע פעולה נוספת בשם `issue.released`** מספר שניות מאוחר יותר. ל-`issue.released` יש side-effect לא-מתועד שמחזיר את ה-status ל-`todo`.
|
||||||
|
|
||||||
|
### ראיה אמפירית — תיק 8174-24, CMPA-18 (30/04/26)
|
||||||
|
|
||||||
|
מתוך `activity_log`:
|
||||||
|
|
||||||
|
```
|
||||||
|
ts | action | actor_type | details
|
||||||
|
----------+---------------------+------------+----------------------------------------
|
||||||
|
18:14:49 | issue.comment_added | agent | comment by researcher
|
||||||
|
18:14:57 | issue.updated | agent | {"status": "done", "_previous": {"status": "in_progress"}}
|
||||||
|
18:15:35 | issue.released | agent | ← here
|
||||||
|
```
|
||||||
|
|
||||||
|
מצב מ-`issues` table 38 שניות לאחר ה-`released`:
|
||||||
|
```
|
||||||
|
identifier | status | updated_at
|
||||||
|
CMPA-18 | todo | 18:15:35
|
||||||
|
```
|
||||||
|
|
||||||
|
ה-status חזר מ-`done` ל-`todo` למרות שאף סוכן או משתמש לא ביקש זאת.
|
||||||
|
|
||||||
|
### ההשפעה על הצינור שלנו
|
||||||
|
|
||||||
|
Paperclip מזהה issue ב-`todo` כ"יש עבודה לעשות" → מיד מפעיל wakeup לסוכן הרלוונטי → הסוכן רץ שוב עם prompt cache מלא (~$0.10-0.50 פר-ריצה) → מסתכל סביב ומבין שהעבודה כבר נעשתה → סוגר את ה-issue שוב → `issue.released` חוזר על עצמו ⇒ פוטנציאל ללולאה.
|
||||||
|
|
||||||
|
### עקיפה — בצד שלנו (ללא תיקון Paperclip)
|
||||||
|
|
||||||
|
הסוכן שלנו **עושה זאת כבר היום בהצלחה** במקרה שהוא רואה issue ב-`todo` עם תוצרים קיימים:
|
||||||
|
|
||||||
|
1. בודק שהקבצים הצפויים קיימים (`Glob /documents/research/*.md`)
|
||||||
|
2. בודק שה-DB מאוכלס (`mcp__legal-ai__precedent_list`, `get_claims`, וכו')
|
||||||
|
3. אם הכל קיים → לא מבצע עבודה כפולה → כותב comment "אין שינוי" → `PATCH issue → done`
|
||||||
|
|
||||||
|
**הראיה:** בריצה החוזרת (PID 309786 ב-30/04/26 18:15:54), המנתח של החוקר זיהה תוך 90 שניות שכל 9 התקדימים והקובץ קיימים, וסגר את ה-issue ב-`PATCH → done` שוב. הריצה הזאת עלתה כ-$0.20 — לא חינם, אבל לא לולאה.
|
||||||
|
|
||||||
|
### אם תרצה לחקור פנימה
|
||||||
|
|
||||||
|
ה-`issue.released` נרשם ב-`activity_log` עם `actor_type=agent` אבל בלי `agent_id` שמסביר מי. הוא לא נכתב על ידי הסקריפטים שלנו (אנחנו לא קוראים endpoint כזה). מקור אפשרי:
|
||||||
|
- מנגנון `executionLockedAt` / `executionWorkspaceId` של Paperclip שמשחרר משאבים אחרי שריצה מסתיימת ובמקביל מאפס status
|
||||||
|
|
||||||
|
האפשרות הנכונה לסגור את הבאג היא **ב-Paperclip עצמו** — לתקן את `issue.released` שלא ידרוס status מסוף-מצב כמו `done`. עד שזה נסגר אצלם, אנחנו חיים עם self-recovery.
|
||||||
|
|
||||||
|
### סטטוס
|
||||||
|
|
||||||
|
- **לא נסגר ב-Paperclip** (ידוע לפי 30/04/26)
|
||||||
|
- **טופל בצד שלנו** דרך self-recovery בסקייל של הסוכן (HEARTBEAT.md §4-recovery)
|
||||||
|
- **לתעד עלות**: כל ריצת self-recovery מוסיפה ~$0.20 לתיק
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Bash backtick trap בעת בניית comment body דרך curl
|
||||||
|
|
||||||
|
### מה קורה
|
||||||
|
|
||||||
|
הסוכן בונה pipeline מורכב כדי לפרסם comment עם markdown ארוך:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl ... -d "$(python3 -c "
|
||||||
|
body = '''## כותרת
|
||||||
|
📁 קובץ: \`/path/to/file.md\`
|
||||||
|
'''
|
||||||
|
print(json.dumps({'body': body}))")"
|
||||||
|
```
|
||||||
|
|
||||||
|
ה-`bash` שמריץ את ה-`$(...)` הראשון רואה את ה-backticks (` ` ` ) בתוך המחרוזת של Python ומפרש אותם **כ-command substitution של bash**. הוא מנסה להריץ את `/path/to/file.md` כפקודה, ומכיוון שהקובץ לא executable — מחזיר:
|
||||||
|
|
||||||
|
```
|
||||||
|
/bin/bash: line 56: /path/to/file.md: Permission denied
|
||||||
|
```
|
||||||
|
|
||||||
|
### ההטעיה
|
||||||
|
|
||||||
|
ההודעה `Permission denied` היא **לא** באמת בעיית הרשאות:
|
||||||
|
- `ls -la` מראה שהקובץ הוא `chaim:chaim` עם `-rw-r--r--`
|
||||||
|
- `touch` ידני באותו נתיב מצליח
|
||||||
|
- ה-Write tool כבר כתב את הקובץ הזה בהצלחה דקה קודם
|
||||||
|
|
||||||
|
### למה זה קורה דווקא בנתיבי מסמכים
|
||||||
|
|
||||||
|
Backticks הם תחביר markdown נפוץ לציטוט נתיבים: `` `/home/chaim/...` ``. בפלט markdown זה נכון, אבל כשהסוכן מטמיע את ה-markdown בתוך bash heredoc / command substitution, ה-backticks מפעילים את עצמם.
|
||||||
|
|
||||||
|
### תיקון — דפוס "כתוב לקובץ זמני אז curl -d @file"
|
||||||
|
|
||||||
|
במקום:
|
||||||
|
```bash
|
||||||
|
curl ... -d "$(python3 -c "...long body with backticks...")"
|
||||||
|
```
|
||||||
|
|
||||||
|
עשה:
|
||||||
|
```python
|
||||||
|
# 1. כתוב את ה-body לקובץ זמני דרך Write tool (בלי שום bash quoting)
|
||||||
|
Write("/tmp/comment.json", json.dumps({"body": markdown_body}))
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
# 2. אז curl קורא מהקובץ — אין shell expansion על התוכן
|
||||||
|
curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \
|
||||||
|
-d @/tmp/comment.json
|
||||||
|
```
|
||||||
|
|
||||||
|
הנתיב `-d @file` קורא את התוכן של הקובץ **בלי שום ניתוח** — אין shell, אין quoting, אין backticks-as-commands. זה גם מאפשר body של 10K+ תווים ללא הגבלת ARG_MAX.
|
||||||
|
|
||||||
|
### סטטוס
|
||||||
|
|
||||||
|
- **תיעוד ב-HEARTBEAT.md** עם הוראה מפורשת להשתמש ב-Write+`-d @file` ל-bodies מעל 500 תווים
|
||||||
|
- **השפעה היסטורית**: לפני התיקון, הריצה ב-CMPA-18 (30/04/26) הצליחה (curl באמת רץ) — אבל ה-`Permission denied` בלוג היה מבלבל וגרם לחקירה. עתה שהסיבה ידועה, אפשר להתעלם.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. CEO main issue auto-block ב-`in_progress`
|
||||||
|
|
||||||
|
### מה קורה
|
||||||
|
|
||||||
|
CEO שמסיים turn (פרסם comment "ממתין לסיום של סוכן Y") ומשאיר את ה-issue ב-`in_progress` יקבל auto-block תוך דקה אחת מ-Paperclip ("live execution disappeared"). הסטטוס יקפוץ ל-`blocked` ויידרש wakeup ידני להמשיך.
|
||||||
|
|
||||||
|
### עקיפה
|
||||||
|
|
||||||
|
CEO צריך להעביר את ה-issue ל-`in_review` (לא `in_progress`) כשהוא ממתין למשאב חיצוני (סוכן אחר, יו"ר). זה מתועד ב-CLAUDE.md זיכרון: `feedback_paperclip_enums.md`.
|
||||||
|
|
||||||
|
### סטטוס
|
||||||
|
|
||||||
|
- **תיקון ב-`legal-ceo.md`** (commit a1969dd)
|
||||||
|
- נצפה עובד ב-CMPA-15 ב-30/04/26 — ה-CEO עבר ל-`in_review` נכון
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Wakeup דרך DB ישיר ≠ wakeup דרך API
|
||||||
|
|
||||||
|
### מה קורה
|
||||||
|
|
||||||
|
`INSERT INTO agent_wakeup_requests` ידני בלי לעבור דרך `POST /api/agents/{id}/wakeup` יוצר רשומת wakeup אבל **לא יוצר `heartbeat_run`**. בלי `heartbeat_run`, ה-runtime של Paperclip לא מזהה שיש משהו להריץ → הסוכן לעולם לא מתעורר.
|
||||||
|
|
||||||
|
### עקיפה
|
||||||
|
|
||||||
|
תמיד להשתמש ב-API. כל הסקייל שלנו תועדו עם האזהרה הזאת.
|
||||||
|
|
||||||
|
### סטטוס
|
||||||
|
|
||||||
|
- **תיקון בכל הסקייל** (CLAUDE.md זיכרון: `reference_paperclip_wakeup.md`)
|
||||||
Reference in New Issue
Block a user