# Operations Runbook — עוזר משפטי > תוכן תפעולי-עומק שהוצא מ-[`CLAUDE.md`](../CLAUDE.md) כדי לרזות את ההקשר הנטען בכל סשן (TaskMaster #107.1). > ה-CLAUDE.md מחזיק את **הכללים הקריטיים בקצרה**; כאן נמצאים הפרטים המלאים, הפקודות, וטבלאות-הייחוס. > כשעובדים על Deploy, Paperplip-ops, או adapters — לקרוא את הסעיף הרלוונטי כאן. --- ## שרת Nautilus (158.178.131.193) | שירות | תפקיד | כתובת | |-------|--------|-------| | Coolify | ניהול containers | `http://158.178.131.193:8000` | | PostgreSQL + pgvector | בסיס נתונים ראשי | `legal-ai-postgres` (`localhost:5433`, user `legal_ai`) | | Redis | תור משימות | `legal-ai-redis` | | Gitea | מאגר קוד | `gitea.nautilus.marcusgroup.org/ezer-mishpati` | | ezer-mishpati-web | ממשק העלאת מסמכים (Docker/Coolify) | `legal-ai.nautilus.marcusgroup.org` | | Paperclip | סוכן AI — מריץ Claude Code agents (pm2, מקומי) | `localhost:3100` | | legal-chat-service | גשר claude CLI לטאב הצ'אט ב-/training (pm2, loopback) | `127.0.0.1:8770` | | Infisical | ניהול סודות — פרויקט **All Infrastructure** (`2c462576-b125-4279-b0ec-7220dbf51ccf`), env `main`. סודות המערכת מפוזרים על שלוש תיקיות-אחיות: `/apps/legal-ai` · `/apps/paperclip` · `/apps/paperclip-hermes` | `secret.marcus-law.co.il` | --- ## ארכיטקטורת Deploy — חובה לקרוא שלושה מודלי-הרצה דרים יחד. ערבוב ביניהם הוא הטעות הנפוצה ביותר. ### עוזר משפטי (Legal-AI) — Docker container דרך Coolify - UUID: `gyjo0mtw2c42ej3xxvbz8zio` (build_pack: `dockerimage`, **לא** `dockerfile`) - שינוי קוד ב-`web/` או `web-ui/` **לא נכנס לתוקף** עד ש: 1. עושים `git commit` + `git push origin main` 2. Gitea Actions בונה image → דוחף ל-registry → מפעיל redeploy ב-Coolify (`mcp__coolify__deploy`) 3. ממתינים ~2-4 דקות לבנייה - **אסור** לנסות להריץ uvicorn / `next dev` מקומית — אין סביבת Python על המכונה - ה-container מריץ Next.js (`:3000`, חשוף) + FastAPI (`:8000`, פנימי) - בדיקה: `curl https://legal-ai.nautilus.marcusgroup.org/api/health` - runbook מלא של ה-pipeline: `~/CI-CD-MIGRATION-GUIDE.md` ### Paperclip — מקומית דרך pm2 - פורט: `localhost:3100`, DB: `localhost:54329` (Postgres embedded) - שינויי קוד נכנסים לתוקף אחרי `pm2 restart paperclip` - **אין צורך ב-Docker או Coolify** (מיגרציה ל-Coolify נוסתה 2026-04-04 והוחזרה 2026-04-08) - תרגום/RTL: `~/.paperclip/hebrew/` → `bash ~/.paperclip/hebrew/apply-hebrew.sh && pm2 restart paperclip` ### legal-chat-service — מקומית דרך pm2 (מאפריל 2026) - פורט: `127.0.0.1:8770` (loopback בלבד) - שירות aiohttp קצר שעוטף את `claude` CLI ב-streaming + session continuation, ומשרת את הטאב "שיחה" בדף `/training`. הקונטיינר משדל אליו proxy דרך `host.docker.internal:8770`. - קוד: [`mcp-server/src/legal_mcp/chat_service/`](../mcp-server/src/legal_mcp/chat_service/) - התקנה: `pm2 start /home/chaim/legal-ai/scripts/legal-chat-service.config.cjs && pm2 save` - בריאות: `curl http://127.0.0.1:8770/health` → `{"ok":true,...}` - שינויי קוד: `pm2 restart legal-chat-service` - **אפס עלות API** — claude CLI משתמש ב-claude.ai subscription של chaim. הנחת היסוד של `claude_session.py` (claude CLI מקומי בלבד) נשמרת. - Coolify dependency: ה-Service Definition של legal-ai חייב להכיל `extra_hosts: host.docker.internal:host-gateway` (אחרת ה-proxy יקבל ConnectError). ### legal-mcp-http — שרת ה-MCP ב-HTTP (מאוגוסט 2026) - פורט: `127.0.0.1:8790` (loopback בלבד) · קונפיג: [`scripts/legal-mcp-http.config.cjs`](../scripts/legal-mcp-http.config.cjs) - **הדלת של סוכני הפלטפורמה** אל 108 כלי ה-MCP. סשן אינטראקטיבי מגיע לאותו שרת ב-stdio דרך `~/.claude.json` — **אותו קוד, שתי תחבורות** (G2). - שער Bearer (`MCP_HTTP_SHARED_SECRET`); השרת **מסרב לעלות** בלי טוקן. בדיקה: ללא טוקן → `401`, עם טוקן → `200`. - **⚠️ שינוי קוד ב-`mcp-server/` לא נכנס לתוקף עד `pm2 restart legal-mcp-http`.** זה תהליך ארוך-חיים, בניגוד ל-stdio שנטען מחדש בכל סשן. אחרי מיזוג שנוגע ב-`mcp-server/` — להפעיל מחדש, אחרת הסוכנים ירוצו על קוד ישן בשקט. > #### 🚨 דריפט תצורה פתוח — `STORAGE_BACKEND` > הקונטיינר רץ `STORAGE_BACKEND=s3`; שרת ה-MCP נופל לברירת-המחדל `filesystem`, כי > `~/.env` אינו מגדיר אותו. כלומר **סוכן שכותב קובץ דרך MCP כותב לדיסק, בעוד ה-web > קורא מ-S3** — שני מסלולים שמתפצלים (G2 / INV-STG1). > > **למה זה עדיין לא תוקן:** ההעברה ל-s3 דורשת `MINIO_ENDPOINT` + `MINIO_ACCESS_KEY` + > `MINIO_SECRET_KEY`, ואלה **אינם קיימים ב-Infisical** — רק ב-env של Coolify. הפיכת > הדגל בלעדיהם תפתור את `MINIO_ENDPOINT` לברירת-המחדל `http://minio:9000` (שם-רשת > פנימי של דוקר, לא נגיש מהמארח) ותשבור כל פעולת-בלוב. > > **מה שצריך כדי לסגור:** ליצור את שלושת המפתחות ב-Infisical תחת `/apps/legal-ai` > (תגית `credentials`, באישור חיים — ראה כלל "Saving a NEW secret" ב-`~/CLAUDE.md`), > לטעון אותם בקונפיג ה-pm2 כמו `MCP_HTTP_SHARED_SECRET`, ואז להוסיף > `STORAGE_BACKEND: "s3"`. MinIO **כן** נגיש מהמארח: `https://s3.nautilus.marcusgroup.org` > (אומת — `/minio/health/live` מחזיר 200). --- ## מבנה תיקיות ``` /home/chaim/legal-ai/ ├── CLAUDE.md ← אינדקס דק (כללים קריטיים + מצביעים) ├── docs/operations-runbook.md ← הקובץ הזה (עומק תפעולי) ├── Dockerfile ← Docker build ├── docs/ ← תיעוד + לקחים │ ├── architecture.md ארכיטקטורה │ ├── block-schema.md 12 בלוקים (המסמך החשוב ביותר) │ ├── migration-plan.md תוכנית מעבר vault → DB │ ├── legal-decision-lessons.md לקחים מ-3 החלטות │ └── memory.md הקשר כללי — skills, פרויקטים ├── skills/ ← כלי עבודה ומדריכים │ ├── decision/ מדריך סגנון + references + 12 בלוקים │ ├── assistant/ קטלוג מסמכים │ ├── docx/ עיצוב DOCX │ ├── dafna-decision-template/ export DOCX לפי תבנית Word של דפנה │ └── new-company-setup/ blueprint הוספת חברה חדשה ├── .claude/ │ └── agents/ ← הוראות סוכנים + HEARTBEAT.md (symlinks ב-Paperclip) │ ├── HEARTBEAT.md checklist הפעלה משותף לכל הסוכנים │ ├── legal-ceo.md תזמורן + בקרת זרימה │ ├── legal-writer.md כתיבת בלוקים בסגנון דפנה │ ├── legal-analyst.md ניתוח משפטי + חילוץ טענות │ ├── legal-researcher.md חיפוש תקדימים │ ├── legal-qa.md 7 שערי איכות │ ├── legal-proofreader.md תיקון OCR │ ├── legal-exporter.md ייצוא DOCX סופי │ └── hermes-curator.md סוכן Hermes לניתוח סגנון post-export ├── data/ │ ├── training/ ← 4 החלטות לאימון (DOCX) │ ├── exports/ ← טיוטות DOCX מיוצאות │ └── cases/{case-number}/ ← תיקי עררים (מבנה שטוח, סטטוס ב-DB) ├── web/ ← FastAPI backend (Python): 75+ API endpoints │ ├── app.py ← API ראשי │ ├── paperclip_api.py ← אינטגרציית Paperclip: `pc_request()` + `emit_case_status_webhook()` │ ├── paperclip_client.py ← legacy client (ישן — השתמש ב-paperclip_api.py) │ └── gitea_client.py ← אינטגרציית Gitea ├── web-ui/ ← Next.js frontend (TypeScript/React): ממשק המשתמש │ └── next.config.ts ← proxy: /api/* → FastAPI :8000 ├── mcp-server/ ← MCP server + services + tools ├── adapters/ ← Paperclip external adapters │ └── deepseek-paperclip-adapter/ ← `deepseek_local` (Hermes-pinned ל-DeepSeek profile) └── scripts/ ← סקריפטים וכלי עזר (ראה scripts/SCRIPTS.md) └── .archive/ ← סקריפטים שהושלמו (לא להריץ) ``` --- ## Paperclip — כללי אינטגרציה (פירוט מלא) > הכללים הקריטיים בתמצית נמצאים ב-[`CLAUDE.md`](../CLAUDE.md). כאן הפירוט המלא, הדוגמאות, וה-"למה". ### Wakeup API — תמיד דרך API, לעולם לא דרך DB - **הנתיב הנכון**: `POST /api/agents/{agent-id}/wakeup` (לא `/wake`!) - **⚠️ אסור**: `INSERT INTO agent_wakeup_requests` ישירות — זה יוצר רק רשומה בלי `heartbeat_run`, והסוכן **לא יתעורר לעולם** - **⚠️ חובה לשלוח `payload` עם `issueId`** — בלי זה הסוכן מתעורר בלי הקשר (בלי תיק, בלי issue, בלי cwd נכון) - דוגמה נכונה: ```json {"source": "automation", "triggerDetail": "system", "reason": "...", "payload": {"issueId": "...", "mutation": "comment", "commentId": "..."}} ``` - **Board API Key**: שמור ב-DB (`board_api_keys`), auth: `Authorization: Bearer pbk_...` ### ניתוב comments דרך CEO - כשמשתמש כותב תגובה על issue ב-Paperclip, הפלאגין (`plugin-legal-ai`) מעיר את ה-CEO דרך `ctx.agents.invoke()` - ה-CEO קורא את ה-comment, מחליט על ניתוב, ויוצר issue לסוכן המתאים - כל הסוכנים חייבים לקרוא comments אחרונים לפני שהם מתחילים לעבוד (HEARTBEAT שלבים 2b-2c) ### קריאות API — תמיד דרך helper, לעולם לא `curl` ישיר - **bash (סוכנים):** `~/legal-ai/scripts/pc.sh [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`. ### Webhook יוצא — עדכון סטטוס תיק לפלאגין כשסטטוס תיק משתנה דרך `PUT /api/cases/{case_number}`, הבקאנד שולח webhook אסינכרוני לפלאגין: ``` PUT /api/cases/{case_number} → emit_case_status_webhook() [BackgroundTask] → POST /api/plugins/marcusgroup.legal-ai/webhooks/case-status → plugin-legal-ai/onWebhook() → comment בעברית על issue + CEO wakeup (כשסטטוס = qa_failed) ``` - הקוד ב-`web/paperclip_api.py` (`emit_case_status_webhook`), fire-and-forget, timeout 5s - הפלאגין שומר idempotency key ב-state עם TTL 5 דקות למניעת spam על retry - `GET /api/cases/stale?days=N` — תיקים שלא עודכנו N ימים; מוחרגים: `new`, `final`, `exported` - `GET /api/chair-feedback/weekly-summary` — סיכום פידבק YU"R לשבוע האחרון ### Scheduled Jobs (plugin-legal-ai) | Job | לוח זמנים | מה עושה | |-----|-----------|---------| | `stale-case-reminder` | יומי 08:00 | שולח comment אזהרה על תיקים תקועים >3 ימים | | `weekly-feedback-analysis` | ראשון 19:00 | מעיר CEO לניתוח פידבק YU"R ועדכון `docs/legal-decision-lessons.md` | | `sync-case-status` | כל 30 דק' | מסנכרן סטטוסי תיקים בין legal-ai ל-Paperclip | CEO שמתעורר מ-`weekly-feedback-job` כותב לקובץ בלבד — **אין לו issueId, אל תנסה לפרסם comment או לסגור issue**. ### External adapters — `deepseek_local` - מיקום ה-package: [`adapters/deepseek-paperclip-adapter/`](../adapters/deepseek-paperclip-adapter/) (לא ב-`node_modules`). - רישום ב-Paperclip: רשומה ב-`~/.paperclip/adapter-plugins.json` (נטען אוטומטית ב-startup דרך `buildExternalAdapters`). אין צורך בעריכת `node_modules`. - **מה ה-adapter עושה**: spawnל-`hermes chat` עם `HERMES_HOME=/home/chaim/.hermes/profiles/deepseek` כך שה-CLI טוען את `config.yaml` (`base_url=https://api.deepseek.com/v1`, `provider=custom`, `key_env=DEEPSEEK_API_KEY`) ואת `.env` (שמכיל את ה-key). - **מודלים זמינים** (lookup ב-DeepSeek `/v1/models`): `deepseek-v4-pro` (default), `deepseek-v4-flash`. יופיעו כדרופ-דאון ב-UI. - **התקנה מחדש / עדכון**: `curl -X POST -H "Authorization: Bearer pcapi_legal_install_key_2026" -H "Content-Type: application/json" -d '{"packageName":"/home/chaim/legal-ai/adapters/deepseek-paperclip-adapter","isLocalPath":true}' http://localhost:3100/api/adapters/install`. לעדכון hot — `POST /api/adapters/deepseek_local/reload`. - **⚠ Cross-company sync**: `sync_agents_across_companies.py` **מדלג** על סוכנים עם `adapter_type` שונה בין CMP ל-CMPA. כשעוברים סוכן ל-`deepseek_local` חובה להחיל ידנית בשתי החברות לפני sync. - **תוספת adapters עתידיים** (OpenAI ישיר, Anthropic ישיר, וכו'): אותו דפוס. ה-package הראשי חייב לייצא `createServerAdapter()` שמחזיר `{ type, label, models, agentConfigurationDoc, execute, testEnvironment, sessionCodec, listSkills, syncSkills, ... }`. ראה את [`adapters/deepseek-paperclip-adapter/dist/index.js`](../adapters/deepseek-paperclip-adapter/dist/index.js) כתבנית. ### External adapters — Hermes Curator (`curator-cmp` / `curator-cmpa`) - פרופילי Hermes נפרדים לסוכן `hermes-curator` — מנתח החלטות סופיות ומציע עדכוני SKILL.md/lessons.md - מיקום: `~/.hermes/profiles/curator-cmp/` + `~/.hermes/profiles/curator-cmpa/` - מופעל אחרי export סופי; אינו מעדכן קבצים ישירות - **תהליך אישור הצעות:** הצעות ה-curator מגיעות כ-comment ב-Paperclip → חיים בוחן ומאשר ידנית → commits ל-`SKILL.md` ו-`docs/legal-decision-lessons.md` --- ## הערות יו"ר (Chair Feedback) מנגנון לתיעוד הערות דפנה על טיוטות: - **DB**: טבלת `chair_feedback` (case_id, block_id, feedback_text, category, lesson_extracted) - **API**: `GET/POST /api/feedback`, `PATCH /api/feedback/{id}/resolve` - **MCP tools**: `record_chair_feedback`, `list_chair_feedback` - **UI**: דף ניהול ב-`/feedback` (ב-Next.js) - **קטגוריות**: missing_content, wrong_tone, wrong_structure, factual_error, style, other --- ## ניהול משימות — Gitea Issues (פירוט) > **TaskMaster AI הוצא משימוש ב-2026-08-05.** מקור-האמת לעבודה הוא > [Gitea Issues](https://gitea.nautilus.marcusgroup.org/ezer-mishpati/legal-ai/issues). > ה-workflow, מערכת התיוג ו-Session-Start Protocol מתועדים ב-[`CLAUDE.md`](../CLAUDE.md); > טבלת המיפוי TaskMaster→issue ב-[`taskmaster-to-gitea-migration.md`](taskmaster-to-gitea-migration.md). ### עבודה מול ה-API הטוקן: `~/.git-credentials` (שורת `gitea.nautilus`) או Infisical `/apps/legal-ai` `GITEA_TOKEN`. ```bash API=https://gitea.nautilus.marcusgroup.org/api/v1/repos/ezer-mishpati/legal-ai T=$(grep -m1 'gitea.nautilus' ~/.git-credentials | sed 's|.*://[^:]*:\([^@]*\)@.*|\1|') # מה הבא בתור (דילוג על in-progress/in-review/blocked/deferred/needs-decision) curl -s -H "Authorization: token $T" "$API/issues?state=open&labels=priority:p1-high" # issue חדש curl -s -X POST -H "Authorization: token $T" -H "Content-Type: application/json" \ "$API/issues" -d '{"title":"...","body":"## What & Why\n...\n## Acceptance Criteria\n...","labels":[]}' # תוויות (id ← שם) curl -s -H "Authorization: token $T" "$API/labels?limit=100" ``` ### ארכיון TaskMaster `~/legal-ai/.taskmaster/tasks/tasks.json` **מוקפא לקריאה-בלבד** — 166 משימות שהושלמו, 4 שבוטלו, ו-26 הפתוחות שהועברו. שימושי לחפירה היסטורית ("למה עשינו X"), אבל **אין לכתוב אליו**. אם צריך הקשר ממשימה ישנה — לחפש בקובץ הזה, ולפתוח issue חדש אם נדרשת עבודה.