Sequencer

The Sequencer runs unattended imaging. You author a plan — targets, filters, exposures — and a set of policy phases that wrap the imaging run (open the roof, point, focus, then park and close up). The executor walks the plan on a background thread, drives the equipment through the Console, and publishes a live journal you can watch in the UI or read at /api/sequencer/state.

This page describes the executor in core/sequencer/run.py and the plan model in plan.py / structured.py. For the on-disk plan format see the Plans topic; for autofocus internals see Autofocus.

What a run is made of

A compiled plan is a flat list of timeline steps the executor walks in order. Wrapped around that list are up to seven policy phases — ordered action lists that run at lifecycle boundaries (start, error, suspend, resume, end). Plan-wide policy (constraints, focus triggers, cooling, meridian, guiding) rides along in a hidden notes step and is consulted between steps.

So a run looks like:

  1. Guiding precondition (if the plan guides) — a stand-alone run refuses to start unless PHD2 is open and connected, with a clear message telling you to connect the guide camera + mount first.
  2. Cooling on-start (if asked) — set the setpoint, optionally wait to settle.
  3. on_start phase — gates (wait-for-safe, wait-for-darkness, wait-until-time, altitude), then actions (open roof, unpark, precise point, autofocus...).
  4. The timeline — slew / filter / expose / wait / focus / guiding, step by step, with constraint, focus-trigger and meridian checks between exposures, plus optional per-frame quality acceptance.
  5. Cooling on-end (warm up, if asked).
  6. on_end phase — park, close roof, warm camera...

A pause drops into the on_suspend phase and holds; a resume runs on_resume and continues. A failed step runs the on_error phase and ends the run. A stand-alone run also drops a checkpoint at every step so an interrupted night can be resumed.

Timeline steps

Each step has a kind. The full vocabulary (validated in plan.py):

Steps are produced for you by the structured editor; you can also hand-write them in the TOML tab.

Policy phases

A phase is an ordered list of actions plus optional gates. The phase action catalog is the single source of truth, and the editor builds its menus from it. Each action has a fixed domain:

The domain split is what makes a sequence safe to run inside a blockscript. When a run is injected (blockscript-supervised), every obs action is SKIPPED — the blockscript owns the roof/mount/power — and only img actions run. Stand-alone runs execute every action. Unknown action types are dropped on normalise, so a hand-edited plan can't inject an unsupported action.

The phases:

(The structured plan normaliser defines on_start, on_error, on_suspend, on_resume, on_end. Legacy on_end bool flags — park / warm_up_camera — are synthesised into an on_end action list when no new-style list exists.)

On-start / on-resume gates

Before a phase's actions run, its optional gates hold until preconditions are met (used mostly on on_start and on_resume):

Per-action completion and wait

Each action is issued, then waited on until it actually completes before the next runs — never fire-and-forget, never parallel. Completion is confirmed three ways (from _WAIT_SPEC):

Override per action via its params: wait_s (dwell / extra settle), settle_s, timeout_s.

Per-action on-fail policy

When an action fails or times out, its optional on_fail policy decides (_apply_on_fail):

With no policy, the legacy resolver runs: standalone runs default to cancel (a human Try-Again / Cancel / Let-it-pass prompt is a Stage-2 hook, not yet wired); injected runs always cancel so the blockscript's error handling takes over. An exec-count safety cap guarantees the retry loop terminates.

Plan-wide policy (between-step checks)

These ride in the hidden notes policy step and are consulted by the run loop, mostly before each expose.

Constraints

Altitude/hour-angle/time gates, read from the mount's live properties:

A tripped altitude/HA constraint skips the current target (all its steps until the next slew) and continues — it does not abort the run. If no mount reports properties, the check is skipped rather than blocking.

Focus

Refocus triggers, checked before an expose (_should_refocus):

When a trigger fires, _do_policy_focus runs: if [autofocus].enabled AND a calibration exists, it runs Refocus via _run_sequence_refocus — the validated, always-inward run_refocus from the Autofocus topic, reading the saved focus_recipe.json sidecar (step / half-range / points) for a tight sweep around the current position, fitting the V, and moving to the mathematical minimum. It runs synchronously and cancelably (a watchdog ties the sequence Stop flag to the focus run's cancel, so Stop / Halt-all actually abort an in-progress refocus) and restores full-frame / bin 1 afterwards so science subs don't inherit the AF binning. If autofocus is off or uncalibrated it moves to focuser_position; otherwise it journals a no-op. AF failures during a run are fail-soft — journaled, never abort the sequence.

The global [autofocus].refocus_ triggers (refocus_enabled + every_n_frames / every_n_minutes / temp_delta_c / on_filter_change) overlay under* the plan's focus block (plan keys win) and route through this same Refocus path. With [autofocus].filter_offsets mapped, a filter change moves the focuser by the per-filter chromatic delta instead of triggering a full refocus. After a successful refocus, an optional [autofocus].focus_final_offset_steps nudges the focuser a fixed number of steps off the measured best-focus (for a known camera/imaging-train offset); it's fail-soft — a failed nudge is journaled, never aborts the run.

Cooling

Cooling does nothing if [cooling].temp_control_supported is false.

Meridian

For German equatorial mounts. The plan's mode is resolved against [mount].flip_mode:

Checked before each expose. halt ends the sequence cleanly when past the meridian. flip re-slews to the same RA/Dec to force the mount to swap pier sides, settles ([mount].flip_settle_s), and — when the driver reports side-of-pier — VERIFIES the side actually changed; if it didn't, it errors and stops rather than imaging on the wrong side. It refuses to flip if the mount won't report RA/Dec (it never defaults a missing Dec to 0). A flip won't refire within 5 minutes of the last one.

Guiding

The guiding policy (exposure_s, binning, dither cadence, settle gate) round-trips through the plan. When the plan enables guiding AND PHD2 control is allowed (phd2.allow_control):

If PHD2 control is off, guiding start/stop/dither become honest no-ops (they don't silently claim success). Set guide exposure/binning inside PHD2 directly if you don't drive it from the plan.

An optional guiding watchdog ([guiding_watchdog], default off) layers quality gates on top while guiding: it holds the next sub when the guide star is lost or RMS exceeds rms_max_px, waiting up to recover_timeout_s (polled every poll_interval_s) before failing the step.

Per-frame quality acceptance

An optional grader ([frame_acceptance], default off) inspects each light sub the moment it's saved and decides whether to keep it. Bias/dark/flat frames and non-light captures are always kept.

Each configured gate rejects a frame that fails it (set to 0 to disable that gate):

The grade is measured on a centred crop (central_region_pct, near the optical axis, for speed). A per_filter_json map lets you loosen the gates per filter — narrowband frames have far fewer stars and larger HFR than broadband, so a single global min_star_count would be wrong for them; any gate not overridden falls back to the global value.

On a reject:

Grading fails open: any error while grading keeps the frame, so a glitch never silently drops data. The running tallies (accepted / rejected / re-shot) appear in the sequencer status.

Run states and controls

idle → running → succeeded (happy path). Other terminals: failed (a step aborted, or on_start failed, or a go-to-end policy fired), stopped (Stop). running ↔ paused for pause/resume; stopping is the transient after Stop.

Checkpoint and resume

A stand-alone run drops a checkpoint at every step boundary (and per-sub inside an exposure), so if a night is cut short — a crash, a power loss, an app restart — you don't lose the whole session. The checkpoint is kept on a crash / FAILED / STOPPED run and dropped on a clean SUCCEEDED completion, so re-running a finished plan always starts fresh.

Resume is target-level: on resume, the on_start phase re-runs (re-establishing pointing / focus / guiding after the downtime), completed targets are skipped, and the run restarts at the in-progress target's slew so it re-acquires and re-shoots that field (resuming mid-target without re-slewing would image the wrong sky). Subs already captured in the resume target are skipped, so you don't re-shoot what you already have.

One checkpoint is kept per plan. A resume is refused if you've edited the plan since the interrupted run (the step list no longer matches) — start fresh instead. Supervised runs (blockscript / scheduler) don't checkpoint; the blockscript or scheduler owns night-level completion.

Per-step error handling

Every timeline step carries an on_error policy (plan.py):

The structured compiler picks sensible defaults: expose defaults to retry × 2; filter changes, track, guiding start/stop, per-target start conditions and end-of-plan park all default to skip (a sticky wheel or a missed start window shouldn't kill the run); slews default to abort.

Journal and status

Every step and synthetic action appends a StepEvent (timestamp, seq, kind, label, state, message, detail) to the journal and publishes it on the bus (sequencer.event). state is one of started / ok / skipped / failed / retrying (steps) or paused. Top-level status (sequencer.state) carries the run id, plan name, state, current/total step, progress fraction, error, and the last 200 journal events. It also reports the live frame-acceptance tallies (accepted / rejected / re-shot) and, while a refocus runs, a live autofocus V-curve so the run view can draw the focus graph as it happens.

Gotchas

How it ties in

Astroworx Starship v22.2.10 · this page is the in-app help of that buildDownload · HTTP API