# 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).