# Scheduled Background Agents Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** הוסף 2 cron jobs לplugin: (1) תזכורת על תיקים תקועים 3+ ימים, (2) ניתוח פידבק יו"ר שבועי עם עדכון `decision-lessons.md` אוטומטי. **Architecture:** שני jobs חדשים נרשמים ב-`ctx.jobs.register()`. Job 1 קורא `/api/cases/stale?days=3` (endpoint חדש ב-legal-ai) ומוסיף תגובה לissues תקועים. Job 2 קורא `/api/chair-feedback/weekly-summary` (endpoint חדש), שולח לCEO agent שמעדכן את `decision-lessons.md`. **Tech Stack:** TypeScript (plugin-legal-ai jobs), Python/FastAPI (legal-ai web), asyncpg, Paperclip SDK `ctx.jobs.register()`, `ctx.agents.invoke()`. --- ## File Map | Action | File | |--------|------| | Modify | `plugin-legal-ai/src/worker.ts` — register 2 new jobs | | Modify | `plugin-legal-ai/plugin.json` — declare 2 new job entries | | Modify | `plugin-legal-ai/src/manifest.ts` — add to jobs array | | Modify | `legal-ai/web/app.py` — add `GET /api/cases/stale` + `GET /api/chair-feedback/weekly-summary` | | Modify | `legal-ai/web/database.py` (or equivalent DB module) — add `get_stale_cases()` + `get_weekly_chair_feedback()` | --- ## Task 1: הוסף endpoint `GET /api/cases/stale` **Files:** - Modify: `legal-ai/web/app.py` - Modify: `legal-ai/web/database.py` (שם DB queries מנוהלים — בדוק עם `grep -n "async def get_cases\|from.*database\|import.*db" /home/chaim/legal-ai/web/app.py | head -10`) - [ ] **Step 1: מצא את module ה-DB** ```bash grep -n "^from\|^import\|db\." /home/chaim/legal-ai/web/app.py | head -20 ``` זהה את שם הmodule שמכיל את DB queries (בד"כ `database.py` או `db.py`). - [ ] **Step 2: הוסף `get_stale_cases()` לmodule ה-DB** ```python async def get_stale_cases(days: int = 3) -> list[dict]: """Return cases whose status is not 'final' and haven't been updated in `days` days.""" async with get_db_connection() as conn: rows = await conn.fetch( """ SELECT case_number, title, status, company_id, updated_at, now() - updated_at AS age FROM cases WHERE status NOT IN ('final', 'new') AND updated_at < now() - ($1 || ' days')::interval ORDER BY updated_at ASC """, str(days), ) return [dict(r) for r in rows] ``` > `get_db_connection()` — השתמש בדפוס הקיים בקובץ. אם זה `asyncpg.connect()` ישיר, `asyncpg.create_pool()`, או context manager — העתק את הדפוס. - [ ] **Step 3: הוסף endpoint ב-`app.py`** ```python @app.get("/api/cases/stale") async def list_stale_cases(days: int = 3): """Cases stuck in non-final status for more than `days` days.""" cases = await db.get_stale_cases(days=days) return { "cases": [ { "case_number": c["case_number"], "title": c["title"], "status": c["status"], "company_id": str(c["company_id"]), "days_stale": c["age"].days, } for c in cases ], "total": len(cases), } ``` - [ ] **Step 4: בדיקה** ```bash curl -s "https://legal-ai.nautilus.marcusgroup.org/api/cases/stale?days=1" | jq .total ``` Expected: JSON עם רשימת תיקים. - [ ] **Step 5: Commit** ```bash cd /home/chaim/legal-ai git add web/app.py web/database.py # או השם הנכון git commit -m "feat: add GET /api/cases/stale endpoint" ``` --- ## Task 2: הוסף endpoint `GET /api/chair-feedback/weekly-summary` **Files:** - Modify: `legal-ai/web/app.py` - Modify: DB module - [ ] **Step 1: בדוק את מבנה טבלת `chair_feedback`** ```bash sqlite3 /home/chaim/.paperclip/instances/default/data/app.db \ ".schema chair_feedback" 2>/dev/null || \ psql -h localhost -p 5433 -U legal_ai -c "\d chair_feedback" legal_ai 2>/dev/null || \ grep -rn "chair_feedback" /home/chaim/legal-ai/mcp-server/src/ | head -10 ``` - [ ] **Step 2: הוסף `get_weekly_chair_feedback()` לDB module** ```python async def get_weekly_chair_feedback(days: int = 7) -> list[dict]: """Return chair feedback entries from the last `days` days.""" async with get_db_connection() as conn: rows = await conn.fetch( """ SELECT cf.case_number, cf.feedback_text, cf.created_at, cf.feedback_type, c.title FROM chair_feedback cf JOIN cases c ON c.case_number = cf.case_number WHERE cf.created_at > now() - ($1 || ' days')::interval ORDER BY cf.created_at DESC """, str(days), ) return [dict(r) for r in rows] ``` > אם שמות השדות שונים (בדוק ב-Step 1) — התאם. - [ ] **Step 3: הוסף endpoint** ```python @app.get("/api/chair-feedback/weekly-summary") async def get_chair_feedback_weekly(days: int = 7): """Feedback entries from the past week, formatted for the learning agent.""" entries = await db.get_weekly_chair_feedback(days=days) if not entries: return {"summary": "", "entry_count": 0} lines = [ f"- תיק {e['case_number']} ({e['title']}): {e['feedback_text']}" for e in entries ] summary = "\n".join(lines) return {"summary": summary, "entry_count": len(entries), "entries": entries} ``` - [ ] **Step 4: בדיקה** ```bash curl -s "https://legal-ai.nautilus.marcusgroup.org/api/chair-feedback/weekly-summary" | jq .entry_count ``` - [ ] **Step 5: Commit** ```bash cd /home/chaim/legal-ai git add web/app.py web/database.py git commit -m "feat: add GET /api/chair-feedback/weekly-summary endpoint" ``` --- ## Task 3: הוסף jobs לplugin **Files:** - Modify: `plugin-legal-ai/plugin.json` - Modify: `plugin-legal-ai/src/manifest.ts` - [ ] **Step 1: קרא את ה-`jobs` הקיים ב-`plugin.json`** ```bash cat /home/chaim/plugin-legal-ai/plugin.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d['jobs'], indent=2))" ``` - [ ] **Step 2: הוסף 2 jobs חדשים לarray `"jobs"` ב-`plugin.json`** ```json { "jobKey": "stale-case-reminder", "displayName": "תזכורת תיקים תקועים", "description": "מזהה תיקים שלא עודכנו 3+ ימים ומוסיף תגובה לissue", "schedule": "0 8 * * *" }, { "jobKey": "weekly-feedback-analysis", "displayName": "ניתוח פידבק שבועי", "description": "מסכם פידבק יו\"ר מהשבוע האחרון ומעדכן את decision-lessons.md", "schedule": "0 19 * * 0" } ``` > `"0 8 * * *"` = כל יום בשעה 08:00. `"0 19 * * 0"` = כל ראשון ב-19:00. - [ ] **Step 3: עדכן `manifest.ts`** ```bash grep -n "jobs\|jobKey\|schedule" /home/chaim/plugin-legal-ai/src/manifest.ts ``` הוסף את אותם 2 objects לarray `jobs` ב-`manifest.ts`. - [ ] **Step 4: Commit** ```bash cd /home/chaim/plugin-legal-ai git add plugin.json src/manifest.ts git commit -m "feat: declare stale-case-reminder and weekly-feedback-analysis jobs" ``` --- ## Task 4: Implement job handlers ב-`worker.ts` **Files:** - Modify: `plugin-legal-ai/src/worker.ts` - [ ] **Step 1: קרא את handler של `sync-case-status` הקיים** ```bash grep -n "sync-case-status\|jobs.register\|jobKey" /home/chaim/plugin-legal-ai/src/worker.ts ``` העתק את הדפוס. - [ ] **Step 2: הוסף את `stale-case-reminder` handler** בתוך `setup(ctx)`, אחרי רישום ה-job הקיים: ```typescript ctx.jobs.register("stale-case-reminder", async (job) => { ctx.logger.info("stale-case-reminder: starting"); const config = await ctx.config.get(); const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085"; const resp = await ctx.http.fetch(`${apiBase}/api/cases/stale?days=3`); if (!resp.ok) { ctx.logger.error(`stale-case-reminder: API error ${resp.status}`); return; } const data = (await resp.json()) as { cases: Array<{ case_number: string; title: string; status: string; company_id: string; days_stale: number; }>; }; for (const staleCase of data.cases) { const issueId = await ctx.state.get( { companyId: staleCase.company_id }, `case:${staleCase.case_number}` ); if (!issueId) continue; await ctx.issues.createComment({ issueId: issueId as string, body: `⚠️ **תיק תקוע** — ${staleCase.days_stale} ימים ללא עדכון (סטטוס: ${staleCase.status}). האם יש צורך בפעולה?`, }); ctx.logger.info( `stale-case-reminder: posted reminder for ${staleCase.case_number} (${staleCase.days_stale}d stale)` ); } ctx.logger.info(`stale-case-reminder: done. ${data.cases.length} cases reminded`); }); ``` - [ ] **Step 3: הוסף את `weekly-feedback-analysis` handler** ```typescript ctx.jobs.register("weekly-feedback-analysis", async (job) => { ctx.logger.info("weekly-feedback-analysis: starting"); const config = await ctx.config.get(); const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085"; const resp = await ctx.http.fetch(`${apiBase}/api/chair-feedback/weekly-summary`); if (!resp.ok) { ctx.logger.error(`weekly-feedback-analysis: API error ${resp.status}`); return; } const data = (await resp.json()) as { summary: string; entry_count: number; }; if (data.entry_count === 0) { ctx.logger.info("weekly-feedback-analysis: no feedback this week, skipping"); return; } // Invoke the CEO agent to process feedback and update decision-lessons.md const companies = await ctx.companies.list(); for (const company of companies) { // CEO IDs per company const CEO_IDS: Record = { "42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33", "8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562", }; const ceoId = CEO_IDS[company.id]; if (!ceoId) continue; await ctx.agents.invoke(ceoId, company.id, { prompt: `ניתוח פידבק שבועי יו"ר (${data.entry_count} פריטים): ${data.summary} המשימה: עדכן את /home/chaim/legal-ai/docs/legal-decision-lessons.md עם הלקחים החדשים שעולים מהפידבק. הוסף רק לקחים חדשים שלא קיימים כבר. קבץ לפי נושא.`, reason: "weekly-feedback-analysis scheduled job", }); ctx.logger.info( `weekly-feedback-analysis: invoked CEO for company ${company.id} (${data.entry_count} feedback entries)` ); break; // One CEO is enough — lessons file is shared } }); ``` - [ ] **Step 4: TypeScript check** ```bash cd /home/chaim/plugin-legal-ai && npx tsc --noEmit ``` Expected: 0 errors. - [ ] **Step 5: Build** ```bash cd /home/chaim/plugin-legal-ai && npm run build ``` - [ ] **Step 6: Commit** ```bash cd /home/chaim/plugin-legal-ai git add src/worker.ts git commit -m "feat: implement stale-case-reminder and weekly-feedback-analysis jobs" ``` --- ## Task 5: Re-install plugin + בדיקה - [ ] **Step 1: Deploy legal-ai** ```bash cd /home/chaim/legal-ai && git push origin main # המתן ~3 דקות curl -s https://legal-ai.nautilus.marcusgroup.org/api/health | jq .status ``` - [ ] **Step 2: Re-install plugin** ```bash cd /home/chaim/plugin-legal-ai && npm run build npx paperclipai plugin uninstall marcusgroup.legal-ai \ --api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026 npx paperclipai plugin install /home/chaim/plugin-legal-ai \ --api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026 pm2 restart paperclip ``` - [ ] **Step 3: בדוק שה-jobs רשומים** ```bash curl -s -H "Authorization: Bearer pcapi_legal_install_key_2026" \ http://localhost:3100/api/plugins/marcusgroup.legal-ai/jobs | jq .[].jobKey ``` Expected: `"sync-case-status"`, `"stale-case-reminder"`, `"weekly-feedback-analysis"`. - [ ] **Step 4: הפעל job ידנית לבדיקה** ```bash curl -s -X POST -H "Authorization: Bearer pcapi_legal_install_key_2026" \ http://localhost:3100/api/plugins/marcusgroup.legal-ai/jobs/stale-case-reminder/run | jq . ``` Expected: Job הופעל. בדוק logs: ```bash pm2 logs paperclip --lines 30 | grep "stale-case-reminder" ``` --- ## אימות סופי | בדיקה | פקודה | תוצאה מצופה | |-------|-------|-------------| | API stale endpoint | `curl .../api/cases/stale?days=1` | JSON עם cases | | API feedback endpoint | `curl .../api/chair-feedback/weekly-summary` | JSON עם summary | | Jobs רשומים | `GET .../api/plugins/.../jobs` | 3 jobs רשומים | | Stale reminder ידני | `POST .../jobs/stale-case-reminder/run` | תגובות בissues | | Feedback analysis ידני | `POST .../jobs/weekly-feedback-analysis/run` | CEO מועיר |