Skip to content

[Feature] Decouple Automation scheduling and execution from UI project selection (process-level scheduler) #1271

@Astro-Han

Description

@Astro-Han

What task are you trying to do?

Trust an Automation to fire and finish while PawWork is open, regardless of which project is currently selected on screen. A scheduled task is a commitment owned by the app process, not by a visible project tab.

Today that commitment silently depends on UI project selection. Two user-visible failures fall out of that coupling:

  1. Switching or closing a project can cancel in-flight scheduled runs. Project disposal currently reaches into the instance-owned scheduler and stops scheduler-owned runs, so a window-level action becomes an engine-level cancellation.
  2. A project that is not open in the app never fires. The scheduler is per-instance state, so a definition whose project is not loaded has no owner watching its clock.

Both break the non-technical user promise: set it and it runs, as long as PawWork itself is open.

Which area would this change affect?

Model harness, prompts, tools, or session mechanics

What do you do today?

Keep the project open and avoid switching or closing it while a scheduled run is in flight. There is no workaround for the "closed project never fires" half.

What would a good result look like?

Lift Automation scheduling from per-project instance state to a single server-process owner, then acquire project instances on demand when a run fires.

This is a process-level scheduler, not an OS-level background service:

  • If PawWork is open, scheduled automations are watched across known projects.
  • If PawWork is closed, automations do not run and missed times are not backfilled.
  • If the machine sleeps or the app is unavailable long enough to miss a fire time, the scheduler records the existing missed-schedule outcome and advances normally instead of launching a backlog.

The implementation should stay deliberately narrow:

  1. Process-level scheduler singleton. Start one Automation scheduler with the server process. It scans the global Automation tables and owns due-fire decisions across projects.
  2. Explicit scope for Automation storage. Keep instance-scoped route behavior for normal API calls, but add the narrow global/scope-aware reads the process scheduler needs instead of relying on Instance.project.id and Instance.directory everywhere.
  3. Instances on demand. When a definition fires for a directory that is not open in the UI, the scheduler provides a headless instance for that project/directory and executes the run there.
  4. Project selection only affects UI state. Closing or switching a project may dispose frontend caches and idle instance state, but it must not mark scheduler-owned runs as stopped: cancelled.
  5. Single-owner property is preserved. A process scheduler and a project-open route must not double-fire the same definition. Keep the existing writer guard / busy-run ledger semantics; this issue is not changing Automation concurrency policy.
  6. Native fresh move. Retire the feat(app): edit automations inline and add full list row actions #1270 create-in-target + delete-from-source workaround for fresh automations. Moving a fresh automation to another project should update its ownership in place, preserve its id, schedule, and run history, and keep continue automations non-movable.

What would count as done?

  • With project A's automation mid-run, switching to project B or closing A does not change the run to stopped: cancelled; the run completes or fails on its real execution outcome.
  • An automation whose project is not open in the UI fires on schedule while PawWork is open, creates its run/session under the target project, and is visible when the project or Automations panel is opened later.
  • Opening a project that also has due automations does not cause a process-owned scheduler and a legacy instance-owned scheduler to double-fire the same definition.
  • Existing busy-run / same-writer behavior is preserved: this change must not introduce unattended concurrent writes to the same checkout.
  • App quit or process loss during a run still resolves on next launch via the interrupted-run reconciliation path; no zombie running rows remain.
  • Fresh cross-project move is a native field update: same automation id, same schedule, history follows the automation, and old runs still open their original sessions.
  • Continue automations remain bound to their source conversation and are not movable.
  • Focused tests cover process-level scheduling for a non-open project, no cancellation on project disposal, double-fire prevention, interrupted-run reconciliation, and native fresh move history.

What should stay out of scope?

  • OS-level persistence such as launchd/login items or running while PawWork is closed.
  • A new Automations management surface, toast system, or system notification behavior.
  • Missed-schedule count/range metadata or backlog catch-up runs.
  • Changing Automation concurrency semantics, including true cron-style parallel writes to the same checkout.
  • Changing stop after N runs from completed-run semantics to started-run semantics.
  • Changing delete semantics to let active runs continue after deleting the automation.
  • Soft-delete/tombstone storage, full run-definition snapshots, or a broad Automation persistence redesign.
  • Moving continue automations across projects.
  • New worktree-placement UI or new worktree-placement product behavior.

Which audience does this matter to most?

Both

Extra context

The current causal chain is:

UI closes/switches project → children.disposeDirectoryInstance.disposeDirectory(directory, { mode: "maintenance" }) → scheduler-owned run sweep → Automation.stopRunByID(runID, "cancelled") → run becomes { state: "stopped", stopReason: "cancelled" }.

The root cause is scheduler lifetime being tied to instance lifetime, which in the desktop app is often tied to UI project-open state. The fix is to make scheduling an app-process concern while keeping execution scoped to the target project when a run actually starts.

Discovered while testing #1270, whose cross-project move workaround should be retired once ownership is a process-level data field rather than an instance-local binding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityenhancementNew feature or requestharnessModel harness, prompts, tool descriptions, and session mechanics

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions