Files
legal-ai/docs/superpowers/plans/2026-05-16-scheduled-agents.md
Chaim 4de555367d
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m32s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 11s
chore: fix .gitignore inline comments + add untracked code files
Inline # comments in gitignore are not supported — they were silently
breaking three patterns (data/checkpoints/, data/adapter-migration-state.json,
.claude/agents/.generated/). Moved comments to their own lines and added
missing entries for runtime dirs (data/audit/, data/logs/, etc.) and
temp files (.interaction_tmp.json, .design-build/, .taskmaster bak files).

Also tracks previously untracked legitimate files: scripts, tests, docs,
skills references, .env.example, taskmaster templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:49:32 +00:00

413 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<string, string> = {
"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 מועיר |