# Proposal: Unify Saved Tasks on SSH Services

**Change:** `unify-tasks-on-services`
**Phase:** proposal
**Date:** 2026-06-19
**Status:** awaiting review (design only — no implementation yet)

## Context and problem

Saved tasks (the Actions page) currently have **two execution paths**:

1. **Actions page** → resolves a *machine* (`default_machine_id`) → runs via
   `_client_for_machine` → logs to `saved_task_runs`.
2. **SSH task widget** → resolves an `ssh_tasks` *service instance* → runs via
   `_build_ssh_client` → logs to `service_task_runs`.

Same saved-task records, two runners, two history tables, two target models. This
is the leftover inconsistency from the service-registry change (design §12): the
widget was migrated to services but the Actions page was not.

## Proposal

Migrate the Actions page onto the same `ssh_tasks` service model the widget
already uses, so there is **one execution path** and **one history table**.

- Saved tasks gain `default_service_id` (replaces `default_machine_id`), pointing
  at an `ssh_tasks` service instance.
- The Actions runner resolves an `ssh_tasks` service (the task's default, or an
  explicit run-time override), builds the SSH client from the service record, runs
  the task, and logs to `service_task_runs`.
- `saved_task_runs` is dropped; both the Actions page and the widget read
  `service_task_runs`.
- Local (API-host) task execution is dropped — all tasks run over SSH against
  `ssh_tasks` services.

## Goals

- One execution path for saved tasks (Actions page + widget share it).
- One run-history table (`service_task_runs`).
- Tasks target `ssh_tasks` service instances, consistent with the rest of the
  service registry.
- Run-time override preserved: a task can be run against any `ssh_tasks` instance.

## Non-goals

- **No change to the jobs router** (`/api/jobs/run`, the `disk_usage` template,
  etc.). That stays machine-based for the File Browser's on-demand SSH checks.
- **No machine/service unification** (follow-up #3). Machines still own File
  Browser + node_exporter transport.
- **No local execution mode.** Dropped per decision; tasks are SSH-only.
- **No automatic data migration** of `default_machine_id` → `default_service_id`.
  Break backwards compatibility (consistent with the service-registry change):
  existing tasks lose their default target and the user re-points them.

## Decisions (from grilling)

| Topic | Decision |
|-------|----------|
| Local execution | **SSH-only.** Drop local mode; `ssh_tasks` services handle all task execution. |
| Run history | **`service_task_runs` only.** Drop `saved_task_runs`. |
| Run-time override | **Keep.** A task can run against any `ssh_tasks` instance at run time. |

## Risks

- **Breaking upgrade.** Existing tasks lose `default_machine_id`; users re-point
  to an `ssh_tasks` service. Document in changelog.
- **Local-mode loss.** Any deployment relying on local task execution must set up
  an SSH loopback (or an ssh_tasks service pointing at localhost with a key) to
  keep running local tasks.
- **Shared execution code.** The Actions runner and the widget must share one
  `run_saved_task` helper to avoid divergence; extracting it is the core refactor.

## Out of scope

- Machine/service unification (follow-up #3).
- Migrating the jobs router (`/api/jobs`) off machines.
- A UI for browsing `service_task_runs` across all services (the service page
  already shows per-instance history; the Actions page shows per-task history).
