feat(seance): אחזור-הקשר מריצות-קודמות של issue לצמצום blind-heartbeats (#220)
מוסיף get_predecessor_context(issue_id, limit) ב-paperclip_client — read-only,
מחזיר ריצות-heartbeat שהסתיימו על אותו issue (newest-first) עם ה-summary שכל
heartbeat משאיר ב-result_json, כך ש-wake טרי קורא מה קודמיו הסיקו במקום לגלות-מחדש
(ה-generic blind heartbeat). קשירת run→issue דרך wakeup_request_id→payload.issueId;
limit מוגבל 1..10.
- Port: pc_get_predecessor_context (read-only, לא עטוף-טלמטריה).
- endpoint: GET /api/operations/issues/{issue_id}/predecessor?limit=3.
- 3 בדיקות (fake-asyncpg: shaping+isoformat, clamp, empty). אומת חי read-only על
issue בן 11 ריצות → מוחזרות מסקנות-דיספוזיציה אמיתיות.
Invariants: מקיים G12 (fetch מהמעטפת, המגע מהשער; אין REST/כלי-פלטפורמה בליבה),
G2 (מקור-predecessor יחיד). follow-up: MCP tool + HEARTBEAT (wake קורא בתחילת ריצה).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1132,6 +1132,54 @@ async def get_agent_health() -> dict:
|
||||
return {"ok": True, "items": items, "counts": counts}
|
||||
|
||||
|
||||
async def get_predecessor_context(issue_id: str, limit: int = 3) -> dict:
|
||||
"""Recent finished runs on an issue, newest-first, for session continuation (#220).
|
||||
|
||||
The "seance" idea from Gastown, grounded in our data: each ephemeral heartbeat
|
||||
rediscovers context every wake (the generic *blind heartbeat*; see
|
||||
[[project_comment_delivery_guarantee]]). This lets a fresh wake read what its
|
||||
predecessors on the SAME issue concluded — the run ``summary`` each heartbeat
|
||||
leaves in ``heartbeat_runs.result_json`` — instead of re-deriving from scratch.
|
||||
|
||||
Read-only. Runs are tied to the issue via ``wakeup_request_id`` →
|
||||
``agent_wakeup_requests.payload->>'issueId'`` (only finished runs are useful
|
||||
as predecessors). ``limit`` is clamped to 1..10.
|
||||
"""
|
||||
limit = max(1, min(int(limit), 10))
|
||||
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
||||
try:
|
||||
rows = await conn.fetch(
|
||||
"""SELECT h.id, h.status, h.started_at, h.finished_at,
|
||||
h.result_json->>'summary' AS summary,
|
||||
h.error_code, h.session_id_after, a.name AS agent_name
|
||||
FROM heartbeat_runs h
|
||||
JOIN agent_wakeup_requests w ON w.id = h.wakeup_request_id
|
||||
LEFT JOIN agents a ON a.id = h.agent_id
|
||||
WHERE w.payload->>'issueId' = $1
|
||||
AND h.finished_at IS NOT NULL
|
||||
ORDER BY h.started_at DESC
|
||||
LIMIT $2""",
|
||||
issue_id, limit,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
runs = [
|
||||
{
|
||||
"run_id": str(r["id"]),
|
||||
"status": r["status"],
|
||||
"started_at": r["started_at"].isoformat() if r["started_at"] else None,
|
||||
"finished_at": r["finished_at"].isoformat() if r["finished_at"] else None,
|
||||
"summary": r["summary"],
|
||||
"error_code": r["error_code"],
|
||||
"session_id": r["session_id_after"],
|
||||
"agent_name": r["agent_name"],
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
return {"ok": True, "issue_id": issue_id, "runs": runs}
|
||||
|
||||
|
||||
# Singleton project for the precedent-library extraction queue. One issue per
|
||||
# uploaded precedent — assigned to the CEO who runs the local-MCP extractor.
|
||||
_LIBRARY_PROJECT_NAME = "ספריית פסיקה — תור חילוץ"
|
||||
|
||||
Reference in New Issue
Block a user