Aleph
Concepts

Wizard

Session-based, multi-step configuration wizard framework driven through gateway RPC.

The wizard module is a session-based wizard framework exposed as wizard.* JSON-RPC methods on the gateway WebSocket. Every interaction is RPC-driven — there is no aleph or aleph-server CLI subcommand for it.

Design Philosophy

  1. Session-based state machinesession_id keeps progress across multiple wizard.next / wizard.answer calls
  2. RpcPrompter abstraction — the flow implementation holds an RpcPrompter, pushes WizardStep to the client, waits for wizard.answer to deliver the value
  3. Panel / desktop / any RPC client can drive it; no terminal required
  4. Sticky terminal statescancel() after Done does not overwrite the completed state or discard its finish_data payload

Architecture

Client (Panel/desktop)               WizardSession                  WizardFlow (background task)
   │                                    │                                │
   │── wizard.start ────────────────────▶│                                │
   │    { wizard_type:"onboarding" }    │── new(flow) tokio::spawn ─────▶│
   │◀── { session_id, step:welcome } ───│                                │
   │                                    │◀── prompter.prompt(welcome) ───│
   │                                    │                                │
   │── wizard.answer ──────────────────▶│                                │
   │    { session_id, step_id, value }  │── answer(step_id,value) ──────▶│
   │                                    │◀── prompter.prompt(next) ──────│
   │◀── { step:primary } ───────────────│                                │
   │                                    │                                │
   │── wizard.cancel ──────────────────▶│                                │
   │    { session_id }                  │── cancel_tx.send(()) ─────────▶│
   │◀── { cancelled:true } ─────────────│◀── settle(Cancelled) ──────────│

Core Components

WizardSession

A session is created by wizard.start and runs the flow in a background tokio task; the client advances by calling wizard.next / wizard.answer:

pub struct WizardSession {
    id: String,
    status: Arc<RwLock<WizardStatus>>,
    current_step: Arc<RwLock<Option<WizardStep>>>,
    step_rx: Arc<tokio::sync::Mutex<mpsc::Receiver<WizardStep>>>,
    answers: Arc<RwLock<HashMap<String, PendingAnswer>>>,
    error: Arc<RwLock<Option<String>>>,
    finish_data: Arc<RwLock<Option<serde_json::Value>>>,
    cancel_tx: Arc<RwLock<Option<oneshot::Sender<()>>>>,
}

Key properties:

  • step_tx is owned only by the RpcPrompter inside the spawned flow task. When the task ends the prompter is dropped, the last sender goes away, the channel closes, and next() receives None — that is how it surfaces Done/Error.
  • Terminal states are sticky. settle() only accepts transitions out of Running; a late cancel() (e.g. client-disconnect cleanup in the result-pending window) cannot clobber a completed Done and discard its finish_data.

WizardFlow trait

#[async_trait]
pub trait WizardFlow: Send + Sync {
    async fn run(&self, prompter: &RpcPrompter) -> Result<(), WizardSessionError>;
    fn name(&self) -> &str { "wizard" }
}

RpcPrompter

The only shipped prompter. prompter.prompt(step) pushes a WizardStep to the client and blocks until the matching wizard.answer arrives. finish(data) writes the terminal payload into finish_data, and the next wizard.next returns it as WizardNextResult.data on a Done.

Supported step types:

StepTypeClient interaction
NoteDisplay only
SelectSingle select
MultiSelectMulti-select
TextFree text (with sensitive / validation)
ConfirmYes/no (with default)
ProgressBackground progress (StepExecutor::Gateway, client just renders)
ActionServer-executed action

Built-in Flows

Flowwizard_typePurpose
OnboardingFlowonboardingFirst-launch: provider × 2, model, thinking, messaging, review (6 steps)
QuickSetupFlowquick-setupOne API key, skip the rest (re-install / quick path)
ProviderSetupFlowprovider-setupAdd / reconfigure a single provider

OnboardingFlow::run step sequence:

  1. welcome — intro
  2. configure_primary — pick primary provider + model + API key
  3. configure_secondary — pick secondary provider + model + API key
  4. configure_thinking — thinking level (low / medium / high)
  5. configure_messaging — Telegram / Discord / iMessage (Slack is currently disabled)
  6. review_and_finalize — review and persist

None of these are triggered from the CLI; the Panel settings page (or the post-install handshake window) calls wizard.start directly.


RPC Protocol

Methods registered in the gateway handler set. service_unavailable placeholders are replaced with the real handlers via install_wizard_handlers once the boot path initialises the WizardSessionManager:

MethodParamsReturns
wizard.start{ wizard_type, initial_data? }{ session_id, step, status }
wizard.next{ session_id, answer? }WizardNextResult (next step / done / cancelled / error)
wizard.answer{ session_id, step_id, value }WizardNextResult
wizard.cancel{ session_id }{ cancelled }
wizard.status{ session_id }{ status, error?, current_step? }

The terminal WizardNextResult can carry a data payload that the flow sets via RpcPrompter::finish(data); onboarding typically uses this to return the issued token or a config-write summary.


Safety / Concurrency

  • AtomicU64 generates step IDs (step-N), lock-free
  • unwrap_or_else(|e| e.into_inner()) lock-recovery pattern (poisoned-lock safe)
  • Explicit step validationanswer checks that step_id matches the current step; carrying an answer on wizard.next is rejected (wizard.answer is the only path that accepts values)
  • Channel-leak guardRpcPrompter::prompt removes the pending sender from the answers map if the step push fails, so it cannot leak for the rest of the session
  • No in-process CliPrompter — terminal UIs go through RPC and are rendered by Panel; the CLI never drives the wizard directly

Code Locations

  • src/wizard/mod.rs — module entry point
  • src/wizard/session.rsWizardSession, sticky terminal states, channel lifecycle
  • src/wizard/prompter.rsRpcPrompter, PendingAnswer, ProgressHandle
  • src/wizard/types.rsWizardStep / WizardOption / WizardStatus / StepType / WizardNextResult / WizardAnswer
  • src/wizard/flows/onboarding.rsOnboardingFlow / QuickSetupFlow / ProviderSetupFlow
  • src/gateway/handlers/wizard.rswizard.* RPC handlers, WizardSessionManager, install_wizard_handlers

On this page