feat(health): טקסונומיית-בריאות לסוכן — zombie/stalled/working/idle (#222)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 31s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

מוסיף web/agent_health.py — מסווג-טהור classify_issue_health (agent-agnostic,
testable) שממפה כל issue פתוח+משויך-סוכן למצב-בריאות אחד, ו-get_agent_health
ב-paperclip_client שגוזר את הפרימיטיבים מ-heartbeat_runs (run חי=finished_at null
בחלון) + agent_wakeup_requests (total + recovery-markers בחלון). מרכיב על אירועי
#219 (אותם reasons של recovery).

- zombie = פתוח+משויך-סוכן, אין run חי, יש wakeup-recovery → הstranded-child שאובחן
  ידנית (reference_recovery_loop_stranded_child) עולה אוטומטית.
- stalled = ננער חוזר בלי progress; working = run חי; idle = המתנה בנונית.
- Port: pc_get_agent_health (read-only, לא עטוף-טלמטריה לפי הכלל); endpoint
  GET /api/operations/agents/health (worst-first, +counts).
- 7 בדיקות pytest (מסווג-טהור + fetch fake-asyncpg). אומת חי read-only מול
  Paperclip DB: 6 issues→idle נכון, אפס zombie.

Invariants: מקיים G12 (מסווג אגנוסטי; fetch מהמעטפת; המגע מהשער), G2 (מקור-בריאות
יחיד). UI ב-/operations = follow-up מגודר-שער-עיצוב (feedback_claude_design_gate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 03:05:17 +00:00
parent 653b951d30
commit bfc352c7b5
5 changed files with 307 additions and 0 deletions

View File

@@ -62,6 +62,7 @@ from web.agent_platform_port import (
pc_create_project,
pc_create_workflow_issue,
pc_escalate_issue,
pc_get_agent_health,
pc_get_agents,
pc_get_agents_for_case,
pc_get_case_issues,
@@ -7531,6 +7532,18 @@ async def operations_agents():
}
@app.get("/api/operations/agents/health")
async def operations_agent_health():
"""Per-issue agent health taxonomy (#222): zombie / stalled / working / idle.
Read-only. Surfaces the stranded-child / recovery-loop cases (``zombie``)
automatically — the ``gt feed --problems`` idea grounded in our failure
modes — so they no longer need hand-querying the Paperclip DB. Consumed by
the /operations dashboard (UI is design-gated, follow-up).
"""
return await pc_get_agent_health()
@app.get("/api/operations/agents/runs/{run_id}/log")
async def operations_agent_run_log(run_id: str):
"""Full output log (NDJSON stream) of one heartbeat run."""