machine-link
One command to prepare a rented or local machine for work.
mlink gpus --gpu a100 --max-price 2 # what your providers rent right now, cheapest first
mlink launch 1 --name trainer --up # rent row 1, wait for it, clone your repo onto it
mlink ssh # or plain: ssh trainer
mlink pull # rsync results back
mlink down # refuses on unpushed work, then destroys it
Highlights
- Rents GPUs from Prime Intellect and Verda, or adopts any machine you can already ssh to.
- Wraps the system
ssh,scpandrsync. No Python SSH library, no daemon, no agent on the box. - Every machine becomes a
Hostalias in~/.ssh/config, sossh trainerworks in git, rsync and VS Code Remote-SSH. - Reaches GitHub from the machine only through the forwarded agent. No key or token is ever copied anywhere.
- Refuses to destroy a machine that has uncommitted or unpushed work.
- Remembers which machine each project uses. Two projects, two boxes, no mix-ups.
- Depends on
typerandrichonly. Python 3.11+, macOS and Linux.
Providers
| Provider | Rent | Notes |
|---|---|---|
| Prime Intellect | yes | on-demand pods |
| Verda | yes | on-demand and spot instances |
| Local | no | any machine with a fixed address, listed under [[machines]] |
More providers are planned. Adding one is a single module; see Contributing.
Installation
uv tool install machine-link # or: pipx install machine-link
uvx --from machine-link mlink --help # try it without installing
Then, once per computer:
mlink init
init checks that your ssh key exists and is loaded in the agent, that GitHub greets it,
writes ~/.config/mlink/config.toml and the managed block in ~/.ssh/config, and registers
your public key at every configured provider. Rerun it any time; it is also the doctor.
Both init and up check that github.com accepts the key, so a GitHub account is assumed;
other git hosts are not supported yet.
Provider credentials go in the environment or in ~/.config/mlink/.env (chmod 600); init
turns on the providers it finds credentials for:
PRIME_API_KEY=... # app.primeintellect.ai > settings > API keys
VERDA_CLIENT_ID=... # Verda console > Credentials > Cloud API Credentials
VERDA_CLIENT_SECRET=...
Usage
Rent a machine
mlink gpus # GPU rows only, every provider, cheapest first
mlink gpus --spot # Verda spot prices
mlink launch 3 --name trainer # row 3 of the last listing
mlink launch 1A100.22V --region FIN-02 # or an offer id
mlink launch --gpu a6000 --up # or the cheapest match, then run 'up'
launch prints the offer and its price and asks before spending anything; with no terminal it
refuses unless you pass --yes. The machine is registered the moment the provider returns an
id, so a hiccup never leaves a billing machine you do not know about.
Prepare it
Put an mlink.toml at your project's root. An empty file means "clone this repository's
origin to ~/<name> on the box". Add sections as needed:
[[repos]]
url = "git@github.com:you/research.git"
dest = "~/research"
post_clone = ["uv sync"]
[[sync]] # for 'mlink pull'
remote = "~/research/runs"
local = "~/runs"
[provision]
commands = ["sudo apt-get install -y rsync tmux"]
script = "" # a local script, run on the box
mlink up # this project's machine
mlink up ubuntu@203.0.113.7 --name lab # or any address, registered on the spot
up waits for the machine, verifies agent forwarding and that GitHub answers from the box,
sets your git identity, runs [provision], then clones or fast-forwards the repos. It is
idempotent.
Work, then let go
mlink ssh -- nvidia-smi # one command, or an interactive session without arguments
mlink pull # rsync the [[sync]] paths back
mlink check # exit 5 if anything is uncommitted or unpushed
mlink down # the same check, a confirmation, then the machine is destroyed
The check covers every repo up deployed to the machine, whichever directory you run it
from. down treats a machine it cannot reach as unsafe. --force overrides the gate.
When a machine goes, so do its alias, its host key and its control connection; mlink forget
does the same without destroying anything.
How it works
Each machine gets one stanza in the managed block of ~/.ssh/config:
Host trainer mlink-trainer
HostName 203.0.113.7
User ubuntu
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
ControlMaster auto
ControlPath ~/.ssh/mlink-%C
ControlPersist 10m
UserKnownHostsFile ~/.config/mlink/known_hosts
StrictHostKeyChecking accept-new
Everything mlink does goes through that stanza, and so does anything else you point at the
name. Agent forwarding is scoped to your machines. All of up shares one multiplexed
connection. Host keys of rented machines live in mlink's own file and are deleted when the
machine is destroyed, so a recycled address never triggers a host-key warning. Everything
outside the block markers is left untouched; a one-time backup is kept at
~/.ssh/config.mlink.bak.
The pointer from a project to its machine lives in ~/.local/state/mlink/machines.json.
Commands run outside a project fall back to the last machine used and say so. Inside a project,
down never acts on another project's machine by fallback.
Configuration
~/.config/mlink/config.toml, written by init:
| Key | Default | Meaning |
|---|---|---|
ssh.identity_file |
~/.ssh/id_ed25519 |
The key; its .pub is registered at each provider |
ssh.default_user |
ubuntu |
Remote user when a target names none |
ssh.connect_timeout |
5 |
Seconds per connection attempt |
ssh.reachability_timeout |
600 |
Seconds to wait for a fresh machine to accept ssh |
git.name, git.email |
Set with git config --global on the box |
|
providers.prime.image |
the offer's first image | Prime pod image |
providers.verda.image |
ubuntu-24.04-cuda-12.6 |
Verda image |
providers.verda.location |
FIN-01 |
Used when an offer names no location |
machines[].name, .host, .user, .port |
Machines with a fixed address and no API |
A provider is enabled by the presence of its [providers.<name>] section; init writes the
sections for the providers whose credentials it finds and leaves the others commented out.
Annotated copies of both files are in examples/; every command and
flag is in DOCS.md.
Exit codes
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | unexpected error, or a remote command failed |
| 2 | local precondition or config problem; the fix is printed |
| 3 | machine unreachable, or it rejected the key |
| 4 | agent-forwarding chain broken |
| 5 | dirty or unpushed work |
Development
uv sync --all-groups
uv run ruff check && uv run ruff format --check && uv run pytest
Tests never reach the network or a real ssh. Provider fixtures are payloads recorded from the
live APIs, and an autouse fixture redirects $HOME so no test can touch your own files.
To release: set __version__, tag vX.Y.Z and push the tag. CI publishes to PyPI and creates
the GitHub release.
Contributing
Pull requests are welcome, providers especially. A provider is one module in
src/machine_link/providers/ with five methods (machines, offers, launch, terminate,
ensure_key) and a section name in the config; verda.py is a complete example. Add tests
against recorded API payloads and run the checks above before opening a pull request.
License
Metadata
Release files for machine-link 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| machine_link-0.1.6.tar.gz | 45.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| machine_link-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.5 kB
Release files / machine_link-0.1.6.tar.gz
| Download URL | machine_link-0.1.6.tar.gz |
|---|---|
| Size | 45.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1d6b5fff8bd7b2dd37d230749635825fdac4722e760a41d4d7340f62e1214170
|
|
BLAKE2b-256 checksum How to use checksums |
5fe3adc8ce4c1b6dfd29d17083ca9389a0dccc6ec331259855452ccc55179cac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / machine_link-0.1.6-py3-none-any.whl
| Download URL | machine_link-0.1.6-py3-none-any.whl |
|---|---|
| Size | 35.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7e06c7c3f3d64dd62b09bedf6f4ea518edba7d568bce5b42717b72f3d84216fa
|
|
BLAKE2b-256 checksum How to use checksums |
fff3f4668de8cdf13ea159f9911066fb944886a8e31fe9f999611f2a31cebbab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log