Skip to content

Plan Lifecycle Controls

Users can intervene in running plans with three lifecycle actions: pause, resume, and stop. These controls appear in the plan details view header and broadcast state changes in real time via WebSocket.

Two states support lifecycle controls:

StateTypeMeaning
PausedNon-terminalExecution suspended. The plan stays paused until a user resumes or stops it.
StoppedTerminalPermanently cancelled. The plan cannot be restarted.

Both states serialize as simple tags in the PlanState JSONB enum — no additional fields:

{"status": "paused"}
{"status": "stopped"}

No database migration is needed. The existing plan.state JSONB column stores the new variants directly.

All three endpoints require PlanInstanceAction::Approve permission on the target plan.

PUT /api/plans/{id}/pause

Transitions a plan from StandBy or AwaitingInput to Paused.

  • Path param: id — plan UUID
  • Success: 204 No Content
  • Errors: 400 (invalid state), 403 (no permission), 404 (not found)
  • Handler: src/mods/plan/api/pause_plan_api.rs

The executor skips paused plans — only StandBy plans are picked up by the background job.

PUT /api/plans/{id}/resume

Transitions a plan from Paused to StandBy { next_execution_at: now() }.

  • Path param: id — plan UUID
  • Success: 204 No Content
  • Errors: 400 (not paused), 403, 404
  • Handler: src/mods/plan/api/resume_plan_api.rs

Setting next_execution_at to now() makes the plan immediately eligible for the next executor cycle (runs every 30 seconds).

PUT /api/plans/{id}/stop

Transitions any non-terminal plan to Stopped.

  • Path param: id — plan UUID
  • Success: 204 No Content
  • Errors: 400 (already terminal), 403, 404
  • Handler: src/mods/plan/api/stop_plan_api.rs

Rejects plans already in Completed, Failed, or Stopped. If a plan is mid-execution, the executor re-reads state after the LLM call completes and skips post-execution logic when the state is no longer Executing.

Each action creates a system log entry in the plan timeline:

src/mods/plan/types/plan_log_entry_type.rs
pub enum PlanLogEntrySystem {
// ...existing variants...
PlanPaused { by: String },
PlanResumed { by: String },
PlanStopped { by: String },
}

The by field stores session.user.name — the display name of the user who triggered the action.

All three endpoints call publish_plan_state(org_id, plan_id, state) after updating the database. This sends an EVT_PLAN_STATE_CHANGED WebSocket event to all organization subscribers. The use_realtime_plan hook in the plan details view merges live state updates with server-fetched data:

let effective_state = live_state
.read()
.clone()
.unwrap_or_else(|| data.plan.state.clone());

Button visibility in the header depends on the current state:

StatePauseResumeStop
StandBy
AwaitingInput
Executing
Paused
Terminal states

The autopilot toggle hides when the plan is Paused.

ComponentPausedStopped
Plan cardOrange badgeGray badge
Plan graphOrange pill with ⏸ icon (PausedBadge)
TimelineOrange “Paused by username”Red “Stopped by username”

The plan timeline component renders lifecycle events with color-coded text:

  • Paused: Orange text
  • Resumed: Blue text
  • Stopped: Red destructive text (uses destructive variant)

See src/mods/plan/components/plan_timeline_component.rs (lines 833–856).

Stopped is a terminal state for re-enrollment purposes:

PolicyBehavior
OnceBlocks re-enrollment if any plan exists (including Stopped)
MultipleAllows re-enrollment — only active plans block. Stopped counts as terminal alongside Completed and Failed.

Active states: PendingReview, StandBy, AwaitingInput, Executing, Paused.

See src/mods/plan/services/check_re_enrollment_service.rs.

FilePurpose
src/mods/plan/api/pause_plan_api.rsPause endpoint
src/mods/plan/api/resume_plan_api.rsResume endpoint
src/mods/plan/api/stop_plan_api.rsStop endpoint
src/mods/plan/types/plan_state_type.rsPaused and Stopped variants
src/mods/plan/types/plan_log_entry_type.rsAudit log event variants
src/mods/plan/views/plan_details_view.rsLifecycle buttons and state-dependent UI
src/mods/plan/components/plan_graph_component.rsPausedBadge component
src/mods/plan/components/plan_timeline_component.rsTimeline event rendering
src/mods/plan/services/check_re_enrollment_service.rsTerminal state handling