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

13 KiB
Raw Blame History

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

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
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
@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: בדיקה
curl -s "https://legal-ai.nautilus.marcusgroup.org/api/cases/stale?days=1" | jq .total

Expected: JSON עם רשימת תיקים.

  • Step 5: Commit
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

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
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
@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: בדיקה
curl -s "https://legal-ai.nautilus.marcusgroup.org/api/chair-feedback/weekly-summary" | jq .entry_count
  • Step 5: Commit
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

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
{
  "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
grep -n "jobs\|jobKey\|schedule" /home/chaim/plugin-legal-ai/src/manifest.ts

הוסף את אותם 2 objects לarray jobs ב-manifest.ts.

  • Step 4: Commit
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 הקיים

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 הקיים:

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
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
cd /home/chaim/plugin-legal-ai && npx tsc --noEmit

Expected: 0 errors.

  • Step 5: Build
cd /home/chaim/plugin-legal-ai && npm run build
  • Step 6: Commit
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
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
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 רשומים
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 ידנית לבדיקה
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:

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 מועיר