Fixing QA, Documenter and PM issues with tasks.

This commit is contained in:
Renn F
2025-12-27 02:15:07 +01:00
parent d2034e538f
commit 3f48a93504
5 changed files with 147 additions and 29 deletions
+25
View File
@@ -113,6 +113,7 @@ class DocumenterAgent(Agent, PhaseEngine[DocTaskPhase, DocContext]):
""" """
MONITOR phase: Watch for documentation requests. MONITOR phase: Watch for documentation requests.
- Check for pending tasks directly assigned by PM
- Check for tasks awaiting documentation - Check for tasks awaiting documentation
- Check for documentation notifications - Check for documentation notifications
""" """
@@ -121,12 +122,36 @@ class DocumenterAgent(Agent, PhaseEngine[DocTaskPhase, DocContext]):
if self._pending_docs: if self._pending_docs:
return self._pending_docs.pop(0) return self._pending_docs.pop(0)
# Priority 1: Check for pending tasks directly assigned to this documenter
# This handles cases where PM assigns a task directly to documenter
pending_task = await self._find_pending_assigned_to_me()
if pending_task:
self.log.info(
"Found pending task assigned to me", task_id=str(pending_task)
)
return pending_task
# Priority 2: Check for tasks awaiting documentation (normal workflow)
task_id = await self._find_awaiting_documentation() task_id = await self._find_awaiting_documentation()
if task_id: if task_id:
return task_id return task_id
return None return None
async def _find_pending_assigned_to_me(self) -> UUID | None:
"""Find pending tasks directly assigned to this documenter."""
try:
result = await self._api_call(
"GET",
"/tasks",
params={"status": "pending", "assigned_to": str(self.id)},
)
tasks = result.get("items", [])
return UUID(tasks[0]["id"]) if tasks else None
except Exception as e:
self.log.warning("Failed to find pending assigned task", error=str(e))
return None
async def execute_task(self, task_id: UUID) -> bool: async def execute_task(self, task_id: UUID) -> bool:
""" """
Execute documentation through lifecycle phases. Execute documentation through lifecycle phases.
+88 -26
View File
@@ -189,18 +189,44 @@ class CellPMAgent(Agent, CyclicPhaseRunner[CellPMPhase]):
return None return None
async def _find_assigned_task(self) -> UUID | None: async def _find_assigned_task(self) -> UUID | None:
"""Find tasks assigned to this PM that are in progress.""" """Find tasks assigned to this PM that need work.
try:
result = await self._api_call( Checks for tasks in priority order:
"GET", 1. pending - newly assigned, needs claiming
"/tasks", 2. claimed - claimed but not started
params={"status": "in_progress", "assigned_to": str(self.id)}, 3. in_progress - active work
) 4. awaiting_pm_review - tasks ready for PM approval
tasks = result.get("items", result) if isinstance(result, dict) else result """
return UUID(tasks[0]["id"]) if tasks else None statuses_to_check = ["pending", "claimed", "in_progress", "awaiting_pm_review"]
except Exception as e:
self.log.warning("Failed to find assigned task", error=str(e)) for status in statuses_to_check:
return None try:
result = await self._api_call(
"GET",
"/tasks",
params={"status": status, "assigned_to": str(self.id)},
)
tasks = (
result.get("items", result)
if isinstance(result, dict)
else result
)
if tasks:
task_id = tasks[0]["id"]
self.log.info(
"Found assigned task",
task_id=str(task_id),
status=status,
)
return UUID(task_id) if isinstance(task_id, str) else task_id
except Exception as e:
self.log.warning(
"Failed to find assigned task",
status=status,
error=str(e),
)
return None
async def execute_task(self, task_id: UUID) -> bool: async def execute_task(self, task_id: UUID) -> bool:
""" """
@@ -258,7 +284,12 @@ class CellPMAgent(Agent, CyclicPhaseRunner[CellPMPhase]):
return await self._handle_in_progress_task(task_id, task) return await self._handle_in_progress_task(task_id, task)
if status == "paused": if status == "paused":
await self._mark_completed(task_id) # Paused tasks need to resume before completion
# Lifecycle: paused → in_progress → completed
await self._api_call("POST", f"/tasks/{task_id}/resume")
self.log.info("PM resumed paused task", task_id=str(task_id))
# Now complete via proper endpoint (validates PM role, checks subtasks)
await self._api_call("POST", f"/tasks/{task_id}/complete")
self.log.info("PM completed task", task_id=str(task_id)) self.log.info("PM completed task", task_id=str(task_id))
return True return True
@@ -1038,18 +1069,44 @@ class MainPMAgent(Agent, CyclicPhaseRunner[MainPMPhase]):
return None return None
async def _find_assigned_task(self) -> UUID | None: async def _find_assigned_task(self) -> UUID | None:
"""Find tasks assigned to this PM that are in progress.""" """Find tasks assigned to this Main PM that need work.
try:
result = await self._api_call( Checks for tasks in priority order:
"GET", 1. pending - newly assigned, needs claiming
"/tasks", 2. claimed - claimed but not started
params={"status": "in_progress", "assigned_to": str(self.id)}, 3. in_progress - active work
) 4. awaiting_pm_review - tasks ready for PM approval
tasks = result.get("items", result) if isinstance(result, dict) else result """
return UUID(tasks[0]["id"]) if tasks else None statuses_to_check = ["pending", "claimed", "in_progress", "awaiting_pm_review"]
except Exception as e:
self.log.warning("Failed to find assigned task", error=str(e)) for status in statuses_to_check:
return None try:
result = await self._api_call(
"GET",
"/tasks",
params={"status": status, "assigned_to": str(self.id)},
)
tasks = (
result.get("items", result)
if isinstance(result, dict)
else result
)
if tasks:
task_id = tasks[0]["id"]
self.log.info(
"Found assigned task",
task_id=str(task_id),
status=status,
)
return UUID(task_id) if isinstance(task_id, str) else task_id
except Exception as e:
self.log.warning(
"Failed to find assigned task",
status=status,
error=str(e),
)
return None
async def execute_task(self, task_id: UUID) -> bool: async def execute_task(self, task_id: UUID) -> bool:
""" """
@@ -1111,7 +1168,12 @@ class MainPMAgent(Agent, CyclicPhaseRunner[MainPMPhase]):
return await self._handle_main_pm_in_progress(task_id, task) return await self._handle_main_pm_in_progress(task_id, task)
if status == "paused": if status == "paused":
await self._mark_completed(task_id) # Paused tasks need to resume before completion
# Lifecycle: paused → in_progress → completed
await self._api_call("POST", f"/tasks/{task_id}/resume")
self.log.info("Main PM resumed paused task", task_id=str(task_id))
# Now complete via proper endpoint (validates PM role, checks subtasks)
await self._api_call("POST", f"/tasks/{task_id}/complete")
self.log.info("Main PM completed task", task_id=str(task_id)) self.log.info("Main PM completed task", task_id=str(task_id))
return True return True
+25 -1
View File
@@ -110,6 +110,7 @@ class QAAgent(Agent, PhaseEngine[QATaskPhase, ReviewContext]):
""" """
MONITOR phase: Watch for tasks ready for review. MONITOR phase: Watch for tasks ready for review.
- Check for pending tasks directly assigned by PM
- Check for tasks flagged as awaiting_qa - Check for tasks flagged as awaiting_qa
- Check for PM notifications - Check for PM notifications
""" """
@@ -119,13 +120,36 @@ class QAAgent(Agent, PhaseEngine[QATaskPhase, ReviewContext]):
if self._pending_reviews: if self._pending_reviews:
return self._pending_reviews.pop(0) return self._pending_reviews.pop(0)
# Query for tasks awaiting QA # Priority 1: Check for pending tasks directly assigned to this QA agent
# This handles cases where PM assigns a task directly to QA
pending_task = await self._find_pending_assigned_to_me()
if pending_task:
self.log.info(
"Found pending task assigned to me", task_id=str(pending_task)
)
return pending_task
# Priority 2: Query for tasks awaiting QA (normal workflow)
task_id = await self._find_awaiting_qa() task_id = await self._find_awaiting_qa()
if task_id: if task_id:
return task_id return task_id
return None return None
async def _find_pending_assigned_to_me(self) -> UUID | None:
"""Find pending tasks directly assigned to this QA agent."""
try:
result = await self._api_call(
"GET",
"/tasks",
params={"status": "pending", "assigned_to": str(self.id)},
)
tasks = result.get("items", [])
return UUID(tasks[0]["id"]) if tasks else None
except Exception as e:
self.log.warning("Failed to find pending assigned task", error=str(e))
return None
async def execute_task(self, task_id: UUID) -> bool: async def execute_task(self, task_id: UUID) -> bool:
""" """
Execute review through QA lifecycle phases. Execute review through QA lifecycle phases.
+2 -1
View File
@@ -140,7 +140,8 @@ async def validate_task_claimable(
""" """
task_status = task.get("status") task_status = task.get("status")
claimable_statuses = { claimable_statuses = {
"qa": ["awaiting_qa"], # QA: pending (direct QA tasks from PM) or awaiting_qa (normal workflow)
"qa": ["pending", "awaiting_qa"],
# Documenters: pending (direct docs tasks) or awaiting_documentation (workflow) # Documenters: pending (direct docs tasks) or awaiting_documentation (workflow)
"documenter": ["pending", "awaiting_documentation"], "documenter": ["pending", "awaiting_documentation"],
} }
+7 -1
View File
@@ -56,7 +56,13 @@ def _get_valid_claim_statuses(
role = agent.role.value if hasattr(agent.role, "value") else str(agent.role) role = agent.role.value if hasattr(agent.role, "value") else str(agent.role)
if role == "qa": if role == "qa":
return {TaskStatus.AWAITING_QA} # QA can claim:
# - PENDING: when PM assigns a QA task directly
# - AWAITING_QA: normal workflow after dev verification
statuses = {TaskStatus.PENDING, TaskStatus.AWAITING_QA}
if allow_reassign:
statuses.add(TaskStatus.CLAIMED)
return statuses
elif role == "documenter": elif role == "documenter":
# Documenters can claim: # Documenters can claim:
# - PENDING: when PM assigns a docs task directly # - PENDING: when PM assigns a docs task directly