Queues
A Queue is the unified scheduling primitive in Lakeshore. It is a named pipe of pending work; daemons subscribe to it by name; the control plane hands each queued Invocation to whichever subscribed daemon claims it first.
There is exactly one primitive at the scheduling layer. Workers carry a
queues: string[] array recording which queues they pull from, and the
empty array means the default queue. Everything else — elasticity,
admission, work-stealing — is a policy field on the queue row, not a
second primitive.
Queue names must match ^[a-z0-9][a-z0-9-]*$.
What is on a queue row
| Field | What it holds |
|---|---|
kind | Scheduling kind — fifo (default), priority, boltzmann, filo. |
kindParams | Kind-specific knobs, e.g. { temperature: 0.5 } or { priority_field: "p" }. |
elasticity | The autoscaling spec. See Elasticity. |
admission | { max_depth?, on_full, rate_limit?, deadline_cutoff_s? } where on_full is reject or block. |
providerRef | The Provider the elasticity controller launches new daemons under. |
daemonTemplate | { instance_type?, image_id?, runner? } used when the controller launches. |
state | active, draining, or archived. |
All four kind values are accepted and stored, but the dispatcher's
claim always sorts { priority: -1, submittedAt: 1 } regardless of the
queue's kind. boltzmann and filo do not change dispatch order today,
and priority is really "priority field on the invocation, FIFO within
a tier" — it is not derived from kindParams. Likewise admission is
validated and stored but not enforced at admit time.
Membership
A daemon's queue membership lives on the worker row, not the queue
row — Worker.queues: string[] is the source of truth. queues show
walks the workers and reports who is pulling from a queue right now.
Default-queue membership is the empty array. A daemon launched
without --queue lands in the default queue. There is no sentinel
string written into Worker.queues by the system. (The membership
lookup does also accept a literal "default" entry if you put one
there, but the explicit add/remove routes reject default — the empty
array is the contract.)
Lifecycle
rm refuses with a 409 while the queue has members; --force cascades
by scrubbing the name out of every Worker.queues array first.
The schema also declares a paused state, but no route can set it —
active, draining, and archived are the three reachable values.
The default queue
You do not have to create it. The control plane materialises a default
row on the first queues ls or queues show default in a namespace,
with kind: "fifo" and the description "Default queue (auto-created).
Cannot be archived or deleted." It is a reserved name: archive and
rm both return 409.
Organization visibility
When namespaces are grouped by a shared orgId, queues ls aggregates
queues from every namespace in the org and stamps a namespace field on
each row so you can tell who owns it. Two namespaces in one org may hold
same-named queues; they are distinct rows scoped to their own namespace.
A token scoped to one namespace in the org also authorises requests against sibling namespaces — put the sibling slug in the URL path:
--dreamrc <path> is the CLI's only program-level option. The namespace
the CLI targets comes from LAKESHORE_NAMESPACE (when LAKESHORE_URL
is set) or from ~/.config/lakeshore/auth.yml. To reach a sibling
namespace, either re-login against it or call the HTTP route directly.
A worked example
Spin up two queues — one for CPU pre-processing, one for GPU training — and a few daemons for each.
Edit membership on a running daemon — no relaunch needed. The daemon picks the change up on its next poll:
Patch a queue in place:
CLI surface
The queues group uses two irregular verbs — ls (not list) and rm
(not remove). Everything else in the CLI uses the regular forms.
| Command | Notable flags |
|---|---|
queues add <name> | --description, --kind, --provider, --elasticity, --min, --max, --threshold, --instance-type, --image-id, --runner, --max-depth, --on-full, --json |
queues ls | --state <active|draining|paused|archived>, --json |
queues show <name> | --json |
queues patch <name> | --description, --provider (pass "" to clear), --min, --max, --threshold, --max-depth, --on-full, --json |
queues archive / unarchive / drain <name> | --json |
queues rm <name> | --force |
queues events <name> | --verbose, --limit <n> (default 50, max 200), --json |
queues stats <name> | --json |
Note that patch cannot change --kind or --elasticity <kind>. To
change the elasticity policy itself, PATCH the queue's elasticity
object over HTTP, or recreate the queue.
Shared pools
Multiple queues can share one worker fleet by carrying the same pool
name inside their elasticity object. Workers in the pool can claim
work from any member queue, and the controller scales the pool as a
single unit.
pool, match, steal_delay_s, and cost_weight have no CLI
flags — set them by PATCHing the queue's elasticity object directly:
See Elasticity — shared pools for the semantics.
HTTP
An unknown namespace on any of these is a 404 — the queue routes do not auto-create namespaces the way the secrets / providers / modes routes do.
Read next
- Elasticity — the four scaling policies you can declare on a queue.
- Scaling rules — the rule book the controller follows on every tick.
- Jobs — inspecting the invocations queued here.
- Compose — declarative queues + daemons in
lakeshore.yaml. - Python SDK — the producer side;
@udf(queue="…")lands on the queues you create here. /dev/queues— the developer-side design note.