dispatch can deliver a prompt to a tmux window on another machine — the same
delivery/auto-submit/confirmation logic, just executed over SSH.
dispatch send --machine spark01 --to work:agent --prompt "build it"
dispatch targets --machine spark01 # discover panes on the remote hostThe Runner abstraction is the only thing that changes between local and remote:
-
Local (
--machineomitted,local,localhost, or this hostname) →LocalRunnerruns tmux viaspawnSync. -
Remote →
RemoteRunnerquotes the tmux argv into a single shell command and resolves how to reach the host through the optional@hasna/machinesconsumer SDK:import { resolveMachineCommand } from "@hasna/machines/consumer"; resolveMachineCommand("spark01", "tmux send-keys ..."); // → { source: "tailscale", shellCommand: "ssh spark01.<tailnet>.ts.net 'tmux send-keys ...'" }
It picks the best live route (LAN address or Tailscale MagicDNS name), so it keeps working even when DHCP rotates an IP. If
@hasna/machinesisn't installed,dispatchfalls back to plainssh <machine> '<cmd>'(resolved by your SSH config / DNS).
Because tmux text payloads travel as a properly-quoted single argument and large prompts
are piped via stdin (load-buffer -), long and multi-line prompts cross the SSH boundary
without corruption — bracketed paste still applies on the remote pane.
Remote commands default to a 20s timeout because real tmux operations over SSH/Tailscale
can take several seconds during route setup. Override it with
DISPATCH_REMOTE_TIMEOUT_MS=<ms> when you need faster failure behavior, especially for
bulk runs against machines that may be down.
- Passwordless SSH to the target host (key/agent), reachable over LAN or Tailscale.
tmuxinstalled on the target host.- For Tailscale/LAN route resolution:
@hasna/machinesavailable (optional dependency). Without it, name resolution falls to your SSH config.
A scheduled dispatch carries its machine, so the daemon fires cross-machine dispatches
too:
dispatch schedule --machine spark01 --to work:agent --prompt "nightly" --cron "0 2 * * *"
dispatch schedule --machine spark01 --to work:agent --prompt "follow up" --in 30m
dispatch loop --machine spark01 --to work:agent --prompt "summarize status" --every 5m --name spark01-statusKeep the daemon always live on the machine where the schedule is stored. On Linux hosts:
dispatch daemon service install --start
dispatch daemon service status
dispatch daemon status --jsonThe daemon status includes the next due item and recent schedule/loop failures. Cross-machine
delivery failures are recorded on the schedule/loop as lastFailureAt,
lastFailureReason, and failureCount; interval loops retry at their next interval.