Files

6.0 KiB

Phase 2 Research: Lifecycle Command Safety

Date: 2026-03-29 Source: Manual orchestration fallback in current session (subagent spawning unavailable in this environment)

Goal to Plan For

Phase 2 must make runtime lifecycle actions trustworthy after Phase 1's draft ownership work. The user should be able to:

  • start, stop, and retry the managed local runtime with clear in-progress and completion feedback (CTRL-01)
  • understand whether a draft change applies immediately, requires restart, or should be saved before an apply action (CTRL-02)
  • avoid conflicting lifecycle actions while another lifecycle action is already running (CTRL-03)

Current Implementation Findings

Frontend runtime manager

  • tauri-app/src/features/runtime/useRuntimeManager.ts owns one coarse busy boolean for all save and lifecycle actions.
  • startLocal() and stopLocal() optimistically set status to starting or stopping, but there is no explicit command metadata describing which action is in flight, what completed, or what the user should do next.
  • Polling continues every 3000 ms and already preserves dirty draft values from Phase 1, so Phase 2 should build on that state model instead of bypassing it.

Runtime status and settings UI

  • tauri-app/src/features/runtime/components/RuntimeStatusCard.tsx exposes a single primary action with generic busy copy (Working...). It does not tell the user whether the current action is a start, stop, retry, or blocked transition.
  • tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx already has a combined Save and start local API / Stop local API CTA and blocks saves for invalid or pending draft fields.
  • The drawer already recognizes one restart-related backend signal: it exits early when a save returns last_error.code === "restart_required", but there is no explicit impact preview before save and no dedicated restart/apply guidance after save.

Backend runtime commands

  • tauri-app/src-tauri/src/runtime/mod.rs already prevents restart-impacting config changes while the local process is running by returning restart_required from save_runtime_config_command() when connection_mode, model_path, addr, gpu_layers, or ctx_size change.
  • start_runtime_inner() returns the current snapshot if a child process already exists, but it does not record a structured "busy/conflict" result.
  • stop_runtime() sets status to Stopping, kills the child, then returns Stopped, but it does not distinguish repeated stop requests, remote-mode no-ops, or user-facing completion messages.
  • tauri-app/src-tauri/src/runtime/state.rs already revalidates health and degrades a running process when /health fails, which means lifecycle feedback should continue to respect ready, degraded, and error states rather than flattening everything into busy.

Existing tests and patterns

  • tauri-app/src/features/runtime/components/RuntimeSurface.test.tsx already covers start, stop, save-and-start, degraded polling, and draft safety. It is the best end-to-end harness for Phase 2 regressions.
  • Phase 1 plan files under .planning/phases/01-hf-model-discovery/ define the house style for executable plans: frontmatter with requirements and must_haves, then XML tasks with mandatory read_first and acceptance_criteria blocks.

Gaps to Close in Phase 2

  1. busy is too generic. The UI needs action-specific state such as start, stop, save, save_and_start, and retry so labels and disabled states are predictable.
  2. Apply semantics are implicit. The user can trigger save or save-and-start flows, but the UI does not explicitly explain which fields require restart and when a saved change is live versus pending restart.
  3. Conflict prevention is mostly optimistic. Frontend controls disable while busy is true, but backend commands do not provide a strong structured guard for duplicate or conflicting lifecycle requests.
  4. Completion behavior is underspecified. The user sees status changes, but there is no stable command-result summary for success/failure/retry guidance after a lifecycle action finishes.

Planning Implications

Phase 2 should be split into three execution plans:

  1. Backend lifecycle command contract - add explicit command guard behavior and command-result semantics at the Tauri runtime boundary.
  2. Frontend lifecycle/apply state model - teach the runtime manager and utilities how to classify draft impact, track the active command, and preserve actionable completion feedback.
  3. User-facing lifecycle surfaces - update the status card, settings drawer, and regression tests so the user sees the correct action copy, restart/apply guidance, and blocked states.

Concrete Decisions for the Plans

  • Treat these fields as restart-impacting because the backend already does: connection_mode, model_path, addr, gpu_layers, ctx_size.
  • Keep Phase 1 draft-preservation behavior intact; Phase 2 must layer lifecycle semantics on top of the current draft/baseline/live split rather than replacing it.
  • Use RuntimeSurface.test.tsx for integrated lifecycle behavior and Rust runtime tests for backend guard behavior.
  • Keep brownfield compatibility: do not remove existing startRuntime, stopRuntime, saveRuntimeConfig, or RuntimeSnapshot entry points; extend them with additional metadata instead.

Risks

  • If the frontend invents apply semantics that do not match backend restart rules, the workspace will feel inconsistent.
  • If backend guards remain implicit, racey double-clicks or overlapping drawer/status-card actions can still produce confusing no-op results.
  • If completion feedback only lives in transient button labels, users will lose the reason for a blocked or completed action after the next poll.
  • Rust tests around duplicate/conflicting start/stop/save requests
  • RuntimeSurface.test.tsx coverage for action-specific busy labels, blocked conflicting actions, restart-required guidance, and retry/error completion flows
  • Existing Phase 1 draft safety assertions still passing after lifecycle changes