RIXI
Run any project on a remote machine as if it were local — your code, your exact environment, and all. Securely. Even air-gapped. Even behind a firewall.
You have a laptop and a beefy box somewhere — a cloud GPU, an on-prem server, an air-gapped
cluster. Getting your work to run there usually means Dockerfiles, image registries, rsync,
SSH tunnels, “works on my machine” env drift, and inbound firewall holes. RIXI deletes all of
that. One command ships your project and its fully-resolved environment to the box, runs it as
an isolated task, and streams the output back live — and the remote job can even read and write
files on your laptop as it runs. It’s a secure, self-hostable remote executor: your code runs on
infrastructure you own — a cloud GPU, an on-prem server, an air-gapped box — in the exact
environment you tested, with nothing to reproduce and no inbound ports to open.
The 30-second version
# Not on PyPI yet — install the pinned release straight from git:
pip install "git+https://github.com/KellerKev/rixi@v0.11.0"
from rixi import Client
rixi = Client("http://127.0.0.1:9000") # add token="…" when the server has auth on
print(rixi.run(".", task="train").output) # package this dir + its env → ship → run → stream back
…or from the shell:
rixi run --server http://127.0.0.1:9000 --task train .
That single call packaged the current directory (code and its resolved Pixi environment),
uploaded it over an encrypted channel, ran the train task on the remote box in an isolated
subprocess, and streamed stdout back to you. No Dockerfile. No image push. No dependency drift.
Start a server locally in one line to try it — see Quickstart.
No server yet? One command makes one
Give rixi up a cloud API key and a size, and it creates the server, installs RIXI on it, and saves
a connection profile — the next rixi run just works:
rixi up --provider hetzner --size sample-cpu --name mybox # HCLOUD_TOKEN in the environment
rixi run --task train . # runs on mybox (the default profile)
rixi profiles # what's running, and what it has cost
rixi down mybox # destroy it — billing stops
| Size | Hetzner | Scaleway | AWS (experimental) |
|---|---|---|---|
sample-cpu — builds, tests, light jobs | cx23 | DEV1-M | t3.medium |
sample-gpu — basic ML and model inference | — | L4-1-24G (24 GB) | g6.xlarge |
The box serves plain HTTP on an open port, and RIXI secures it end to end: every request carries a
two-minute token signed by a key that never leaves your machine, and code, inputs, and output travel
AES-256-GCM-sealed under a key negotiated over RSA right after boot. Credentials come from flags or
the provider’s usual env vars (HCLOUD_TOKEN, SCW_SECRET_KEY + SCW_DEFAULT_PROJECT_ID, …); see
rixi up --help. Requires rixi 0.2.8+.
No cloud account? --provider eutonomous gets the box from the hosted service on EU-only
machines, paid from a prepaid wallet: create an API key at
rixi.eutonomous.eu/keys, export RIXI_API_KEY=rxk_…, then
rixi up --provider eutonomous --size cpu-s (also cpu-l, gpu-l4, gpu-l40s; --ttl hours).
The key only launches, tokens and shuts down boxes; the box itself is reached directly over TLS
with a token for that box alone. Everything below works the same on it. Requires rixi 0.4.0+.
Models from inside the box — your call. --model-access governed routes the box’s model
calls through eutonomous and the malgra policy engine (policy, PII rules, signed audit) with a
token that only works for that box; your eutonomous key never enters it. --model-access direct --model-url https://…/v1 --model-key … puts nothing in between: the box calls your own endpoint
with your own key. Either way OPENAI_BASE_URL / OPENAI_API_KEY are set for every command.
Requires rixi 0.6.0+.
Seal it. --egress block (or --egress allowlist --egress-allow 203.0.113.0/24,198.51.100.7:443)
closes the box down once it is ready, in its own cloud firewall — outside the box, where nothing
running on it as root can undo it. DNS to the box’s resolvers stays open; --allow-from limits who
may connect. Ship environments as offline bundles when egress is blocked. Requires rixi 0.3.0+.
Agents can drive a box command by command: rixi exec "pytest -q", rixi put, rixi get,
rixi ls (or Client.exec() / write_file() / read_file() in the SDK), confined to /workspace.
For a throwaway box per agent run, rixi.Sandbox wraps all of it:
from rixi import Sandbox
with Sandbox.create(provider="scaleway", policy={"egress": {"mode": "block"}}) as sb:
sb.write_file("main.py", "print(6 * 7)")
print(sb.exec("python3 main.py").stdout) # 42 — and the box is gone after the block
Long-running work, attach later. Start a pipeline task or any command in the background, leave, and come back from anywhere — several can run side by side:
rixi run --detach --task train . # prints a task id; the run keeps going
rixi exec --background "npm run dev" # any command, kept as a task too
rixi tasks # what is running, what finished (exit codes)
rixi attach <id> # replay its output, then follow (Ctrl-C detaches)
rixi attach <id> --no-history # only what it prints from now on
rixi input <id> "yes" # a line to its stdin
rixi restart <id> · rixi redeploy <id> . · rixi kill <id> · rixi logs <id>
The same from Python: Client.start(), spawn(), tasks(), attach(), send_input(),
restart(), redeploy(), kill(), output() — and on a sandbox, sb.spawn() /
sb.follow(id). Requires rixi 0.7.0+.
Say what you need. Instead of a size, rixi up --provider eutonomous --gpu-ram 40 (or --ram,
--vcpu, --gpu L4|L40S|any) gets the cheapest box that has it among the types your account
may use; the gateway’s require does the same for self-hosters. Requires rixi 0.11.0+.
Keep your work: volumes and stop/start (hosted). rixi volumes create data --size 50 makes
a disk that outlives boxes; rixi up --provider eutonomous --volume <id> mounts it at
/workspace/data/data, and the next box can attach it again. rixi stop powers a box off with
its disk, files and name kept (compute stops billing, the disk is billed per GB) and rixi start
brings it back; --on-idle stop makes an idle box stop instead of going away. From Python:
Sandbox.create(volumes=[…], on_idle="stop"), sb.stop(), sb.resume(). Requires rixi 0.10.0+.
S3 buckets (hosted). rixi buckets create results and rixi s3-keys create laptop give you
an S3 endpoint for results, artifacts and datasets, usable with aws-cli, boto3 or s3fs and billed
per GB stored and downloaded. rixi up --provider eutonomous --storage (or
--storage-bucket results, --storage-read-only) gives the box its own key — it dies with the
box — and sets AWS_ENDPOINT_URL_S3 and friends, so Metaflow’s S3 datastore works on the box
as is. Keys can be limited to some buckets and to reading; nothing can be made public. Self-hosters
get the same from the gateway’s S3 proxy. Requires rixi 0.12.0+.
A terminal on the box. rixi shell opens an interactive login shell in the workspace (a PTY
over a WebSocket; window resizes follow, the exit code passes through). On hosted boxes the
portal’s box page has the same terminal in the browser, connected to the box directly. Requires
rixi 0.9.0+.
Preview URLs. rixi preview 8000 serves port 8000 of a hosted box at
https://8000.b-<id>.run.eutonomous.eu/ with its own certificate, issued on the box. It is
private by default: the printed link carries a token, and opening it once sets a cookie for that
host. --public lets anyone with the URL in, and --close ends it. Ports that were not opened,
the box’s own API among them, never answer. From Python: sb.preview(8000). Requires rixi 0.8.0+.
Boxes as tools for your agent (MCP). rixi mcp serves boxes over the Model Context
Protocol: an agent can create a box, run commands and background processes on it, move files,
ship a Pixi project, and destroy the box. It runs on your machine and talks to the boxes directly,
so command output and files never pass through a hosted service. In Claude Code, Cursor or any MCP
client:
{"mcpServers": {"rixi": {"command": "rixi", "args": ["mcp"], "env": {"RIXI_API_KEY": "rxk_…"}}}}
Hosted boxes made this way shut down after ttl_hours (default 2); set RIXI_MCP_PROVIDER to
use your own cloud account instead. Requires rixi 0.8.0+.
rixi run, rixi stream, and the Python SDK work with these boxes directly (and so does any example
that uses them — drop --server and the profile is used). Offline bundling and the MCP
back-channel are in the full client (clients/), which works against these boxes too.
Hard problems RIXI makes easy
Each of these is normally a project of its own. In RIXI each is one command — because they all fall out of a single primitive: ship a project + its environment to a box and run it there.
| The hard problem | How RIXI makes it easy |
|---|---|
| “It runs on my laptop but the env won’t reproduce on the GPU box.” | RIXI ships your exact resolved environment (.pixi/) with the code. What you tested is what runs. |
| “The box is air-gapped — no internet to install anything.” | --offline-mode bundles the resolved environment into the upload; the server runs it with zero network. Fully reproducible on disconnected hosts. |
| “The box is behind a firewall with no inbound ports.” | The box dials out over an encrypted reverse tunnel; you then drive it as http://localhost:…. No inbound holes, ever. |
| “The remote job needs to read/write files on my laptop.” | An MCP back-channel exposes your local filesystem to the remote task as a tool — so a job running on the GPU box writes its results straight back to your machine. |
| “My laptop chokes on this compile / test suite.” | Any pixi.toml task runs on the remote box, one command, streamed live. It’s not just for ML — it’s for any heavy workload. |
| “I want throwaway GPU boxes, spun up on demand and gone when idle.” | For one box, rixi up --size sample-gpu creates it and rixi down destroys it. For a fleet, declare boxes by name in TOML; the gateway provisions them on demand (OpenTofu) and auto-tears-down on idle / TTL / release. |
| “Serve my fine-tuned model behind an API my tools already speak.” | Deploy the model as a keep-alive task, put the proxy in front, and call it from OpenAI, Anthropic, or Ollama clients — one model, three API surfaces. |
| “Kick off a long run, close my laptop, check back later.” | --keep-alive hands you a task id; reattach from anywhere and replay the output, or push a code change to the running task without tearing it down. |
| “I want a Metaflow step to run on my own box — a GPU, an on-prem server, whatever I can reach.” | Add @rixi to a step — it runs on a rixi box (one you point at, or gateway-provisioned), artifacts flow through S3, and the rest of the flow stays local. See Metaflow: @rixi. |
| “Give me the cheapest box that meets a spec, and cap what the fleet can spend.” | Request a box by capability (--gpu L4 --min-ram 24) and the gateway picks the cheapest match; max_eur_per_hour / max_fleet_eur bound the spend. See the gateway. |
| “I want a clean, throwaway CI runner for each job.” | The CI runner provisions a box, registers it as an ephemeral GitHub Actions runner, runs one job, and tears the box down. |
What makes RIXI different
Plenty of tools run code on remote machines. What’s genuinely unique here:
- It ships your exact environment, not a recipe. Because Pixi resolves the whole env into a
local
.pixi/folder, RIXI can send that folder with the code — which is also what makes true air-gapped execution possible. No Dockerfile, no registry, no “solve deps on the server and hope.” - Remote feels local — in both directions. Output streams back live; you detach and reattach by task id; you redeploy into a running task. And the remote job can reach back to your machine’s files through the MCP back-channel.
- Reaches machines nothing else can. Outbound-only reverse tunnels + a rendezvous gateway mean firewalled and NAT’d boxes need no inbound ports — they dial out to you.
- Secure by default, self-hosted by default. End-to-end AES-256-GCM, pinned-algorithm JWT auth, a tighten-only policy floor, hardened archive extraction — on infrastructure you own, with no vendor account.
RIXI at a glance:
| Capability | What it means |
|---|---|
| Self-hosted, no vendor account | runs entirely on infrastructure you own |
| Ships your resolved env (Pixi/conda) | the .pixi/ folder travels with the code — nothing to rebuild on the box |
| Reaches firewalled / NAT’d boxes | boxes dial out over a reverse tunnel — no inbound ports to open |
| Air-gapped / offline bundle | the resolved env rides along in the upload; zero network on the target |
| One-command cloud boxes | rixi up creates a Hetzner, Scaleway, or AWS box with rixi installed and a saved, secured profile |
| On-demand provisioning + auto-teardown | declare boxes in TOML; the gateway spins them up and reclaims them |
| Cheapest-box scheduling + budget caps | request the cheapest box matching a spec; cap the fleet’s hourly and total spend |
| Multi-API inference front door | serve a model as a task, call it from OpenAI / Anthropic / Ollama clients |
| Drive a long-lived task over HTTP | write to its stdin, proxy HTTP into it, hot-redeploy code — no downtime |
| Remote job reaches back to local files | the MCP back-channel exposes your laptop’s files to the task |
RIXI’s niche: a secure remote executor you fully control — strongest when boxes are firewalled, environments must reproduce exactly, or the network is air-gapped. It’s not a hosted SaaS and not a DAG orchestrator; pair it with your scheduler of choice.
See it work
Real command sequences. Simple runs use the lightweight rixi CLI (pip install "git+https://github.com/KellerKev/rixi@v0.11.0" — not on PyPI yet).
Advanced lifecycle features — offline bundling, attach-history, the interactive redeploy menu —
use the full client in clients/, shown here as rixi-client:
alias rixi-client='pixi run --manifest-path /path/to/rixi/clients/pixi.toml python /path/to/rixi/clients/rixi_client.py'
Train on a remote GPU, detach, reattach later
The pain it removes: no more babysitting an SSH session for a six-hour run.
$ rixi-client --server https://gpu-box:9000 --task train --keep-alive
Task ID: 9f0a… Status: running
epoch 1/50 loss=2.41
^C → choose "2) Let task continue and exit"
# hours later, from anywhere — replay history, then follow live:
$ rixi-client --server https://gpu-box:9000 --attach-history 9f0a…
epoch 27/50 loss=0.38
Status: Process completed
Deploy to an air-gapped box (env bundled, zero network)
The pain it removes: no internet on the target, no dependency solving, no drift.
$ pixi install # resolve the env into .pixi/ once
$ rixi-client --server https://airgapped-host:9000 --task run --offline-mode --validate-dependencies
📁 .pixi folder size: 1924.7 MB 🔒 Mode: Offline (dependencies included)
Status: Starting task with offline dependencies
The client tags the bundle; the server auto-detects it and runs with PIXI_OFFLINE=1. Nothing is
fetched or solved on the target.
Reach a server behind a firewall
The pain it removes: no inbound ports, no VPN, no port-forwarding tickets.
# reachable host (your laptop): accept the tunnel, expose a local port
$ RIXI_TUNNEL_SECRET=… pixi run listen -- --bind 127.0.0.1:9100 --ws-bind 0.0.0.0:7000
# firewalled host (next to the server): dial OUT and forward to the local rixi server
$ RIXI_TUNNEL_SECRET=… pixi run connect -- --to ws://laptop:7000 --target 127.0.0.1:9000
# now drive the firewalled server as if it were local:
$ rixi run --server http://127.0.0.1:9100 --task hello .
Hello from RIXI!
Let a remote job write results back to your laptop
The pain it removes: no scp round-trips to collect outputs — the remote job delivers them locally.
A CrewAI research crew runs on the remote box, generates a report, and writes it to your local
filesystem through the MCP back-channel — see
examples/crewai-showcase/. The remote task treats your machine’s
files as a tool it can call.
Provision → fine-tune → serve → tear down
The full lifecycle, end to end, in the flagship walkthrough:
examples/showcase-gpu-lifecycle/ — spin up a GPU, QLoRA
fine-tune on it, serve the result behind an OpenAI-compatible API, then destroy the box so billing
stops.
More runnable examples (inference, data pipelines, notebooks, native agent orchestration) in
examples/.
Metaflow compute backend: @rixi
RIXI ships a Metaflow compute backend. Decorate a step with @rixi and that step runs on a
box you control — a server you point at, or one the gateway provisions on demand and
tears down after — while the rest of the flow stays local. It uses the same seam @batch and
@kubernetes use, so it’s one line:
from metaflow import FlowSpec, step, rixi
class TrainFlow(FlowSpec):
@step
def start(self):
self.data = load() # local
self.next(self.train)
@rixi(resource="hetzner-cpu") # ← this step runs on a rixi box; the rest stay local
@step
def train(self):
self.model = fit(self.data) # heavy work, elsewhere — on infra you own
self.next(self.end)
@step
def end(self):
deploy(self.model) # back on your laptop, reads the model from the datastore
Artifacts move through a shared S3 datastore, so Metaflow itself carries inputs and results
between steps — a downstream local step transparently reads what the remote step produced. Point
@rixi at your Mac, a rented GPU, or a gateway-provisioned box; pay by the second; tear it down. It
brings Metaflow’s flow model to compute you run yourself — including firewalled or air-gapped boxes,
with the exact same environment reproduced on them. The idea is simple: the right machine for each
step, with direct access to it.
- Runnable sample (seconds, on a CPU):
metaflow-rixi/examples/regulatory_extractor/— a real ML flow that predicts a product’s regulatory profile from its public listing text; thetrainstep runs on a rixi box by changing one line, and the model returns via S3 (base 60% → trained 96% on held-out listings). - How it works + more flows:
metaflow-rixi/— the@rixidecorator (a pip-installed Metaflow extension, nothing forked), the minimal branching flow, and a Metaflow step running inside a Kubernetes pod.
Scale it up: the gateway
For a fleet instead of a single box, the gateway/ is a control plane — all open
source, in this repo. Your laptop and every compute box dial out to one rendezvous host, so the
gateway is the only machine that needs a public IP. It makes “get a box, use it, stop paying” a
declarative loop:
Provisioning & lifecycle
- On-demand provisioning across providers — declare boxes by name in TOML; the gateway stands them up with OpenTofu. Providers: Hetzner (CPU), Scaleway (CPU + L4/L40S/H100 GPUs), and Kubernetes — which runs a rixi box as a pod on your own cluster via kubeconfig, no cloud account (great on Rancher/k3s): resource ≠ VM. A custom-module hatch lets you bring your own OpenTofu.
- Auto-teardown —
idle/ttl/on_release/manual; a reaper reclaims boxes so billing stops. Warm pools (prewarm) keep a box ready for a no-cold-start first request. - Capacity retry — on a stock-out, fall back across regions and instance types before giving up.
Cost & scheduling
- Capability-based scheduling — ask for the cheapest box matching a spec (GPU, RAM, vCPU, arch) and the gateway picks it.
- Native cost tracking — per-provider pricing in
offerings.toml(auto-refreshed from provider APIs); live running cost and per-box lifetime cost via the admin API (/api/costs). - Budget caps — bound the fleet’s burn rate (
max_eur_per_hour) and cumulative spend (max_fleet_eur), enforced at provision time. - Spot / preemptible — wiring to request spot capacity, fall back to on-demand, and self-heal on preemption. (No shipped provider offers spot yet; the path is ready for an AWS/GCP module.)
Security & governance
- Brokered end-to-end encryption — it routes client↔box traffic but sees only ciphertext.
- Policy floor, JWT RBAC & audit — an admin floor users can only tighten, per-role and per-resource access rules, one-time provisioning tokens, and a queryable DuckDB + OTLP audit trail, with an admin API and web console.
- Encrypted OpenTofu state at rest (
RIXI_STATE_PASSPHRASE), with optional remote state backends for a highly-available gateway.
Drive it from Python with GatewayClient — request a box by name or by capability, wait for it, use
it, release it. See gateway/README.md.
A throwaway CI runner per job
ci-runner/ turns a rixi box into an ephemeral GitHub Actions runner: it
provisions a box, registers it as a self-hosted runner, runs exactly one job, then tears the box
down — a clean machine per job, with no standing runner to maintain. It’s built entirely on
GatewayClient + the rixi SDK (no gateway changes), mints a short-lived runner token per run, and
works on a real cloud box or a local Kubernetes pod (free on Rancher).
Serve models & long-lived services
A keep-alive task can be a service, not just a batch run. Drive a running task over HTTP — write
to its stdin, proxy HTTP straight into it, and hot-redeploy new code into it without
tearing it down. Put the proxy/ in front and the same served model answers OpenAI,
Anthropic, and Ollama API calls — one model, three API surfaces — with response caching,
model-name mapping, and metrics. The inference-server/ is a ready-made worker
(HuggingFace or Ollama backends, automatic device placement) you deploy as that task.
Agent orchestration
RIXI includes a native, config-driven agent engine (agent/): declarative multi-step
YAML workflows that chain MCP tool calls and route generation to a model served as a remote
task — no external framework required. It ships MCP tool servers (filesystem, web search), runs
hybrid local-tool + remote-generation workflows, and bridges to CrewAI and AutoGen when
you’d rather drive those. Tool access travels over MCP, hardened for the network by SMCP (below).
Secure by default
- End-to-end AES-256-GCM for data in transit; optional TLS (
--tls-cert/--tls-key). - RSA→AES key handshake — a client negotiates a per-session AES-256 key over an ephemeral RSA
keypair (
--key-secret), with use-bounded key rotation — no shared key to pre-distribute. - JWT auth with a pinned algorithm allow-list (no header-driven
alg— no algorithm-confusion), and a separate authorization gate on log retrieval. - Hardened reverse-tunnel crypto — a per-deployment KDF salt, 600k-round PBKDF2 + HKDF-split keys, and an HMAC auth proof (the shared secret is never sent).
- Loopback-by-default bind; a non-loopback bind refuses to start without auth unless you
explicitly opt into
--insecure. - Hardened extraction — uploads are unpacked with path-traversal/symlink filtering, task names are validated, and decompression size is capped.
- Secrets go via
RIXI_KEY_SECRET/RIXI_AES_KEYenv vars, never on the command line.
Full details and hardening checklist in SECURITY.md.
Secure MCP (SMCP)
MCP is a clean way to give an agent tools, but it’s designed for a local, trusted transport (typically stdio on the same machine). SMCP — Secure MCP — is a companion project that keeps MCP’s tool model exactly as-is and wraps it in a security layer so it works safely over a network and between agents: authentication, per-message encryption, and signed payloads, with tiered modes from a simple API key up to JWT + AES-256 and audited multi-agent (A2A) coordination. Same tools, now runnable across machines.
RIXI’s agent uses SMCP for exactly that networked case: WebSocket transport, Fernet (AES) on every
payload, api_key → JWT sessions, and an HMAC-SHA256 signature per message — so the same tools are
shareable between agents over an untrusted link. The handshake negotiates the protocol version by
MAJOR component (a future 3.x interoperates with 3.0; an incompatible 2.x/4.x peer is
rejected). See agent/README.md for RIXI’s implementation, and
github.com/KellerKev/smcp for the standalone SMCP project.
A2A federation (full peer). RIXI is a first-class SMCP federation peer — it can both send
(forward a client-authorized task to a downstream node with the user’s identity) and receive
(federated_key_exchange / federated_forward, invoked over tool_invoke). Federation is off by
default; when enabled it uses signed forwarding proofs (shared-secret HMAC or per-node RSA-PSS/PS256
with algorithm pinning), forward-secret P-256 ECDH session keys, AES-256-GCM session encryption
bound to the session id, and RS256 client-token verification. The crypto is self-contained in
agent/smcp_federation.py and verified byte-for-byte against the shared
cross-language conformance vectors (the same file malgra’s Rust implementation checks against).
Quickstart
Start a server on loopback (no auth needed for local dev), then ship the bundled hello example
to it:
# terminal 1 — the server
cd server && pixi install && pixi run python rixi_server.py --port 9000
# terminal 2 — ship examples/hello to it
pip install "git+https://github.com/KellerKev/rixi@v0.11.0" # not on PyPI yet
rixi run --server http://127.0.0.1:9000 --task hello examples/hello
Prefer a container? docker build -t rixi-server . && docker run --rm -p 9000:9000 -e RIXI_ALLOW_INSECURE=1 rixi-server
(see Dockerfile for authenticated/TLS options).
Prefer a cloud box? rixi up --provider hetzner --size sample-cpu creates one with rixi already
running and saves the connection; then rixi run --task hello examples/hello needs no --server.
See No server yet?.
Learn more
- README_second.md — the full reference: every server/client flag, custom headers, offline internals, the complete architecture diagrams, and deployment modes.
- examples/README.md — every runnable example, framed by the problem it solves (fine-tune, inference, data pipelines, notebooks, native + CrewAI agent orchestration).
- Component guides: server · clients · agent · proxy · inference-server · tunnel · gateway · metaflow-rixi · ci-runner
- CONTRIBUTING.md · SECURITY.md