feat(host-agent): stop assignment execution collaboratively on cancellation
- LeaseGuard gains an is_cancellation convenience property - ActiveAssignmentRunner marks the lease lost with a cancellation reason when a renewal response reports cancel_requested - AssignmentExecutor threads stop_reason through to TaskRunner/ WorkflowRunner and maps a cancellation-flavored stop to AssignmentExecutionResult.status = "cancelled" instead of "failed" - AssignmentProcessor forwards a three-way done/cancelled/failed status when reporting the terminal result - Add/extend tests across lease, assignment, processor, and client
This commit is contained in:
@@ -9,6 +9,7 @@ from core.models import Task
|
||||
from host_agent.execution import ExecutionFactories
|
||||
from host_agent.planner_context import bind_planner_execution_context
|
||||
from host_agent.progress import TaskProgressHolder, TaskProgressSnapshot
|
||||
from runtime.task import is_cancellation_reason
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -32,18 +33,24 @@ class AssignmentExecutor:
|
||||
assignment: AssignmentModel,
|
||||
*,
|
||||
should_stop: Callable[[], bool] | None = None,
|
||||
stop_reason: Callable[[], str | None] | None = None,
|
||||
) -> AssignmentExecutionResult:
|
||||
self._progress.clear()
|
||||
with bind_planner_execution_context(assignment):
|
||||
if should_stop is not None and should_stop():
|
||||
reason = stop_reason() if stop_reason is not None else None
|
||||
return AssignmentExecutionResult(
|
||||
status="failed",
|
||||
failure_reason="execution interrupted",
|
||||
status="cancelled" if is_cancellation_reason(reason) else "failed",
|
||||
failure_reason=reason or "execution interrupted",
|
||||
)
|
||||
if assignment.workflow_definition_id is not None:
|
||||
return self._execute_workflow(assignment, should_stop=should_stop)
|
||||
return self._execute_workflow(
|
||||
assignment, should_stop=should_stop, stop_reason=stop_reason
|
||||
)
|
||||
if assignment.goal is not None:
|
||||
return self._execute_goal(assignment, should_stop=should_stop)
|
||||
return self._execute_goal(
|
||||
assignment, should_stop=should_stop, stop_reason=stop_reason
|
||||
)
|
||||
return AssignmentExecutionResult(
|
||||
status="failed",
|
||||
failure_reason="assignment has neither goal nor workflow definition",
|
||||
@@ -54,6 +61,7 @@ class AssignmentExecutor:
|
||||
assignment: AssignmentModel,
|
||||
*,
|
||||
should_stop: Callable[[], bool] | None,
|
||||
stop_reason: Callable[[], str | None] | None,
|
||||
) -> AssignmentExecutionResult:
|
||||
task = Task(goal=assignment.goal or "", device_id=assignment.device_id)
|
||||
if self.factories.metadata_store is not None:
|
||||
@@ -67,9 +75,11 @@ class AssignmentExecutor:
|
||||
if should_stop is None:
|
||||
completed = runner.run(task)
|
||||
else:
|
||||
completed = runner.run(task, should_stop=should_stop)
|
||||
completed = runner.run(
|
||||
task, should_stop=should_stop, stop_reason=stop_reason
|
||||
)
|
||||
return AssignmentExecutionResult(
|
||||
status="done" if completed.status == "completed" else "failed",
|
||||
status=_terminal_status(completed.status),
|
||||
failure_reason=completed.failure_reason,
|
||||
metadata={
|
||||
"runtime_task_id": completed.id,
|
||||
@@ -82,6 +92,7 @@ class AssignmentExecutor:
|
||||
assignment: AssignmentModel,
|
||||
*,
|
||||
should_stop: Callable[[], bool] | None,
|
||||
stop_reason: Callable[[], str | None] | None,
|
||||
) -> AssignmentExecutionResult:
|
||||
definition_id = assignment.workflow_definition_id or ""
|
||||
definition = self.factories.workflow_store.get_definition(definition_id)
|
||||
@@ -98,9 +109,10 @@ class AssignmentExecutor:
|
||||
definition,
|
||||
device_id=assignment.device_id,
|
||||
should_stop=should_stop,
|
||||
stop_reason=stop_reason,
|
||||
)
|
||||
return AssignmentExecutionResult(
|
||||
status="done" if run.status == "completed" else "failed",
|
||||
status=_terminal_status(run.status),
|
||||
failure_reason=(
|
||||
None if run.status == "completed" else f"workflow ended as {run.status}"
|
||||
),
|
||||
@@ -109,3 +121,11 @@ class AssignmentExecutor:
|
||||
"workflow_status": run.status,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _terminal_status(runtime_status: str) -> str:
|
||||
if runtime_status == "completed":
|
||||
return "done"
|
||||
if runtime_status == "cancelled":
|
||||
return "cancelled"
|
||||
return "failed"
|
||||
|
||||
Reference in New Issue
Block a user