Job Queue
Work that waits for a device instead of failing when none is free.
A fleet of personal machines is intermittent by nature — phones sleep, laptops close, iPads charge overnight. Through 0.7 every one of those was a failed request. The queue turns intermittency into a scheduling input: submit now, and the job runs when a node is free and fit to take it.
POST /v1/jobs queue a job, get an id and a positionGET /v1/jobs list jobs, counts, and why the queue is or isn't movingGET /v1/jobs/{id} one job, with its position and waiting reasonDELETE /v1/jobs/{id} cancel, refunding the stake if it never ranSubmitting
Section titled “Submitting”curl -X POST http://localhost:8420/v1/jobs \ -H "Authorization: Bearer $MYCELLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messages": [{"role": "user", "content": "Summarise this quarter."}], "min_tier": "capable", "stake": 10 }'{ "job_id": "q_d5cfe664a012", "state": "queued", "position": 1, "expires_at": 1787691541.9, "stake": 10.0}| Field | Meaning |
|---|---|
messages | Required. Same shape as a chat request. |
model | A model name, or mycellm/swarm. Empty / auto lets the scheduler resolve. |
min_tier | Quality floor: frontier, capable, fast, tiny. Only valid when model is empty. |
trust | Trust ceiling: local, trusted, any. |
token_budget | Ceiling on generated tokens across the whole job. |
fanout | Proposer count, for mycellm/swarm. |
stake | Credits staked for position. Staked, not spent. |
ttl_s | Seconds before the job gives up waiting. Default 24h; 0 never expires. |
model and min_tier together are rejected with a 400, not silently
reconciled. A floor constrains the node’s choice; once you name a model there
is nothing left to constrain, so a request asking for both cannot mean what it
says.
Why a job is waiting
Section titled “Why a job is waiting”Every queued job carries a waiting_reason in plain language, and the list
endpoint reports a device-level reason of its own:
{ "jobs": [{ "job_id": "q_d5cfe664a012", "state": "queued", "waiting_reason": "No frontier-tier model is reachable (3 model(s) online below that tier).", "position": 1 }], "counts": {"queued": 1}, "scheduler": {"running": 0, "reason": "Waiting — this device is in Low Power Mode or low on battery."}}This is not decoration. A queue that cannot say what it is waiting for is indistinguishable from a hang, and the two have completely different fixes: “go wake the Mac Studio” versus “take the phone off Low Power Mode”.
Scheduling
Section titled “Scheduling”A node takes queued work when it is serving, not power-constrained and not thermally throttled — the same telemetry it already advertises to the rest of the fleet, so a node cannot decide it is fine while telling everyone else it is throttled.
Order is deliberately boring:
priority = staked_credits + waiting_time_bonus…evaluated within an ownership class, because owner affinity is applied first and as a hard rule:
- Your own jobs outrank a stranger’s on your hardware, at any bid. Not “usually”, not “weighted heavily” — always. That is the property a marketplace cannot offer, and it must not be expressible as a number someone can outbid.
- Waiting time always accrues. Without it, any bidding converges on pay-to-play, unpaid work never runs, and the people donating hardware leave.
- A stake is staked, not spent. Refunded when a job expires or is cancelled unrun, so bidding for position is never a gamble on the fleet’s availability.
Durability
Section titled “Durability”A job is in SQLite before the caller is told it was accepted, and jobs left
running by an unclean shutdown are returned to the queue on the next start. A
queue that loses work on restart is worse than no queue, because the caller
stopped holding the request.
Configuration
Section titled “Configuration”| Setting | Default | Meaning |
|---|---|---|
MYCELLM_QUEUE_ENABLED | true | Off makes /v1/jobs answer 503 queue_disabled. |
MYCELLM_QUEUE_MAX_CONCURRENT | 1 | Jobs run at once. A personal device that starts three because three were waiting is a device someone force-quits. |
MYCELLM_QUEUE_POLL_INTERVAL | 5.0 | Seconds between polls while work is pending. |
/v1/jobs always requires authentication, including on a public bootstrap.
Queuing work on someone’s hardware for up to a day is not something anonymous
callers should be able to do.