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

82 lines
5.2 KiB
Markdown

## 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
(`ActiveAssignmentRunner``AssignmentExecutor``TaskRunner`/`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.