How it works¶
lote submit <host> <script> rsyncs your repo to the host, opens one ssh connection, and hands the script to that host's scheduler through the on-host executor. The same script lands on a plain box, a slurm cluster, or a pbs supercomputer with no code changes, because lote picks the backend from what the host actually has.
flowchart TB
subgraph control["control plane (lote, on your laptop)"]
direction LR
SETUP["lote setup<br/>onboard a host"]
SUBMIT["lote submit<br/>dispatch a job"]
PS["lote ps / reconcile<br/>track every host"]
end
subgraph onhost["on-host executor (lote exec, over ssh)"]
direction LR
DETECT["scheduler probe<br/>command -v qsub / sbatch"]
ADAPT["submit / run / monitor<br/>one script, adapts"]
end
subgraph clients["typed clients (thin wrappers)"]
direction LR
PUEUE["pueue<br/>plain host"]
SBATCH["sbatch<br/>slurm cluster"]
QSUB["qsub<br/>pbs cluster"]
BASH["bash<br/>no scheduler"]
end
RSYNC(["rsync the repo up<br/>lote.toml allowlist"]):::brand
DB(["state store<br/>.lote/db.json"]):::brand
SETUP --> RSYNC
SUBMIT --> RSYNC
RSYNC --> DETECT
DETECT --> ADAPT
ADAPT --> PUEUE
ADAPT --> SBATCH
ADAPT --> QSUB
ADAPT --> BASH
SUBMIT --> DB
PS --> DB
classDef brand fill:#eab308,stroke:#1a1a1a,stroke-width:2px,color:#1a1a1a;
The three layers¶
Control plane (lote) runs on your laptop. It onboards hosts with lote setup, dispatches jobs with lote submit, and tracks run state across every host with lote ps and lote reconcile. It never runs a job itself. It drives the on-host executor over ssh and records every dispatch to a local state store.
On-host executor (lote exec) runs on one host. It finds the job script, detects the scheduler the host actually has, then submits, runs, and monitors the job, auto-adapting to that scheduler. You can also run it by hand on a login node, but lote usually invokes it for you as chefe run lote exec ....
Typed clients are thin wrappers over the real tools (qsub, sbatch, pueue, rsync). Each parses that tool's output into typed records, so the executor reads a job's state the same way everywhere. New backends are new classes, never new if host == ... branches.
Scheduler auto-detection¶
lote onboards a host once with lote setup, probing it in a login shell so the cluster toolchain is on PATH. From what it finds, it routes every later job for that host.
| host | what lote finds | backend |
|---|---|---|
| a DGX or PC over ssh | no scheduler | pueue |
| a slurm cluster (e.g. miyabi) | sbatch |
sbatch |
| a pbs cluster (e.g. Pegasus) | qsub |
qsub |
| a plain login node | nothing schedulable | bash |
The same job.sh runs in all four. Job scripts guard module load with command -v module, so they no-op off a cluster, and positional arguments flow through an ARGS env var the script forwards to its entry point.
Dispatch flow¶
lote setup miyabi # probe + rsync + chefe install, then cache the host's facts
lote submit miyabi train.sh # rsync up, pick sbatch, submit, print + record the handle
lote ps # the run shows up across every host
lote reconcile miyabi # compare recorded runs with the live scheduler
lote pull <handle> # rsync the recorded results path back
A host becomes a target only after chefe install succeeds during onboarding, so a machine that cannot build the environment never becomes a target. After that, each dispatch is rsync up, one ssh call into lote exec, and one row written to the state store.
Next, the command reference and the configuration reference.