Dispatcher self-healing + QA report classification fix
- Poll now includes blocked work items: when Hermes retries a transiently blocked task and it is active again, the item auto-resumes (task.resumed event) instead of staying blocked forever. Blocked items whose kanban task is done/review are left alone — re-finishing them every tick would spam events and duplicate cost entries. - Extraction prompt: a QA/review task that finished its checks is task.completed even when it found defects — findings are the deliverable. Previously "accessibility fails" wording classified the task as blocked. Verified live: misclassified QA task re-extracted to done; transiently blocked task auto-resumed; wall-clock cap fired on a 30-min runaway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0bae6dc51
commit
a858337edc
2 files changed
+26
-1
No files matched your search
+23
-1
@@ -208,10 +208,12 @@ class Dispatcher:
|
|||||||
# ------------------------------------------------------------ polling
|
# ------------------------------------------------------------ polling
|
||||||
|
|
||||||
async def _poll_in_flight(self) -> None:
|
async def _poll_in_flight(self) -> None:
|
||||||
|
# blocked items are polled too: Hermes retries transient blocks itself,
|
||||||
|
# so a blocked work item whose kanban task is active again must resume
|
||||||
items = await db.fetch_all(
|
items = await db.fetch_all(
|
||||||
"SELECT wi.*, e.id AS employee_id FROM work_item wi "
|
"SELECT wi.*, e.id AS employee_id FROM work_item wi "
|
||||||
"JOIN employee e ON e.id = wi.owner_id "
|
"JOIN employee e ON e.id = wi.owner_id "
|
||||||
"WHERE wi.status = 'in_progress' AND wi.hermes_task_id IS NOT NULL"
|
"WHERE wi.status IN ('in_progress','blocked') AND wi.hermes_task_id IS NOT NULL"
|
||||||
)
|
)
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
@@ -220,6 +222,13 @@ class Dispatcher:
|
|||||||
log.warning("poll failed for %s: %s", item["hermes_task_id"], e)
|
log.warning("poll failed for %s: %s", item["hermes_task_id"], e)
|
||||||
continue
|
continue
|
||||||
status = kt.get("status")
|
status = kt.get("status")
|
||||||
|
if item["status"] == "blocked":
|
||||||
|
# resume only on ACTIVE kanban states; a done/review kanban task
|
||||||
|
# under a blocked item was already classified — re-finishing it
|
||||||
|
# every tick would spam events and duplicate cost entries
|
||||||
|
if status in ("running", "ready", "todo", "scheduled"):
|
||||||
|
await self._resume(item)
|
||||||
|
continue
|
||||||
if status in ("review", "done"):
|
if status in ("review", "done"):
|
||||||
await self._finish(item, kt)
|
await self._finish(item, kt)
|
||||||
elif status == "blocked":
|
elif status == "blocked":
|
||||||
@@ -229,6 +238,19 @@ class Dispatcher:
|
|||||||
await self._cap(item, kt)
|
await self._cap(item, kt)
|
||||||
# todo/scheduled/ready/running: still waiting
|
# todo/scheduled/ready/running: still waiting
|
||||||
|
|
||||||
|
async def _resume(self, item: dict) -> None:
|
||||||
|
async with db.pool.connection() as conn:
|
||||||
|
async with conn.transaction():
|
||||||
|
await conn.execute(
|
||||||
|
"UPDATE work_item SET status='in_progress', updated_at=now() WHERE id=%s",
|
||||||
|
(item["id"],))
|
||||||
|
await conn.execute(
|
||||||
|
"UPDATE employee SET status='working' WHERE id=%s", (item["employee_id"],))
|
||||||
|
await db.append_event(conn, actor="system", event_type="task.resumed",
|
||||||
|
company_id=item["company_id"], work_item_id=item["id"],
|
||||||
|
payload={"note": "hermes task active again"})
|
||||||
|
log.info("resumed %s", item["id"])
|
||||||
|
|
||||||
async def _over_budget(self, item: dict) -> bool:
|
async def _over_budget(self, item: dict) -> bool:
|
||||||
if not item["dispatched_at"]:
|
if not item["dispatched_at"]:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ EXTRACT_SYSTEM = (
|
|||||||
"the options given\n"
|
"the options given\n"
|
||||||
"- approval.requested: needs authority the employee lacks\n"
|
"- approval.requested: needs authority the employee lacks\n"
|
||||||
"- incident.declared: something broke that affects others\n"
|
"- incident.declared: something broke that affects others\n"
|
||||||
|
"A QA/review/audit task that finished its checks is task.completed even "
|
||||||
|
"when it found defects in the thing under test — findings are its "
|
||||||
|
"deliverable, not a blocker.\n"
|
||||||
"Copy concrete facts (files, commits, test counts) into summary/detail. "
|
"Copy concrete facts (files, commits, test counts) into summary/detail. "
|
||||||
"Do not invent facts not present in the report."
|
"Do not invent facts not present in the report."
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user