chore(skills): remove paperclip-dev, scope converting-plans-to-tasks
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
paperclip-dev is for maintaining the Paperclip codebase itself — not relevant to legal work. Removed from all 14 agents (was on CMPA mirror). paperclip-converting-plans-to-tasks helps decompose a plan into assigned issues. Useful for the planning-heavy agents (CEO, analyst). Now scoped to those two — removed from the other 5 in CMPA where it had crept in. Net effect: zero drift on paperclipai/* skills across all 7 master+mirror pairs. Verified via the new Agents tab dashboard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
| `pc.sh` | bash | **wrapper לכל קריאות Paperclip API מסוכנים** — מוסיף Authorization, X-Paperclip-Run-Id (audit trail), Content-Type, base URL. תחביר: `pc.sh <METHOD> <PATH> [BODY_JSON]`. אסור `curl` ישיר ל-`$PAPERCLIP_API_URL`. ראה `HEARTBEAT.md §0`. counterpart ב-Python: `web/paperclip_api.py`. | נקרא ע"י סוכנים |
|
||||
| `sync_missing_agent_skills.py` | python | סקריפט "אל-כשל" להוספת `paperclipSkillSync` ל-`הגהת מסמכים` ו-`מנתח משפטי` שפיספסו את ה-sync ההיסטורי (Gap #28). תומך `--verify`/`--dry-run`/`--apply`. גיבוי אוטומטי ל-`agents-pre-skill-sync-*.sql`. דורש `PAPERCLIP_BOARD_API_KEY` (Infisical /paperclip ב-nautilus env). idempotent. | חד-פעמי (בוצע 2026-05-04). שמור לרפרנס |
|
||||
| `sync_agents_across_companies.py` | python | **סנכרון סוכנים מ-CMP (1xxx, master) ל-CMPA (8xxx, mirror)** — Gap #25. משווה adapter_config (model/timeout/instructions/skills/etc), runtime_config (heartbeat), ושדות top-level (budget/metadata/icon/title/role). מסנן אוטומטית local skills שלא קיימים ב-mirror. לוגיקת subset (mirror יכול להחזיק יותר skills כי ה-API מוסיף required runtime skills). תומך `--verify`/`--dry-run`/`--apply [--only NAME]`. גיבוי אוטומטי. דורש `PAPERCLIP_BOARD_API_KEY`. **להריץ אחרי כל שינוי הגדרות ב-CMP.** | ידני אחרי כל שינוי |
|
||||
| `fix_paperclipai_skills_drift.py` | python | סקריפט חד-פעמי (בוצע 2026-05-04) שניקה drift על `paperclipai/*` skills בין CMP ל-CMPA. הסיר `paperclip-dev` מכל 14 הסוכנים, ודאג ש-`paperclip-converting-plans-to-tasks` קיים רק על CEO ו-analyst. תומך `--apply` (ברירת מחדל: dry-run). דורש `PAPERCLIP_BOARD_API_KEY`. נשמר לרפרנס למקרה שhdrift חוזר. | חד-פעמי (בוצע) |
|
||||
| `auto-sync-cases.sh` | bash | סנכרון תיקי ערר ל-Gitea — רץ כל דקה | `* * * * *` (cron) |
|
||||
| `backup-db.sh` | bash | גיבוי PostgreSQL יומי ל-`data/backups/` (gzip) | לתזמן: `0 2 * * *` |
|
||||
| `restore-db.sh` | bash | שחזור DB מגיבוי (companion ל-backup-db.sh) | ידני |
|
||||
|
||||
134
scripts/fix_paperclipai_skills_drift.py
Normal file
134
scripts/fix_paperclipai_skills_drift.py
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix paperclipai/* skill drift across CMP+CMPA agents.
|
||||
|
||||
Goal: zero drift on paperclipai/* skills between master(CMP) and mirror(CMPA).
|
||||
|
||||
Rules:
|
||||
* Remove ``paperclipai/paperclip/paperclip-dev`` from all 14 agents (not relevant
|
||||
for legal work — it's for maintaining Paperclip itself).
|
||||
* Ensure ``paperclipai/paperclip/paperclip-converting-plans-to-tasks`` exists
|
||||
on CEO + analyst agents in both companies (planning skill).
|
||||
* Remove ``paperclipai/paperclip/paperclip-converting-plans-to-tasks`` from any
|
||||
other agent in either company that currently has it.
|
||||
|
||||
Local/* and company/* skills are not touched — they're scoped to a company
|
||||
by design and drift is expected.
|
||||
|
||||
Usage::
|
||||
|
||||
PAPERCLIP_BOARD_API_KEY=pbk_... python scripts/fix_paperclipai_skills_drift.py # dry-run
|
||||
PAPERCLIP_BOARD_API_KEY=pbk_... python scripts/fix_paperclipai_skills_drift.py --apply # commit
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
import httpx
|
||||
|
||||
PAPERCLIP_API_URL = os.environ.get("PAPERCLIP_API_URL", "http://localhost:3100")
|
||||
PAPERCLIP_BOARD_API_KEY = os.environ.get("PAPERCLIP_BOARD_API_KEY")
|
||||
|
||||
COMPANIES = {
|
||||
"licensing": ("CMP ", "42a7acd0-30c5-4cbd-ac97-7424f65df294"),
|
||||
"betterment": ("CMPA", "8639e837-4c9d-47fa-a76b-95788d651896"),
|
||||
}
|
||||
|
||||
DEV_SKILL = "paperclipai/paperclip/paperclip-dev"
|
||||
CONVERTING_SKILL = "paperclipai/paperclip/paperclip-converting-plans-to-tasks"
|
||||
|
||||
# Hebrew names of the agents that should retain converting-plans-to-tasks.
|
||||
CONVERTING_TARGETS = {"עוזר משפטי", "מנתח משפטי"}
|
||||
|
||||
|
||||
def headers() -> dict[str, str]:
|
||||
if not PAPERCLIP_BOARD_API_KEY:
|
||||
sys.exit("PAPERCLIP_BOARD_API_KEY not set — fetch from Infisical first.")
|
||||
return {
|
||||
"Authorization": f"Bearer {PAPERCLIP_BOARD_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
async def fetch_company_agents(client: httpx.AsyncClient, company_id: str) -> list[dict]:
|
||||
r = await client.get(f"{PAPERCLIP_API_URL}/api/companies/{company_id}/agents", headers=headers())
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
def compute_changes(agent: dict) -> tuple[bool, list[str], list[str]]:
|
||||
skill_sync = (agent.get("adapterConfig") or {}).get("paperclipSkillSync") or {}
|
||||
old = list(skill_sync.get("desiredSkills") or [])
|
||||
new = [s for s in old if s != DEV_SKILL]
|
||||
if agent["name"] in CONVERTING_TARGETS:
|
||||
if CONVERTING_SKILL not in new:
|
||||
new.append(CONVERTING_SKILL)
|
||||
else:
|
||||
new = [s for s in new if s != CONVERTING_SKILL]
|
||||
return (sorted(old) != sorted(new), old, new)
|
||||
|
||||
|
||||
async def patch_agent(
|
||||
client: httpx.AsyncClient, agent_id: str, current_skill_sync: dict, new_skills: list[str]
|
||||
) -> None:
|
||||
body = {
|
||||
"adapterConfig": {
|
||||
"paperclipSkillSync": {**current_skill_sync, "desiredSkills": new_skills},
|
||||
}
|
||||
}
|
||||
r = await client.patch(
|
||||
f"{PAPERCLIP_API_URL}/api/agents/{agent_id}", headers=headers(), json=body, timeout=15
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--apply", action="store_true", help="commit changes (default: dry-run)")
|
||||
args = parser.parse_args()
|
||||
|
||||
mode = "APPLY" if args.apply else "DRY-RUN"
|
||||
print(f"=== {mode}: fixing paperclipai/* skill drift ===\n")
|
||||
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
all_agents: list[dict] = []
|
||||
for label, (_, cid) in COMPANIES.items():
|
||||
agents = await fetch_company_agents(client, cid)
|
||||
for a in agents:
|
||||
a["_company_label"] = COMPANIES[label][0]
|
||||
all_agents.extend(agents)
|
||||
|
||||
changes_planned = 0
|
||||
for a in sorted(all_agents, key=lambda x: (x["_company_label"], x["name"])):
|
||||
changed, old, new = compute_changes(a)
|
||||
label = a["_company_label"]
|
||||
if not changed:
|
||||
print(f" {label} {a['name']:20} no change")
|
||||
continue
|
||||
changes_planned += 1
|
||||
removed = sorted(set(old) - set(new))
|
||||
added = sorted(set(new) - set(old))
|
||||
print(f" {label} {a['name']:20} -{len(removed)} +{len(added)}")
|
||||
for s in removed:
|
||||
print(f" - {s}")
|
||||
for s in added:
|
||||
print(f" + {s}")
|
||||
if args.apply:
|
||||
skill_sync = (a.get("adapterConfig") or {}).get("paperclipSkillSync") or {}
|
||||
try:
|
||||
await patch_agent(client, a["id"], skill_sync, new)
|
||||
print(" ✓ patched")
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f" ✗ failed: {e.response.status_code} {e.response.text[:200]}")
|
||||
raise
|
||||
|
||||
print(f"\n{mode}: {changes_planned} agents would change")
|
||||
if not args.apply and changes_planned > 0:
|
||||
print("Run with --apply to commit.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user