Scheduler prioritization
This page explains how the scheduler chooses which observation to run next on a given telescope. The mechanics here cut across every layer of the access model — telescope access grants, queues, queue access grants, and observing accounts — so it's worth reading Telescope access, Observing queues, and Access grants first if any of that vocabulary is new.
The short version: the scheduler walks a telescope's queues in priority order, and within each queue it orders eligible observations by the priority of their submitter's queue access grant. Higher queues come before lower; within a queue, higher-priority grants come before lower.
The three records that drive prioritization
TelescopeAccessGrant ←─ entity ↔ telescope, total usage across ALL queues
│
│ has many
▼
ObservingQueueAccessGrant ←─ entity ↔ one queue, priority within the queue,
│ usage within the queue
│ referenced by
▼
ObservingAccount ←─ bundles queue access grants for submitters
TelescopeAccessGrant
Each telescope maintains an ordered set of telescope access grants — one per entity that's been admitted to observe with the telescope.
The grant carries the entity's total usage across all queues
(time_used, plus the bookkeeping fields time_contested and
time_waiting) and a shares value that's the input to
ownership share-based prioritization.
Telescope access grants are not the same thing as the
TelescopeAccessGrants that delegate read/update/delete on the
telescope record — they share a name (and a model)
but represent different intent. The one described here is the
observing-time grant; an owner uses it to say "this entity is
admitted to the telescope, with this many shares of its time."
ObservingQueueAccessGrant
Once an entity holds a telescope access grant, queue access grants hang off it — one per queue the entity is allowed to access.
A queue access grant carries:
- The queue it applies to.
- A back-reference to the parent telescope access grant (so the entity and the telescope are both implied).
order— the configured priority of this grant within the queue. Lower values are higher priority.effective_order— the priority the scheduler actually uses, which isorderfor static queues and is recomputed by the scheduler for dynamic queues (see below).time_used,time_contested,time_waiting— the entity's usage within this queue specifically, distinct from the parent telescope access grant's cross-queue totals.- An optional observing-policy override.
The split between "total usage on the telescope" (on the telescope access grant) and "usage in this queue" (on the queue access grant) is what lets a queue's prioritization mode talk about queue-local usage while ownership share-based prioritization can talk about telescope-wide usage.
ObservingAccount
Once an entity holds a queue access grant, they can include it in one or more observing accounts. An observing account is a bundle of:
- One or more queue access grants (possibly across telescopes), and
- A list of observing grants identifying which entities are
allowed to add observations to those queues through this account
(
OwnerObservingGrant,MemberObservingGrant,GroupObservingGrant,DelegatedObservingGrant, etc. — see Observing accounts).
The account is what submitters interact with. They never pick a queue access grant directly; they pick an account, and the account exposes whichever queues its grants reach.
Queue prioritization modes
Each queue chooses exactly one prioritization mode, recorded as
ObservingQueue.access_prioritization. The mode determines how the
scheduler turns the queue's set of queue access grants into an
ordered list:
| Mode | Behavior |
|---|---|
| Static (fixed) | effective_order = the configured order on each queue access grant. Owner-set. Doesn't change as time is used. |
| Total-usage (ownership share-based) | effective_order is recomputed each run so that entities under-using their telescope-wide share (TelescopeAccessGrant.shares vs. time_used) float to the top. Fair-share across the telescope. |
| Queue-usage | Same shape as total-usage, but the usage comparison uses ObservingQueueAccessGrant.time_used instead. Fair-share within this queue. |
| Schedule (calendar) | A calendar maps entity → time window. During an entity's window, that entity's grant floats to the top of the queue; outside it, the entity isn't considered. |
The two usage-based modes both produce a dynamic ordering — the
effective_order is the output of the prioritization model, not a
field owners edit. The static mode is the only one where
effective_order == order by construction.
How the scheduler orders observations
For a given telescope, at scheduler-run time:
- Walk the queues in queue order.
ObservingQueue.orderdefines the priority order of queues on the telescope — lower values are higher priority. Queueenabled = falsequeues are skipped. - Within each queue, order the queue access grants by
effective_order. Lower is higher priority. Theeffective_orderis what the queue's prioritization mode produced (static = configured, total-usage / queue-usage = share-based, schedule = calendar-driven). - Within each grant, surface that grant's eligible observations. Eligibility filters out observations whose targets aren't observable now, whose policy constraints are failing, or whose funding account / grant has hit a quota. Observations from higher-priority grants come before those from lower-priority grants. Higher-priority queues come before lower-priority queues.
- Pick the next observation to dispatch. The scheduler then layers its slew / overhead model on top — among the highest-priority eligible observations, it prefers the one that's cheapest to start next — and dispatches one task.
ObservingQueue.can_interrupt controls whether a higher-priority
queue is allowed to preempt an in-progress observation from a
lower-priority queue, or whether it has to wait for the current
observation to finish.
Each pass through the loop emits an ObservingScheduleEvent per
queue, and the overall run is summarized by a SchedulerRun. Those
are the records to read first when diagnosing "why didn't my
observation run." See Observing queues → Schedule events and
scheduler runs.
Where observing accounts fit
The prioritization above is about queue access grants, not
observing accounts. The account layer matters for which
submitter is allowed to put an observation on the queue, and for
quota throttling on top of dispatch, but the scheduler's ordering
within a queue is determined by the queue access grant's
effective_order, not by anything on the account.
That has two practical consequences:
- An observation's priority is fixed at the moment it's submitted through a particular account → queue access grant. Switching accounts (if the observer has access to more than one) changes which queue and grant the observation lands on, which can change its priority.
- Quotas on the account or its grants don't reorder the queue; they pause dispatch when exceeded. A high-priority observation whose account has hit a quota stalls in place rather than being reordered behind anyone else. See Observing accounts → Quotas.
Open: prioritization within an observing account
A single observing account can hold queue access grants spanning multiple queues — and sometimes multiple telescopes — and a single queue access grant can be referenced by multiple accounts. We have not yet specified whether an entity is allowed to express a preference among the queue access grants inside one of its accounts, or among the accounts a given submitter can choose from.
For now, when an account exposes more than one eligible target for the same observation, the scheduler treats them as interchangeable — the choice between them falls out of the per-queue ordering rules above plus the slew/overhead model, not out of any account-side priority field. If you have a use case that needs explicit account-side prioritization, raise it; it's an open design question, not a settled feature.
Reference
ObservingQueue Schema
Properties
| Name | Type | Description |
|---|---|---|
| id | Integer |
No description |
| queue_type | Enum(maintenance, too, general, calibration, custom) |
No description |
| telescope_id | Integer |
No description |
| name | String(256) |
No description |
| slug | String(256) |
No description |
| description | String(Unbounded) |
No description |
| can_interrupt | Boolean |
Whether or not the queue can interrupt lower priority queues |
| access_prioritization | Enum(default, total_usage, queue_usage, schedule) |
No description |
| order | Integer |
The order used to prioritize queues for a given telescope. Lower values are higher priority. |
| enabled | Boolean |
Whether or not the queue is enabled. Disabled queues will not be scheduled. |
Relationships
| Relationship Name | Type |
|---|---|
| telescope | Telescope |
| access_grants | ObservingQueueAccessGrant |
ObservingQueueAccessGrant Schema
Properties
| Name | Type | Description |
|---|---|---|
| id | Integer |
The unique identifier for the queue access grant. |
| queue_id | Integer |
The ID of the queue to which the allocation belongs. |
| telescope_access_grant_id | Integer |
The ID of the telescope access grant providing access to this queue. |
| order | Integer |
The order used to prioritize this allocation within the queue. Lower values are higher priority. |
| revoked | Boolean |
Whether this allocation has been revoked. If true, the allocation is no longer valid. |
| effective_order | Integer |
The effective order used to prioritize thisa llocation within the queue, taking into account the queue's prioritization policy and the allocation's own priority. |
| time_used | Float |
The total time used by the account regardless of whether it was contested or not. |
| time_contested | Float |
The total time used by the account when sibling accounts also had observations which could have been scheduled. |
| time_waiting | Float |
The total time that the other accounts used while this account was waiting for its observations to be scheduled. |
| observing_policy_id | Integer (Optional) |
No description |
Relationships
| Relationship Name | Type |
|---|---|
| queue | ObservingQueue |
| telescope_access_grant | TelescopeAccessGrant |
| observing_accounts | ObservingAccount |
| observing_policy | ObservingPolicy |
TelescopeAccessGrant Schema
Properties
| Name | Type | Description |
|---|---|---|
| id | Integer |
No description |
| telescope_id | Integer |
No description |
| entity_id | Integer |
The entity (user or organization) granted access to the telescope. |
| shares | Float |
The number of shares this access grant has. This is used to determine how much time the entity can use the telescope. |
| time_used | Float |
No description |
| time_contested | Float |
No description |
| time_waiting | Float |
No description |
| revoked | Boolean |
Whether this access grant has been revoked. If true, the access grant is no longer valid. |
Relationships
| Relationship Name | Type |
|---|---|
| telescope | Telescope |
| entity | Entity |
| invitation | TelescopeAccessGrantInvitation |
| queue_access_grants | ObservingQueueAccessGrant |
ObservingScheduleEvent Schema
Properties
| Name | Type | Description |
|---|---|---|
| id | Integer |
No description |
| owner_id | Integer (Optional) |
No description |
| telescope_id | Integer (Optional) |
No description |
| priority | Integer |
No description |
| created_on | DateTime |
No description |
| updated_on | DateTime (Optional) |
No description |
| start_on | DateTime (Optional) |
No description |
| end_on | DateTime (Optional) |
No description |
| end_time_unspecified | Boolean |
No description |
| all_day | Boolean |
No description |
| recurrence | String(100) (Optional) |
No description |
| recurring_event_id | Integer (Optional) |
No description |
| is_too | Boolean |
No description |
| is_exclusive | Boolean |
No description |
| description | String(100) |
No description |
Relationships
| Relationship Name | Type |
|---|---|
| owner | Organization |
| telescope | Telescope |
| users | User |
| groups | Group |
| organizations | Organization |
| observations | Observation |
SchedulerRun Schema
Properties
| Name | Type | Description |
|---|---|---|
| id | Integer |
No description |
| started_at | DateTime |
No description |
| finished_at | DateTime (Optional) |
No description |
Relationships
| Relationship Name | Type |
|---|---|
| tasks | ObservationTask |