API
Julia entry points for notebooks and packages. Day-to-day: CLI (julia --project=. -m DistSSHKit …); see Home, First Steps, and the User Guide. REPL help also works (?DistSSHKit.go!).
DistSSHKit.DistSSHKit — Module
DistSSHKit — meta-package for the run and the queue.
Users add this package. The command is julia -m DistSSHKit. DistSSHRun is the run. DistSSHQueue is the queue, started on a queue host. This module reexports their public names.
The shape mirrors the CLI: go! for as-is scripts, drive! (and friends) for Distributed drivers. pipeline! is an optional one-shot for the usual remote order. Worker placement uses the same tokens as the CLI (parent:2, child:user@host:1):
pipeline!(driver, "parent:2"; args=["8"])
pipeline!(
driver, "child:user@h1:1", "child:user@h2:1";
remote="/path/to/project", args=["8"],
)
go!("job.jl", "parent:2"; args=["8"])
drive!("job.jl", "parent:2"; args=["8"])This machine
Start a job here. The names below are defined in DistSSHRun and reexported by DistSSHKit. up is under Prepare hosts, not one of these calls.
Run a script as-is — go!
The job file does not call DistSSHKit. Each parent:N / child:NAME:N slot is one full run, concurrent. repeat=N is N runs in total (--repeat N), spread across listed hosts.
DistSSHRun.go! — Function
go!(script, workers...; kwargs...)
go!(script, workers::AbstractVector; kwargs...)Run an as-is complete job on one or more slots (local and/or remote).
go!("job.jl") # one parent slot
go!("job.jl"; repeat=100) # 100 independent runs on parent
go!("job.jl", "parent:2"; args=["8"])
go!("job.jl", "child:user@h1:1", "child:user@h2:1"; remote="/path/to/project")Each slot gets DISTRIBUTED_OUTPUT_DIR pointing at {script}/.distsshkit/go/<stem>_<UTC>/<slot>/. Override the batch root with output_dir (CLI: --output-dir). For backward compatibility collect_spec::AbstractString also sets the batch root, but passing both output_dir and collect_spec::String is an error. collect_spec === false means "skip collect" and is orthogonal to output_dir.
Default sync is false (no pre-run sync; remotes are checked first — setup is assumed done). Pass sync=:sync or sync=:rsync to copy, then check. After :rsync, hosts that still lack Manifest deps get instantiate! before the check. Use sync=:rsync only onto a missing/empty remote path (or setup --delete / setup!(session, :delete) first). go! has no git-parity gate; use drive! with skip_hash_check=false (CLI: drive --require-git) when you need that.
julia sets the Julia binary for each slot (nothing / "auto" → detect; same as CLI --julia).
When workers is empty and hosts_file is omitted, DISTSSHKIT_HOSTS_FILE is still read (API go!("job.jl")). Non-empty workers (CLI host_tokens) does not re-read that ENV.
parent:N and child:NAME:N mean N independent full-job runs (not Distributed workers), started together. repeat=N (CLI --repeat N) is the total number of those runs, spread round-robin across listed hosts (no tokens → all on parent). :N on a token is a per-host cap when repeat is set; omit it to leave that host uncapped. Without repeat, every listed token needs :N (omitting it is not 1 and not size!). Empty tokens stay one parent slot. path_anchor shortens displayed paths (CLI passes kit project root).
DistSSHRun.GoResult — Type
Outcome of go!. On failure, failed_step is "sync", "run", or "collect".
DistSSHRun.report_go_errors — Function
report_go_errors(result::GoResult; io=stderr)Print a short summary when go! failed. Returns result.ok.
DistSSHRun.KitRunResult — Type
Shared run outcome (ok, kind, dirs, failed_step, exit_code, hosts).
kind is :go, :drive, :ride, or :pipeline. Convert with kit_run_result. hosts is post-run collect (HostRunResult); empty for go, hung wait, or a drive that never collected.
DistSSHRun.kit_run_result — Function
Build KitRunResult from a kit outcome (:go / :ride / :drive / :pipeline).
DistSSHRun.report_run_errors — Function
report_run_errors(result; io=stderr)Print a short summary when a kit run failed. Accepts KitRunResult or a typed outcome (GoResult / RideResult / DriveResult / PipelineResult). Returns result.ok.
Drive work across workers
When the script is a driver (init_output_dir! / main, pmap, …), build a KitSession, then call the steps you need:
(optional setup! / sync! / instantiate!)
→ (optional size!) → drive! → (optional collect!)First-time remotes usually look like:
session = KitSession(
workers=["child:user@h1"], remote="/path/to/project", yes=true,
)
setup!(session, :rsync, :instantiate)
setup!(session, :check; ignore_julia_version=true) # optional
# Julia channel: `julia -m DistSSHKit up` and `up parent`.
# `up update` refreshes channels and leaves the default.
setup!(session, :runtest) # optional: job Pkg.test() on remotes
# git trees: setup!(session, :clone; repo="https://…") instead of :rsyncsetup! mirrors julia -m DistSSHKit setup --… (:delete, :rsync, :clone, :sync, :pull, :instantiate, :check, :runtest, :cleanup, :prune). Channel alignment is up / up update. The setup parser still accepts --juliaup and --juliaup-update. Confirmations follow session.yes. A matching confirm_path on setup!(session, :delete) or setup!(session, :prune) deletes even when yes=false. Omitting it still follows session.yes. Any other mode rejects confirm_path. :clone requires repo= — no silent origin lookup; clone runs on the remote. sync! / instantiate! remain as short aliases for the common deploy steps.
A few points that carry over from the CLI:
go!/drive!do not pre-run sync or require git parity by default. Passsync=:sync/:rsyncto copy first; after:rsync, missing Manifest deps getinstantiate!.pipeline!maysync!first but does not passsync=intodrive!, so it does not instantiate after rsync- Git parity (
skip_hash_check=false, CLI:drive --require-git) is drive / pipeline only —go!stays simpler - Listed
driveplacements must join, stay, and collect (require_all_hosts=true, the default). Passrequire_all_hosts=falsefor a 0.4.3-style partial run - Prefer positional worker tokens over building a
WorkerPlanby hand (WorkerPlanis the return type ofsize!) - Pass
julia=ongo!/drive!/pipeline!to pin the remote Julia binary (same as CLI--julia)
Or call pipeline! for optional sync → drive! → collect in one shot (pipeline! does not call setup!). pipeline_config_from_env reads DISTSSHKIT_HOSTS / DISTSSHKIT_HOSTS_FILE, SYNC_MODE (rsync / sync / off; unset → off for remotes too), JULIA_DISTRIBUTED_EXE (same as CLI --julia), and the usual quiet / progress / yes flags — same vocabulary as the CLI.
DriveResult.hosts is one HostRunResult per host that joined as a worker (empty when no host-collection step ran). The same vector is in kit.result for a detached caller that only has output_dir. HostResult is the per-host row for setup / sync, not drive collect.
DistSSHRun.KitSession — Type
Runtime context for sync!, size!, drive!, collect!.
DistSSHRun.setup! — Function
setup!(session::KitSession, mode::Symbol; kwargs...) -> SyncResult
setup!(session::KitSession, modes::Symbol...) -> SyncResultPrepare SSH hosts — same jobs as julia -m DistSSHKit setup --….
mode | CLI | Notes |
|---|---|---|
:delete | --delete | Destructive. A confirm_path that matches the delete path deletes even when yes=false. Without it, confirm unless session.yes |
:rsync | --rsync | Refuses nonempty remote; delete first to replace |
:clone | --clone | Requires repo=; clone runs on the remote |
:sync | --sync | Local push + remote pull (git remotes); confirm unless session.yes |
:pull | --pull | Local pull then remote pull; confirm unless session.yes |
:instantiate | --instantiate | julia= (default "auto") |
:juliaup | up | Align Julia via juliaup on child:NAME and/or parent ($HOME/.juliaup or Homebrew); confirm unless session.yes. Tip if kit parent patch lags channel latest |
:juliaup_update | up update | juliaup update on those hosts (no default); confirm unless session.yes |
:check | --check | ignore_julia_version=, check_code_sync= |
:runtest | --runtest | job Pkg.test() on remotes; julia= |
:cleanup | --cleanup | Kill stale workers (no confirm) |
:prune | --prune | .distsshkit go/drive/setup/runs leaves. A confirm_path that matches the remote project path deletes even when yes=false. Without it, confirm unless session.yes. older_days=, id= |
Confirmations follow session.yes (CLI -y). A matching confirm_path on :delete or :prune deletes even when yes=false. Any other mode rejects confirm_path. Multiple modes run in order and stop on the first failure:
session = KitSession(workers=["child:user@h1"], remote="~/proj", yes=true)
setup!(session, :rsync, :instantiate)
setup!(session, :check; ignore_julia_version=true)sync! / instantiate! remain as thin aliases for the common deploy steps. Prefer setup! when you want the full CLI vocabulary in one place.
DistSSHRun.sync! — Function
sync!(session::KitSession; mode=:sync)Sync local project to SSH hosts (call explicitly; go/drive do not call this by default).
mode=:sync— git push + pull on remotes (confirm unlesssession.yes)mode=:rsync— rsync working tree (no git; confirm unlesssession.yes). Refuses nonempty remote paths; usesetup!(session, :delete)/setup --deletefirst.
Also available as setup!(session, :rsync) / setup!(session, :sync).
Returns SyncResult.
DistSSHRun.instantiate! — Function
instantiate!(session::KitSession; julia="auto") -> SyncResultRun Pkg.instantiate() on each SSH host in session (parallel).
julia is the remote Julia path ("auto" detects per host, same as setup --instantiate / drive --julia). Returns SyncResult.
Typical first-time remote prep:
session = KitSession(workers=["child:user@h1"], remote="/path/to/project", yes=true)
setup!(session, :rsync, :instantiate) # or sync!(…; mode=:rsync) then instantiate!Also available as setup!(session, :instantiate; julia=…).
DistSSHRun.HostResult — Type
Per-host outcome for sync and similar operations.
DistSSHRun.SyncResult — Type
Outcome of sync! (rsync or git sync). cancelled is set when a confirm prompt is aborted.
DistSSHRun.drive! — Function
drive!(session::KitSession, script; plan=nothing, args=[], ...)
drive!(script, workers...; kwargs...)
drive!(script, workers::AbstractVector; kwargs...)Run a driver script on workers. Tokens match the CLI (parent:2, child:user@host:1). A file with no Distributed vocabulary warns (this-file scan only) and still runs. Load is the master include. Publish sends definitions to workers (not top-level work). Run is main() when defined. Use plan / go! / ride! when that matches the script.
drive!("job.jl", "parent:2"; args=["8"])
drive!(session, "job.jl") # uses `session.tokens`With sync omitted or false, prepare remotes with setup! or CLI setup first; remotes are checked before execution. Optional sync=:sync / sync=:rsync copies, then checks. After :rsync, hosts that still lack Manifest deps get instantiate! (same as CLI drive --rsync). Use sync=:rsync only onto a missing/empty remote path (or setup --delete / setup!(session, :delete) first). Git parity is off by default (skip_hash_check=true). With sync=:rsync, parity stays off even if skip_hash_check=false (no remote .git/). require_all_hosts=true (the default; CLI --require-all-hosts) fails unless every explicit parent:N / child:NAME:N joined, stayed through the run, and collect succeeded. Pass require_all_hosts=false or CLI --best-effort for a partial run.
mem_headroom / parent_gb are the RAM budget for drive preflight (same as size! / CLI --mem-headroom / --parent-gb). pipeline! passes config.mem_headroom and config.parent_gb.
julia sets the remote Julia binary (nothing / "auto" → detect; same as CLI --julia). plan is an optional explicit WorkerPlan. pipeline! syncs separately and does not pass sync= into drive!, so it does not instantiate after rsync.
sync_script=true (CLI --sync-script) re-includes the full driver on workers. The default publishes definitions / using / import / include only, so top-level work on Load is not repeated on workers.
DistSSHRun.DriveResult — Type
Outcome of drive! (and similar CLI steps that return an exit code).
ok is true only when the run finished with exit 0. By default that includes the placement contract: listed parent / child hosts joined, stayed, and collect succeeded (require_all_hosts=true). require_all_hosts=false keeps a partial run as ok=true.
output_dir / log_dir are the directories actually used for this run — resolved the same way drive reports Results: / writes its log, even when drive! was not called with output_dir= / log_dir=. nothing when no real run happened (e.g. built by hand) or, for log_dir, when logging was disabled.
hosts is one HostRunResult per host that joined as a worker, in the order results were collected. Empty when no host-collection step ran (e.g. built by hand, or a run with no remote/local-only hosts).
DistSSHRun.HostRunResult — Type
Per-host outcome of the drive result-collection step.
ok is false when SSH / find / rsync raises while collecting from host. An empty file list after a successful probe is not an error. error is the message string, or nothing on success. An AbstractString is stored as-is so kit.result round-trips; other values use sprint(showerror, error).
DistSSHRun.collect! — Function
collect!(
session::KitSession,
local_root::AbstractString;
merge=false,
hosts=nothing,
)Rsync result files from SSH hosts into local_root.
Collect modes:
merge=false→ collect-missing (CLIdrive --collect-missing)merge=true→ collect-overwrite (CLIdrive --collect-overwrite)
Distinct from drive's automatic post-run-new (sentinel / newer-than-run) and go's slot-overwrite. Returns CollectResult.
DistSSHRun.CollectResult — Type
Outcome of collect!.
DistSSHRun.ride! — Function
ride!(script, workers...; args=[], spi_check=false, output_dir=nothing, project=pwd())Experimental. Rewrite map / filter / simple comprehensions / independent indexed for (dest[i] = …) and run the script on Distributed workers (parent and optional SSH child:). Rejects Distributed vocabulary (use drive!).
Worker add for SSH children is the same add_drive_workers! path as drive. The job project is Pkg.activated on the parent first (drive does this before using the app package on every process). The script still runs on the parent (no driver include on workers). Named functions used by map / filter are sent to workers as a prelude.
--spi-check (default off) compares the distributed result to a sequential map / filter. Unknown syntax stays sequential. Inspect first with plan. Listed child: hosts are fail-closed (require_all_hosts=true). Queue: execute! :ride (detached=true writes kit.result like go; SSH child: also writes kit.hosts for terminate!).
DistSSHRun.RideResult — Type
Outcome of ride!. Experimental.
DistSSHRun.print_ride — Function
Print a RideResult. Field names match go / drive (Script:, Workers:).
DistSSHRun.DistSSHBase.stored_path — Function
stored_path(rel; project=pwd(), output=nothing) -> StringWhere rel is stored. output=nothing reads DISTRIBUTED_OUTPUT_DIR. A passed string is used as given and does not read that variable. "" means no output directory.
An absolute rel, after expanding a leading ~ on this machine, is returned as a canonical local path. The output directory is not consulted.
A relative rel uses the first match:
- The output directory is set and
relalready exists there. relalready exists underproject, even when the output directory is set.- The output directory is set, including when the file does not exist yet.
project, when the output directory is empty.
DistSSHRun.DistSSHBase.file_sha256 — Function
SHA-256 hex digest of file contents at path.
DistSSHRun.DistSSHBase.cache_file — Function
cache_file(src; project=pwd()) -> StringCopy src into .distsshkit/cache/sha256/<digest> if that blob is missing or stale. Same contents share one file (no second copy). Returns the cache path. Does not rsync by itself; .distsshkit/ is excluded from project sync.
DistSSHRun.DistSSHBase.cache_path — Function
Absolute cache path under project for hash.
DistSSHRun.DistSSHBase.cache_relpath — Function
Project-relative cache path for digest hash (64 hex chars).
DistSSHRun.push_cache! — Function
push_cache!(session; hashes=nothing) -> SyncResultRsync local .distsshkit/cache/sha256/ blobs onto each SSH host under the same relative path on the session remote project root. Does not use --delete. Hosts run at most DISTSSHKIT_JOBS at a time (default 1). Host result order matches session.hosts. Project setup --rsync still excludes .distsshkit/; this call is the dedicated transfer.
hashes limits the push to those digests. nothing pushes every local blob. No SSH hosts → error. Empty cache → success with no host rows.
DistSSHRun.cache_remote_dir — Function
cache_remote_dir(remote_root) -> StringJoin remote_root with .distsshkit/cache/sha256 (same layout as local cache_relpath).
Inspect a file — plan
No bang. Disk read + Meta.parseall only (no SSH, no exclusive gate). Suggests go / ride / drive. Does not start a job. Optional slot estimate calls size!; that probe is opt-in, so there is no plan!.
Inventory hosts — pool!
Cores and RAM on listed hosts, plus a slot hint. SSH, so bang. No RSS probe. Unreachable hosts stay listed with ok=false.
Occupancy — size!
RSS-based WorkerPlan used by CLI size and size!. go / drive / ride do not infer :N from occupancy. Paste printed parent:N / child:NAME:N tokens. go --repeat may still list bare pool hosts (omit :N = uncapped).
DistSSHRun.plan — Function
plan(script; session=nothing, workers=[], project=pwd(),
gb_per_worker=nothing, probe=nothing, mem_headroom, parent_gb) -> KitPlanInspect script without starting a job. Syntax only: Distributed vocabulary, map / filter / comprehensions, and independent indexed for (dest[i] = … with loop-var indices and no return in the RHS). Accumulating, stencil, or multi-statement for is out of scope. Effect analysis is not applied at this stage.
Suggests :drive, :ride, or :go. The caller still chooses the command.
Slot estimates (slots::WorkerPlan) run only when session is given, or when workers / gb_per_worker / probe request sizing. That path calls size! internally. Default is syntax only (no SSH probe).
DistSSHRun.KitPlan — Type
Result of plan. Does not start a job.
DistSSHRun.PlanFinding — Type
One syntax finding from plan.
DistSSHRun.print_plan — Function
Print a KitPlan (CLI and tests).
DistSSHRun.pool! — Function
pool!(session; gb_per_worker=nothing, mem_headroom, parent_gb) -> ResourcePoolProbe listed hosts for cores and RAM, then a slot hint via the same RAM/CPU formula as size! (no RSS measure_rss; that stays on size!). Unreachable hosts stay in the list with ok=false. The pool is ok only when every listed host probed successfully.
Does not start workers or rewrite go / drive membership.
DistSSHRun.ResourcePool — Type
Cluster view of listed hosts: totals plus per-host health.
ok is false if any listed host failed the probe (fail-closed). cores, gb, and slots sum reachable hosts only; failed rows stay in hosts. Does not change go / drive --best-effort.
DistSSHRun.HostInventory — Type
One host in a ResourcePool. slots is 0 when ok is false.
DistSSHRun.print_pool — Function
Print cluster totals first, then per-host health.
DistSSHRun.size! — Function
size!(
session::KitSession;
gb_per_worker=nothing, probe=nothing,
mem_headroom=DEFAULT_MEM_HEADROOM, parent_gb=DEFAULT_PARENT_GB,
)Estimate worker counts for hosts in session. When gb_per_worker is omitted, probes each host via measure_rss (package-load baseline, optional warm-up probe script for peak RSS). Counts use effective_worker_gb.
Returns WorkerPlan.
DistSSHRun.WorkerPlan — Type
Sized worker counts per host (parent_workers + child_workers).
DistSSHRun.pipeline! — Function
pipeline!(driver, workers...; kwargs...) -> PipelineResult
pipeline!(driver, workers::AbstractVector; kwargs...) -> PipelineResult
pipeline!(config::PipelineConfig) -> PipelineResultRun the usual remote workflow: optional sync, driver, optional collect. Does not call setup!; prepare remotes first.
Worker tokens match the CLI (parent:2, child:user@host:1). Listed tokens need :N. Keyword args are passed to the driver; remote is the remote project path. Default yes=true skips confirm prompts.
pipeline!(driver, "parent:2"; args=["8"])
pipeline!(driver, "child:user@h1:1", "child:user@h2:1"; remote="/path/to/project", args=["8"], collect=true)Remote hosts default to no pre-run sync; set sync=:sync / :rsync explicitly. Collect defaults on when remotes are present (collect=false to skip). Use sync=:rsync only onto a missing/empty remote path (or setup --delete / setup!(session, :delete) first). pipeline! sync=:rsync copies only; it does not instantiate missing Manifest deps. Use drive! with sync=:rsync, or setup! :instantiate, for that.
Returns PipelineResult; check result.ok or use report_pipeline_errors.
DistSSHRun.PipelineConfig — Type
PipelineConfigSettings for pipeline!: sync, worker tokens, driver run, and optional collect.
Worker placement uses CLI-style tokens (parent:2, child:user@host:1). Listed tokens need :N. Set sync=false to skip sync. Set collect=false to skip rsync-back. Git parity is off by default; pass skip_hash_check=false (or CLI --require-git) to require matching remote commits.
julia sets the remote Julia binary (nothing / "auto" → detect; same as CLI --julia). Prefer pipeline!(driver, workers...; …) for day-to-day use.
DistSSHRun.pipeline_config_from_env — Function
pipeline_config_from_env(; driver=...)Build PipelineConfig from environment variables.
| Variable | Role |
|---|---|
DISTSSHKIT_HOSTS | Comma-separated placement tokens (parent:N / child:NAME:N) |
DISTSSHKIT_HOSTS_FILE | Hosts file (appended after DISTSSHKIT_HOSTS, same order as CLI) |
DISTRIBUTED_REMOTE_PROJECT_ROOT | Remote repo root |
DISTRIBUTED_PROJECT_ROOT | Local project root |
DRIVER | Driver script path |
SYNC_MODE | rsync, sync, or off |
JULIA_DISTRIBUTED_EXE | Remote Julia path (same as CLI --julia; auto / unset → detect) |
DISTSSHKIT_YES / DISTSSHKIT_QUIET / DISTSSHKIT_PROGRESS / DISTSSHKIT_VERBOSE | Same as CLI -y / -q / --progress / --verbose |
DistSSHRun.PipelineResult — Type
Combined outcome of pipeline!. On failure, failed_step names the step that stopped.
DistSSHRun.report_pipeline_errors — Function
report_pipeline_errors(result::PipelineResult; io=stderr)Print a short summary when pipeline! failed. Returns result.ok.
Worker tokens — parent:N / child:NAME:N
host_tokens rebuilds execute! token strings from parse_go_args / parse_drive_args. Go keeps parser strings; drive emits parent:N from parent_workers and child:NAME:N for SSH. is_parent_host_name is the parent token (not a hostname).
DistSSHRun.DistSSHBase.is_parent_host_name — Function
Whether host denotes this job's parent in drive/go/size.
Only parent matches. parenthost is rejected by parse_placement_token. A token local / localhost / l is an ordinary SSH child (child:local).
Examples
julia> using DistSSHRun
julia> DistSSHRun.is_parent_host_name("parent")
true
julia> DistSSHRun.is_parent_host_name("parenthost")
false
julia> DistSSHRun.is_parent_host_name("local")
false
julia> DistSSHRun.is_parent_host_name("worker1")
falseDistSSHRun.DistSSHBase.host_tokens — Function
host_tokens(hosts::AbstractVector{<:AbstractString}) -> Vector{String}
host_tokens(hosts::AbstractVector{Tuple{String,Union{Int,Nothing}}}; parent_workers=0)
host_tokens(parsed; kind::Symbol) -> Vector{String}Rebuild CLI host tokens for execute!.
Go tokens are the parser strings. Ride uses the same shape. Drive tuples plus parent_workers emit parent:N then child:NAME:N. kind must be :go, :drive, or :ride.
One seam over go! / drive! — execute!
For callers that pick the kind at runtime: one function, one result type, instead of branching on kind yourself.
execute!(:go, "job.jl", ["parent:2"]; args=["8"])
execute!(:ride, "job.jl", ["parent:2"])
execute!(:drive, "job.jl", ["parent:2"]; args=["8"])
wait(execute!(:go, "job.jl", ["parent:1"]; detached=true, args=["8"]))detached=true:
- Spawns a child
julia -mofgo/ride/drive(not in-processgo!/ride!/drive!). When the chosen project's[deps]lists DistSSHKit, the child is-m DistSSHKit. Otherwise it is-m DistSSHRun. A Manifest-only or transitive name does not count. - Keywords are an allow-list;
yesmust staytrue - Child stdio defaults to
kit.out/kit.errinrun_dir KitProcessholds theBase.Process,run_dir, and artifactoutput_dirwhen known before spawn. Detached:drivewithoutoutput_dir=/ inheritedDISTRIBUTED_OUTPUT_DIRleaveskp.output_dirasnothing.waitdoes not write back intokp; the resolved leaf isKitRunResultoutput_dir(fromkit.result/run.toml)waitconverts it toKitRunResult. If the child wrotekit.result, that file wins (includinggo!failed_step). Otherwise a non-zero child exit yieldsfailed_step"go"/"ride"/"drive"only.wait(kp; timeout=N)returnsfailed_step="hung"/exit_code=124without killing the child
DistSSHRun.execute! — Function
execute!(kind, script, tokens=String[]; output_dir=nothing, args=String[], project=pwd(), sync=nothing, julia=nothing, detached=false, kwargs...) -> KitRunResult or KitProcessOne seam over go! / ride! / drive! for callers that pick the kind at runtime (kind ∈ (:go, :ride, :drive)), returning the shared KitRunResult instead of GoResult / RideResult / DriveResult.
execute!(:go, "job.jl", ["parent:2"]; args=["8"])
execute!(:ride, "job.jl", ["parent:2"])
execute!(:drive, "job.jl", ["parent:2"]; args=["8"])
wait(execute!(:go, "job.jl", ["parent:1"]; detached=true, args=["8"]))output_dir, args, project, sync, julia are the keywords go! and drive! already share (ride! ignores sync). With detached=false (default), any other keyword is forwarded to the chosen function.
detached=true spawns julia -m DistSSHKit go|ride|drive and returns a KitProcess. --project= is project= when that tree lists DistSSHRun in Project.toml [deps] (julia -m needs a direct dep); otherwise pkgdir(DistSSHRun). A Manifest-only / transitive DistSSHRun does not count. Keywords are then an allow-list (unknown names throw): output_dir, args, project, sync, julia, quiet, verbosity, yes, remote, hosts_file, job_id, and drive-only log_dir, enable_log, package, require_all_hosts, skip_hash_check, mem_headroom, parent_gb, workers, sync_script. Go-only repeat. Ride: spi_check (default false). Detached argv passes --spi-check when true and --no-spi-check when false, so an older child whose default is on still skips the compare. Neither go nor ride takes size! flags. yes must be true (the default): an unattended child cannot answer a prompt. remote that starts with ~ is stored in DISTRIBUTED_REMOTE_PROJECT_ROOT as a layout path (not expanduser on the kit parent). Child stdio defaults to kit.out / kit.err in run_dir. Pass stdout / stderr (IO) to override; stdout=stdout inherits the parent. Parent redirect_stdout does not apply to the subprocess. Detached :drive omits --output-dir unless output_dir= or inherited DISTRIBUTED_OUTPUT_DIR is set, so init_output_dir! can choose the artifact root.
job_id, if given, is passed to the child as DISTSSHKIT_JOB_ID, which adds job=<id> to every progress: log line. DISTSSHKIT_PROGRESS=1 is --progress verbosity, not a watcher; read lines with DistSSHRun.parse_progress_line / DistSSHRun.kit_progress_latest. Omitted entirely when unset.
DistSSHRun.allocate_output_dir — Function
allocate_output_dir(kind, script; project=pwd(), job_id=nothing) -> StringCreate a unique output directory for a later detached execute! and return its path. kind is :go, :drive, or :ride (the same values as execute!). The directory is created; pass it as output_dir=.
Layout is {script dir}/.distsshkit/<kind>/<script-stem>_<UTC-stamp>/. When job_id is set it is appended after the stamp (same charset as execute! job_id). The leaf is created with exclusive mkdir; if it already exists (same-second collision), a nanosecond suffix is retried so two allocations do not share a directory.
This matches omitted in-process defaults for go, ride, and drive: {script}/.distsshkit/<kind>/<stem>_<UTC>/. Drive still honors output_dir / --output-dir and a driver's init_output_dir! (DISTRIBUTED_OUTPUT_DIR) when those are set. Detached execute!(:drive) does not pin --output-dir unless output_dir= or inherited DISTRIBUTED_OUTPUT_DIR is set.
DistSSHRun.allocate_run_dir — Function
allocate_run_dir(kind, script; project=pwd(), job_id=nothing) -> StringCreate a unique Kit run directory (cancel / logs / run.toml), not the artifact output_dir.
Layout: {script dir}/.distsshkit/runs/<kind>/<script-stem>_<UTC-stamp>/. When job_id is set it is appended after the stamp (same charset as execute! job_id).
If that parent tree is not writable (read-only pkgdir / Registry install), the same leaf is created under {project}/.distsshkit/runs/…, then under tempdir()/distsshkit-runs/….
DistSSHRun.kit_run_dir — Function
ENV["DISTSSHKIT_RUN_DIR"] when set and non-blank, else nothing.
DistSSHRun.read_kit_run_toml — Function
Read run.toml from a run directory (or the file itself). nothing if missing.
DistSSHRun.execute_detached_accepts — Function
execute_detached_accepts(kw; kind) -> BoolWhether execute!(kind, ...; detached=true) accepts keyword kw. Uses the same tables as the detached throw path, plus the named parameters (output_dir, args, project, sync, julia, detached).
DistSSHRun.execute_kwargs_from_parsed — Function
execute_kwargs_from_parsed(parsed; kind) -> Dict{Symbol,Any}Keywords for execute! (detached=true) from parse_go_args / parse_drive_args / DistSSHRun.parse_ride_args. Keys are a subset of execute_detached_accepts.
Does not include hosts_file / --hosts: those tokens belong in host_tokens (kind required). Does not set project, detached, yes, job_id, remote, or stdio. Drive --workers is :workers only when the parser set default_workers.
DistSSHRun.KitProcess — Type
Handle to a detached execute! child (detached=true).
process is the julia -m DistSSHKit go|drive|ride subprocess. run_dir is always allocated in the parent before spawn (pid / kit.out / run.toml). output_dir is the artifact root when known at spawn (go / ride, explicit output_dir=, or inherited DISTRIBUTED_OUTPUT_DIR). Detached :drive without those leaves output_dir as nothing so the child can honor init_output_dir!. log_dir is nothing for :go / :ride. wait returns a KitRunResult; it does not fill output_dir on this handle.
Base.wait — Method
wait(kp::KitProcess; timeout=nothing) -> KitRunResultBlock until the detached child exits, then return a KitRunResult.
Does not mutate kp.output_dir / kp.log_dir. After a detached :drive that deferred the artifact leaf, read the returned result (or kit_result_from_dir / read_kit_run_toml on kp.run_dir).
timeout is wall-clock seconds until the child process exits (not the drive worker heartbeat). nothing waits forever. On timeout the child is left running: failed_step is "hung", exit_code is 124, and owned stdio stays open. Call terminate! if the hang is fatal.
If the child wrote kit.result under run_dir or output_dir, that file is the source of truth (including failed_step from go! / ride!). Otherwise failed_step is "go" / "ride" / "drive" on a non-zero exit — the parent cannot recover a more specific in-process step name.
Best-effort: remove kit.pid if it still names this child (pid captured before wait on the OS process; after reap getpid can throw ESRCH). Not on a hung timeout.
DistSSHRun.kit_pid_file_running — Function
kit_pid_file_running(output_dir) -> BoolWhether kit.pid names a still-running child of this run. Requires DistSSHRun.kit_pid_alive and, when a start key is in the file, a match with kit_process_start_key. A leftover after SIGKILL whose pid was reused is false. One-line (pid-only) files keep the old probe. Missing file is false.
DistSSHRun.terminate! — Function
terminate!(kp::KitProcess; grace=10, interrupt_grace=0) -> KitRunResultStop a detached run. interrupt_grace == 0 (the default) does not send SIGINT. A positive interrupt_grace sends SIGINT and waits that many seconds first: a drive child runs its Ctrl-C path (interrupt workers, rmprocs, exit 130). On Julia 1.13, a driver waiting in pmap does not run that path, and SIGTERM stops it. A process-group SIGINT, as from a terminal Ctrl-C, also misses that path on Julia 1.13: the driver exits 1 and does not send kill -INT. Measured on macOS. On Julia 1.14 the driver exits 130, but a worker can be removed before it sees the interrupt. A go or ride child is julia -m with exit-on-sigint, so SIGINT runs atexit and does not enter catch or finally. Then SIGTERM, wait up to grace seconds (0 sends SIGTERM and does not wait), then SIGKILL if the process is still alive. Then pkill only processes tagged with this run's job_id (from kit.job), never julia.*--worker. Without job_id, only the child is signaled.
DistSSHRun.terminate_run! — Function
terminate_run!(output_dir; grace=10, interrupt_grace=0, log_dir=nothing, kind=:go) -> KitRunResultLike terminate! after losing KitProcess: read kit.pid / kit.job / kit.hosts under output_dir. interrupt_grace is the same SIGINT stage. kind is only used when kit.result is missing.
DistSSHRun.kit_result_from_dir — Function
kit_result_from_dir(output_dir) -> Union{Nothing,KitRunResult}Read output_dir/kit.result written by a finished go / ride / drive child. nothing when the file is missing or unreadable (still running, or a hard death).
DistSSHRun.drive_host_status — Function
drive_host_status(output_dir) -> Vector{DriveHostStatus}
drive_host_status(kp::KitProcess) -> Vector{DriveHostStatus}Live per-host membership for a running (or recently collecting) drive or ride. Reads kit.hosts.status. Empty when the file is missing (too early, a go run, or a hard death before join). This is not DriveResult hosts (post-run collect); that vector is stored in kit.result as hosts.
DistSSHRun.DriveHostStatus — Type
Live per-host membership during a drive (not the post-run collect outcome).
state is :joined, :alive, :left, or :collect_pending. last_seen is Unix time when this host last appeared in Distributed.workers(), or nothing if it has not been observed since join.
Progress lines (external watchers)
When a kit log file is open (go_*.log / drive_*.log), go and drive append progress: lines. The same lines go to kit.progress in output_dir (even with --no-log). The last event (done) is written regardless of verbosity. begin / step / item lines appear only in --progress mode (DISTSSHKIT_PROGRESS=1 for a child process).
Each line is space-separated key=value fields after the event name. t= is Unix time (seconds) at the end of every line. Events:
begin:kind, optionaljob,label,total,tstep:kind, optionaljob,label,done,total,cur,titem:kind, optionaljob,label(item),status(pending/running/ok/fail),done,total,tdone:kind, optionaljob,ok,done,total,t
kind is go, ride, or drive. job= is present only when job_id / DISTSSHKIT_JOB_ID is set. Fields are not quoted; labels are chosen by the run (phase names or slot labels) and do not contain spaces.
Watchers can tail kit.progress (or the log next to it) and use julia -m DistSSHKit progress DIR. DISTSSHKIT_PROGRESS=1 is --progress verbosity on the child, not a watcher. Slot-level go artifacts (go_manifest.txt, {slot}/go.exitcode) remain the source of truth for per-slot exit codes. kit_progress_latest stays on DistSSHKit for scripts that parse those lines; it is not exported. parse_progress_line and kit_progress_phases are DistSSHRun names and are not on DistSSHKit.
Helpers for detached runs
Opt-in extras on go! / drive! (in-process or detached) for a caller that runs many jobs.
Still holding KitProcess
terminate! with the default interrupt_grace=0 SIGTERMs the child, waits grace for its rmprocs path, then SIGKILLs if needed, then pkills only argv tagged with this run's job_id. A positive interrupt_grace sends SIGINT first and waits that many seconds: a drive child runs its Ctrl-C path (interrupt workers, rmprocs, exit 130); a go or ride child is julia -m with exit-on-sigint, so SIGINT runs atexit and does not enter catch or finally. On Julia 1.13, a drive driver waiting in pmap does not run that path, and SIGTERM stops it. On Julia 1.14 the driver exits 130, but a worker can be removed before it sees the interrupt. kp.process is still a Base.Process if you need kill yourself.
Sidecar files
Detached stdio (kit.out / kit.err) lives in KitProcess run_dir. kit.pid, kit.job, kit.hosts, kit.hosts.status, and kit.result are written under run_dir, and also under output_dir / log_dir when those paths are known at write time. .kit.lock stays on the artifact output_dir (exclusive run against that leaf). Logs (go_*.log / drive_*.log) default to run_dir for drive when DISTSSHKIT_RUN_DIR is set; go still writes go_*.log next to the batch.
On-disk contract:
.kit.lock: pid of the process holding the artifact dir. A second process against the same path raisesArgumentError. A lock left by a dead pid is reclaimed. Two in-process runs share a pid, so overlapping calls are also rejected:go!/drive!/ride!/size!/pool!/setup!/sync!/instantiate!/collect!/push_cache!/pipeline!(same-task nesting is ok).kit.out/kit.err: detached child stdio whenstdout/stderrwere omitted (run_dir).kit.pid: child OS pid, optional start key on the second line. Running iskit_pid_file_running(pid plus start). Removed on a normal finish. A leftover is SIGKILL / crash.kit.job:job_idwhen set.terminate_run!uses it for taggedpkillafter a restart.kit.hosts: remote hosts this run started (one name per line), written after workers join (gochildren,driveSSH joins,rideSSH children).terminate_run!reaps these. Parent-only ride writes nothing.kit.hosts.status: live per-host membership duringdrive/ride(:joined/:alive/:left/:collect_pending). Read withdrive_host_status. Not the post-run collect vector. A second in-processdrive!/ride!while the monitor is running throws.kit.result: TOML with the same fields asKitRunResult, includinghosts(post-run collect,HostRunResultrows). Omitted when empty (go, or adrivethat never collected). Read withkit_result_from_dir. Missing while running or after a hard death.kit.progress:progress:lines for watchers (kit_progress_latest). Written even when--no-logskipsdrive_*.log.
Together: running (kit.pid live and start matches, no result), finished (result present), or died hard (leftover pid that is dead or reused, no result).
Handle gone
terminate_run! reads kit.pid / kit.job / kit.hosts and uses the same signal-then-tagged-pkill sequence as terminate!. Without job_id, only the child pid is signaled.
Before spawn
allocate_output_dir: create a unique artifact directory under{script}/.distsshkit/<kind>/for a lateroutput_dir=. Omittedgo/ridedefault is still{script}/.distsshkit/<kind>/<stem>_<UTC>/. Detacheddrivedoes not pin that path before spawn, soinit_output_dir!can setDISTRIBUTED_OUTPUT_DIR.allocate_run_dir: run bundle{script}/.distsshkit/runs/<kind>/<stem>_<UTC>/(run.toml, pid, stdio). Falls back to{project}/.distsshkit/runs/…thentempdir()/distsshkit-runs/…when the script directory is not writable.execute_kwargs_from_parsed: mapparse_go_args/parse_drive_argsonto detachedexecute!keywords. Ride argv maps the same way for:ride. Hosts stay inhost_tokens;:workersis drive--workerswhen set.:log_dir/:mem_headroom/:parent_gb/:workersare drive-only except ride also takes:spi_check(defaultfalse); go / ride do not take size flags;:planis never accepted.job_id(execute!keyword, orENV["DISTSSHKIT_JOB_ID"]for in-processgo!/drive!):job=<id>on everyprogress:line. Drive workers get a comment-only--eval=#distsshkit-job:<id>; go slots-La no-op file of that name so the user script staysPROGRAM_FILE.terminate!pkills that argv tag. Unset: tagging is off; teardown cannot be host-scoped..kit.lockin the resolvedoutput_dir: a second run against the same directory raisesArgumentError. A lock left by a dead pid is reclaimed.
CLI parsers and helpers
Exported so a rename is a Semver break, not a silent DistSSHKit._… change. go / drive argv wrappers stay unexported.
Parsers / SSH helpers (parse_*, ssh_opts, run_on_host, …) are for callers that share this CLI.
Help / display helpers (print_help_*, print_colored, SPINNER_FRAMES) are an extension surface aimed at DistSSHQueue.jl (and similar). Guaranteed: names and call signatures. Not guaranteed: exact rendered text, colors, or spinner glyphs — presentation may change without a breaking version bump.
dist_ssh_kit_version and println_kit_version are defined in DistSSHKit. The other names in this section are defined in DistSSHRun and reexported. ssh_opts, run_on_host, the path helpers, and the help helpers live in DistSSHRun.DistSSHBase. SPINNER_FRAMES, print_cli_error, and the go / drive parsers live in DistSSHRun.
DistSSHRun.parse_go_args — Function
Parse go CLI arguments (same shape as other kit CLI parsers).
DistSSHRun.parse_drive_args — Function
Parse drive CLI arguments (same shape as other kit CLI parsers).
DistSSHRun.show_go_usage — Function
Print go --help (same chrome as julia -m DistSSHKit go -h).
DistSSHRun.show_drive_usage — Function
Print drive --help (same chrome as julia -m DistSSHKit drive -h).
DistSSHKit.dist_ssh_kit_version — Function
Version of this meta-package, from its Project.toml.
DistSSHKit.println_kit_version — Function
println_kit_version(io::IO=stdout)Print two lines: this package, then DistSSHRun and DistSSHQueue.
DistSSHRun.DistSSHBase.ssh_opts — Function
ssh_opts(; request_tty=false) -> Vector{String}SSH argv flags for ssh / scp / rsync -e.
Reads DISTRIBUTED_SSH_OPTS live (not frozen at package precompile). Prefer this over a const so E2E / ProxyJump overrides apply in the same process.
A non-empty DISTRIBUTED_SSH_OPTS replaces the default vector (it is not merged). It is split with Base.shell_split, so a quoted value that contains spaces stays one argument. An unmatched quote throws ArgumentError. request_tty only changes the default vector: false includes -o RequestTTY=no; true omits it so a caller may put ssh -t before or after these flags. The ENV replacement is unchanged (if it contains RequestTTY=no, put -t after the vector). Use -o RequestTTY=no, not ssh -T: these flags are also passed to scp (scp -T is unrelated). Does not insert -t (that flag is ssh-only).
DistSSHRun.DistSSHBase.run_on_host — Function
run_on_host(host, argv; julia=nothing, detect=true, tty=false, wait=true) -> Base.ProcessOne SSH connection: resolve remote Julia the same way as resolve_remote_julia / detect_julia_path, then exec it with argv (Julia flags / script / args). Does not replace resolve_remote_julia when the caller only needs the path.
julia=nothing / "auto" with detect=true probes candidates on the remote. detect=false requires an explicit path. tty=true adds ssh -t. Process-local detect cache is not used (a new CLI process never hits it).
SSH is ignorestatus: a non-zero remote or ssh exit returns the Process (.exitcode) instead of throwing ProcessFailedException. Missing ssh on PATH throws ArgumentError.
DistSSHRun.DistSSHBase.resolve_controller_julia — Function
Resolve the Julia binary on this kit parent (nothing / "auto" / empty → the running process). Explicit paths are kept as given after usability check.
Throws ArgumentError when the binary is missing or --version does not parse.
DistSSHRun.DistSSHBase.canonical_local_path — Function
Absolute local path with ~ expanded (canonical form for file I/O and comparisons).
DistSSHRun.DistSSHBase.short_path — Function
Shorten absolute paths by replacing the home directory prefix with ~.
DistSSHRun.DistSSHBase.resolve_pkg_project_dir — Function
Walk upward from start_dir to find the directory that should be passed to Pkg.activate on workers.
If the first Project.toml found is a vendored DistSSHKit, DistSSHQueue, or DistSSHRun stub (name is one of those) and the parent directory also has a Project.toml, skip it and keep walking. Scripts next to that stub then use the application project root. The folder name does not matter.
DistSSHRun.DistSSHBase.resolve_pkg_env — Function
Pkg environment for project (a directory, or a Project.toml path).
project_dir is the directory of that Project.toml (--project). manifest is Base.active_manifest of that file, or nothing when Pkg has no lock yet. env_dir is the directory of manifest, or project_dir when there is no lock. That is the tree setup --rsync sends.
Queue: default_queue_env (no dedicated ~/.distsshqueue/env) should return env_dir. stage_job_tree! should rsync env_dir, and set DISTRIBUTED_PROJECT_ROOT to the staged project_dir (relative to that tree). Serve --queue-env is env_dir. A job's --project stays project_dir, because env_dir as --project misses [deps] that exist only on the member.
DistSSHRun.explain_script_not_found — Function
Full script/driver-not-found message (headline + optional demo-related hint).
The demo tip stays here because it knows the bundled examples.
DistSSHRun.print_cli_error — Function
CLI user error on stderr (no stacktrace).
DistSSHRun.DistSSHBase.print_help_chrome — Function
Kit help chrome — every overview / --help starts here:
DistSSHQueue setup
──────────────────Exported for family callers. The signature is stable. Glyphs and colors are not.
DistSSHRun.DistSSHBase.print_help_section — Function
Section heading (dim) with a trailing blank line so body lines follow immediately.
Section
<blank>
body…DistSSHRun.DistSSHBase.print_help_lines — Function
One or more verbatim help body lines.
DistSSHRun.DistSSHBase.print_help_blank — Function
One blank line in kit --help output.
DistSSHRun.DistSSHBase.print_colored — Function
Print msg with color when the kit would use ANSI (TTY, no NO_COLOR).
DistSSHRun.SPINNER_FRAMES — Constant
Frames for the kit TTY spinner (kit_spin!) and matching queue chrome.
print_colored is the public name; _print_colored is the same function.
Default ssh_opts() is BatchMode and RequestTTY=no. ssh_opts(; request_tty=true) omits RequestTTY=no (does not insert -t; that is ssh-only). Setting DISTRIBUTED_SSH_OPTS replaces that vector; -F alone does not keep RequestTTY=no. Use -o RequestTTY=no, not ssh -T (scp -T means something else). run_on_host(; tty=true) still requests a pty. Non-zero exit is .exitcode on the returned Process (no ProcessFailedException).
Inside a driver — worker_pmap
World-age escape hatch when a driver needs pmap-like fan-out after defining methods in the same session.
DistSSHRun.worker_pmap — Function
worker_pmap(f, collection)Like Distributed.pmap, but evaluates f with Base.invokelatest on each element.
After drive syncs your driver to workers and loads the project package, plain pmap is usually enough. Use worker_pmap when a callback still hits Julia 1.12+ world-age errors (for example, a function reference or closure captured before package load).
Prepare hosts — up
up puts the same Julia channel on each machine. From Julia, setup!(session, :juliaup) does the same work as up, and :juliaup_update does up update. See up. setup! is defined in DistSSHRun and reexported by DistSSHKit.
Always-on machine
Leave jobs on a queue host (an always-on machine). They run one at a time. The names below are defined in DistSSHQueue and reexported by DistSSHKit.
A client runs julia -m DistSSHKit submit …, with qhost:HOST when that machine is another computer. On that machine the commands are qhost setup, qhost up, qhost add-host, qhost remove-host, qhost serve, qhost stop, qhost enable, qhost disable, qhost teardown, plus qhost size / qhost plan / qhost pool. Bare setup / up / size / plan / pool stay the commands you run on this machine.
q = Queue(; store=default_store_path(), follow_config=true)
id = submit!(q, "SCRIPT.jl", "child:host1:4"; kind=:go)
cancel!(q, id)
serve!(q)DistSSHQueue.Queue — Type
FIFO table: one DistSSHKit job at a time. Placement tokens are Kit's, not a Queue ceiling.
DistSSHQueue.Job — Type
One table row. hosts are DistSSHKit placement tokens (parent[:N] / child:NAME[:N]). kwargs is an opaque bag for DistSSHRun execute! (project is the Kit project).
DistSSHQueue.submit! — Function
Enqueue. kind is a DistSSHRun execute! kind in KIT_EXECUTE_KINDS (:go, :ride, :drive).
hosts must be DistSSHKit 0.4 placement tokens (parent[:N] / child:NAME[:N]). Missing project uses job_project() (cwd / DISTRIBUTED_PROJECT_ROOT), not the serve env (enable --queue-env / Julia --project= that loaded Queue). Same verb as CLI submit.
q.allowed is the inventory (Queue(; allowed=…)), names plus optional max :N. It does not read config.toml. CLI submit uses Queue(; follow_config=true) so each enqueue re-reads hosts without restarting serve. step! does not drop :queued rows when a name is later removed, and does not stop a Kit job that is already :running.
DistSSHQueue.cancel! — Function
Cancel :queued, or :running via DistSSHKit terminate_run! when the Kit sidecar dir is known.
DistSSHQueue.jobs — Function
Copy of the table.
DistSSHQueue.job — Function
Copy of one row. id may be a unique prefix of the stored UUID.
DistSSHQueue.load! — Function
Load store (stale :running → :failed).
DistSSHQueue.step! — Function
Start the FIFO head if nothing is :running. At most one Kit job.
DistSSHQueue.serve! — Function
Load store (stale :running → :failed), then step! until interrupt. Ctrl-C stops serve, not Kit.
A second serve on the same store prints Already running and returns.
DistSSHQueue.serve — Function
serve(; store=default_store_path(), interval=0.2) -> NothingRun serve! on store. Ctrl-C stops this process, not a job that is already running. The CLI rejects a non-finite --interval. interval == 0 still runs and warns once; a negative finite value warns once, then sleep fails.
DistSSHQueue.default_store_path — Function
Jobs file for a queue host: ~/.distsshqueue/jobs.toml under home.
DistSSHQueue.job_project — Function
Kit project: DISTRIBUTED_PROJECT_ROOT, else the Project.toml above cwd, else cwd.
Not the Julia --project= that loaded DistSSHQueue.