Files
agentic-mobile-control/openspec/changes/task-cancellation/proposal.md
T

5.2 KiB

Why

Task cancellation has been an explicit, repeatedly-acknowledged gap: cloud-console, cloud-control-plane-integration, and cloud-console-governance each excluded it from scope and deferred it to "a later lifecycle capability." Operators currently have no way to stop a queued, assigned, or in-flight task short of letting it run to completion, failure, or lease expiry — including tasks stuck against an offline device or a runaway plan. core/models.py's TaskStatus already reserves a "cancelled" value that no code path ever sets, and workflow/ already proves the collaborative-stop pattern this change extends to goal-based cloud tasks.

What Changes

  • Add a cancelled scheduler-side task status (cloud/scheduler.py's ScheduledTaskStatus) reachable from queued, assigned, and dispatched.
  • Add a public SDK endpoint (POST /v1/tasks/{task_id}/cancel, gated by the existing tasks:submit scope — the repository has no per-task submitter/ownership tracking to authorize against more narrowly) that immediately cancels a queued task and otherwise records a cancellation request against an assigned/dispatched task.
  • Add an internal Host Agent protocol signal: LeaseRenewalResponse gains a cancel_requested: bool field; the Cloud repository's renew_lease reports it when the active attempt has a pending cancellation. This is the only new edge on the existing outbound-only Host Agent protocol — no inbound push, no new endpoint on the Host side.
  • Extend the Host Agent's existing should_stop collaborative-stop mechanism (ActiveAssignmentRunnerAssignmentExecutorTaskRunner/WorkflowRunner) so a cancel_requested signal observed at lease-renewal time stops execution the same way a lost lease does today, and reports a cancelled-flavored terminal result.
  • Add "cancelled" as a real, reachable value of core/models.py's TaskStatus (the _interrupt_task path already flowing through should_stop gains a cancellation-vs-lease-loss distinction) and confirm TerminalResultRequest.status can express it end to end.
  • Add an Alembic migration recording cancellation request/acknowledgement metadata on scheduled_tasks/task_attempts (requestor, requested-at, and the terminal cancelled outcome) — no schema change to unrelated tables.
  • Add a "Cancel" action to the Cloud Console TasksView.vue task-detail panel (visible for queued/assigned/dispatched tasks the operator is authorized to act on) and to the status filter dropdown; add a matching POST /tasks/{id}/cancel route + button to the Host Agent local console's task detail page for Host-local visibility/action on tasks running on that Host.
  • BREAKING: none of the existing status literals are renamed or removed; cancelled is purely additive. Callers that exhaustively match/switch over TaskStatus (Python) or TaskStatus (TypeScript) without a default arm will need to add a case — flagged in design.md's migration plan.

Capabilities

New Capabilities

  • task-cancellation: cancellation request lifecycle across task-scheduler (queued/ assigned/dispatched states), host-agent-protocol (collaborative cancel signal over lease renewal), and agent-runtime/workflow execution (stopping mid-task on a cancellation signal, distinct from lease loss).

Modified Capabilities

  • task-scheduler: ScheduledTaskStatus gains cancelled; task submission/assignment requirements are unchanged, but the status-transition requirements need a new terminal transition path from queued/assigned/dispatched.
  • host-agent-protocol: the lease-renewal requirement ("Active execution renews its lease") gains a new SHALL for surfacing a cancellation request in the renewal response and treating it as a stop condition alongside lease loss.
  • platform-sdk: new cancel endpoint and scope-authorization requirement; task-status responses gain the cancelled status value.
  • cloud-console-ui: task list/detail view gains a Cancel action and the cancelled status value in filtering/display.

Impact

  • Cloud API / persistence: packages/cloud-platform/cloud/scheduler.py, repository.py (Protocol), sql_repository.py, db_models.py, new Alembic migration, internal_api/models.py + internal_api/api.py (renew/claim/cancel routes), sdk/api.py + sdk/models.py (new public cancel endpoint), auth.py (scope reuse).
  • Host Agent: apps/device-host-agent/host_agent/lease.py (ActiveAssignmentRunner cancellation-aware stop), client.py (surface cancel_requested from renew response), processor.py/assignment.py (terminal status reporting), local console (host_agent/web/app.py + templates/task_detail.html) new cancel route.
  • Runtime: core/models.py (TaskStatus reachability), runtime/task.py (_interrupt_task cancellation-vs-interruption distinction), workflow/runner.py (reuse of the already-existing cancelled terminal status — no change needed there).
  • Cloud Console frontend: cloud-console/src/views/TasksView.vue, src/types.ts, src/api.ts (new cancelTask client method).
  • Docs: docs/CLOUD_DEPLOYMENT.md gets a short note on cancellation being collaborative (not instantaneous) and its ~1/3-lease-period latency bound.