Anonymous View
Skip to content

Expose workflow cancellation via REST, Studio UI, and programmatic API #184

@jakobklippel

Description

@jakobklippel

Context

The engine implements workflow cancellation but no user-facing surface exists. WorkflowOrchestrationService.cancel(workflowId) recursively cancels children and fires the parent callback with Canceled status, and WorkflowRunnerService.cancel(workflowId, userId) is an auth-aware wrapper — but nothing in @loopstack/api, the Studio frontend, or BaseWorkflow exposes it. Users have no way to cancel a run, and there is zero documentation for cancellation as a general feature (only a passing mention in agent-workflows.md).

Originally surfaced as Q59 in context/how-to-2-workflows.md ("Can a sub-workflow be cancelled? How does recursive cancellation work?"), currently tagged [not answered]. Decision was to implement the missing surface area first, then document.

What to do

  • Add a REST endpointPOST /workflows/:id/cancel (or DELETE /workflows/:id) wired to WorkflowRunnerService.cancel().
  • Add a Studio UI button — cancel button on the workbench/run view that calls the REST endpoint, with a ConfirmDialog to avoid misclicks on long-running runs. Frontend already renders canceled status colors.
  • Add a programmatic API on BaseWorkflowthis.cancel(workflowId) and/or this.cancelChildren() so a workflow can cancel sub-workflows from transition code. Recommendation: cancelling a child fires the parent callback with Canceled status (matches existing callback path).
  • Confirm the auth model — is WorkflowRunnerService.cancel user-scoping correct for the REST surface? (owner-only? workspace-wide? admin override?)
  • Decide idempotency / race handling — cancel of an already-terminal workflow currently logs and skips. Surface this predictably from REST + programmatic paths (200 idempotent vs 409 conflict).

Once shipped, document:

  • loopstack/docs/build/patterns/sub-workflows.md — cancelling a parent recursively cancels children; callback fires with Canceled status.
  • loopstack/docs/learn/workflow-engine.md — engine-internals view: Canceled terminal state, callback semantics.
  • Swagger docs for the new endpoint.
  • Update Q59 coverage tag in context/how-to-2-workflows.md from [not answered] to [sufficiently answered].

Affected area

  • Package(s): @loopstack/api, @loopstack/core, @loopstack/loopstack-studio
  • File(s):
    • loopstack/packages/core/src/workflow-processor/services/workflow-orchestration.service.ts (existing cancel())
    • loopstack/packages/core/src/scheduler/services/workflow-runner.service.ts (existing auth-aware cancel())
    • loopstack/packages/api/src/... (new endpoint)
    • loopstack/frontend/studio/... (new button)

Acceptance criteria

  • REST endpoint exists and is wired to WorkflowRunnerService.cancel()
  • Studio UI exposes a cancel button with confirm dialog on the run view
  • BaseWorkflow exposes a programmatic cancel API for sub-workflows
  • Auth scoping for cancel is confirmed and documented
  • Idempotent vs conflict behavior on already-terminal workflows is decided and consistent across surfaces
  • Docs updated (sub-workflows.md, workflow-engine.md, Swagger)
  • Q59 in context/how-to-2-workflows.md re-tagged [sufficiently answered]

Open questions

  • Should cancel fire any cleanup hooks (e.g. a @OnCancel transition), or is "terminal status + callback" the whole feature?
  • Do we need partial-cancel (cancel one child, parent continues)? Current cancel() recursively cancels children of the target — fine. No API to cancel a sibling without cancelling the parent. Probably not needed.

Additional context

Source: todo-cancel.md in the project root.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:p3Low prioritytaskGeneral task / chore

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions