fix(self-heal): assign the fix task to the Main PM agent, not just the team

Origination created the task with team=main_pm but no assignee, so after
the CEO's Approve-&-Start it fell to the unassigned-team routing, which
the orchestrator picks up slowly or never. Assign the Main PM agent up
front (the seeded foundation uuid) so the dispatcher routes it straight
to that agent via the assigned-PM path once confirmed. The
confirmed_by_human hold is unaffected — the dispatcher's self-heal skip
sits before the assigned/unassigned split, so the task stays inert until
the CEO approves it (now covered by a test).
This commit is contained in:
Renn F
2026-06-20 19:10:21 +02:00
parent 65683394d4
commit 2d8a6c5d0f
3 changed files with 51 additions and 11 deletions
@@ -15,12 +15,14 @@ import pytest
from roboco.runtime.orchestrator import AgentOrchestrator
def _task(tid: str, source: str, confirmed: bool) -> dict[str, Any]:
def _task(
tid: str, source: str, confirmed: bool, assigned_to: str | None = None
) -> dict[str, Any]:
return {
"id": tid,
"source": source,
"confirmed_by_human": confirmed,
"assigned_to": None,
"assigned_to": assigned_to,
}
@@ -30,11 +32,17 @@ async def test_unconfirmed_self_heal_task_is_held_out_of_dispatch() -> None:
_task("A", "self_heal", False), # held — must NOT route until approved
_task("B", "self_heal", True), # CEO-approved → routes
_task("C", "manual", False), # ordinary task → routes
# The loop now assigns the Main PM agent up front, so the hold must
# survive an assignee too — the self-heal skip sits before the
# assigned/unassigned split, so an assigned-but-unconfirmed task is
# neither routed nor handed to the assigned-PM path.
_task("D", "self_heal", False, assigned_to="main-pm"),
]
stub = MagicMock()
stub._fetch_tasks = AsyncMock(return_value=tasks)
stub._is_task_handled_this_tick = MagicMock(return_value=False)
stub._route_unassigned_pm_task = AsyncMock()
stub._handle_pm_assigned_task = AsyncMock()
client: Any = MagicMock()
await AgentOrchestrator._dispatch_pm_work(cast("AgentOrchestrator", stub), client)
@@ -42,3 +50,6 @@ async def test_unconfirmed_self_heal_task_is_held_out_of_dispatch() -> None:
routed = [c.args[1]["id"] for c in stub._route_unassigned_pm_task.await_args_list]
assert "A" not in routed # the unconfirmed self-heal task stays inert
assert set(routed) == {"B", "C"}
# The assigned-but-unconfirmed self-heal task (D) is held before the
# assigned-PM branch — never handed to _handle_pm_assigned_task.
stub._handle_pm_assigned_task.assert_not_awaited()