@threadway/launcher
v0.1.0
Published
Local agent for a Threadway runner: a React UI + Node backend that acquires, pairs, supervises, and auto-updates the Rust runner (natively on Linux, via WSL2 on Windows). Retrievable over npm; resident via a login user-service.
Readme
Threadway Launcher
The machine-local agent for a Threadway runner: a small Node daemon + browser UI that gets the hardened Threadway runner onto this machine, keeps it updated from a signed release feed, pairs it with your organization, and supervises it as a resident background service.
Threadway runs agentic workloads inside hardened Firecracker microVMs (seccomp + Landlock + eBPF egress policy, signed audit chains, verified artifacts). The launcher is the part that lives on your machine: an installer + process supervisor around the runner binary. Everything operational — operator approval, work dispatch, pool policy, fleet health, session monitoring, audit — lives in the control plane and web console; the launcher links out to those rather than reimplementing them.
Install
npm i -g @threadway/launcher
threadway-launcherThat opens the UI in your browser and lands you in the first-run wizard: host check → runtime → pairing. The built-in host doctor diagnoses the prerequisites (WSL2/distro/KVM/Firecracker/guest tools) and can fix the fixable ones itself — apt-installing guest tools, installing a pinned official Firecracker release, installing the WSL distro.
Requirements
- Node.js ≥ 20
- Windows 10/11 with WSL2 (the runner and its microVMs run inside the WSL2
guest, which needs virtualization /
/dev/kvm), or Linux with KVM - A Threadway control plane to pair with (alpha — ask us)
What it does
- Acquires + updates the runner platform from a signed release feed:
content-addressed downloads with per-asset progress and resume, architecture
matching, downgrade (high-water-mark) protection, and idle-drain auto-update
with health-check rollback. Update policy is yours: daily maintenance
window, idle-only mode, per-incident deferral. Air-gapped hosts can
side-load a bundle directory from disk, verified against the same pinned
release key. Corporate proxies are honored end to end
(
HTTPS_PROXY/HTTP_PROXY/NO_PROXYor config). - Pairs via the runner's device-authorization flow (GitHub-CLI-style): the launcher shows the user code, an org admin approves it, the runner stores its SPIFFE identity and joins the pool over mTLS QUIC. A "forget identity & pair again" action recovers from a wrong pool or revoked cert.
- Supervises the runner: start/stop/restart, crash auto-restart with backoff, orphan reap, live status + log stream (persisted to disk). The process is owned by the daemon, so it survives UI reloads — and reboots, via the login service.
- Notifies the desktop when it matters: a pairing code appearing, an update applied or rolled back, the runner crashing — so a dead runner isn't first discovered by opening the UI.
- Records every local action in an append-only, hash-chained audit log (start/stop, config changes — key names only, never values — updates, rollbacks, identity resets, doctor fixes). Optional OTLP telemetry.
- Links out to the central web console for the operational depth (fleet, sessions, audit, intrusions) and exports a one-click, redacted diagnostics zip for support.
On Linux the daemon runs the runner directly; on Windows it runs it inside a WSL2 distro — the hardened microVM execution stays in the Linux guest where it has to be.
CLI
threadway-launcher # run + open the UI (default)
threadway-launcher serve # run headless (what the login service runs)
threadway-launcher open # open the UI in your browser
threadway-launcher install-service # start at login (no admin needed)
threadway-launcher service-status
threadway-launcher uninstall-serviceinstall-service registers a per-user login service — a systemd --user
unit on Linux, an HKCU Run key with a supervising helper on Windows, a
LaunchAgent on macOS — that runs the daemon headless at login. No elevation,
no installer; it points the OS at this npm-installed entrypoint.
Security model
- This package is thin by design. It ships only the compiled server + UI (well under 1 MB unpacked). Kernels, rootfs images, and runner binaries are never distributed via npm — they come from Threadway's release feed as a content-addressed bundle whose manifest is signed with a key pinned inside this package. A compromised npm package could lie in the UI, but it cannot mint a release-feed manifest that verifies against the baked public key.
- Tokenless publishing. Every version is published from CI via npm trusted
publishing (GitHub Actions OIDC) — no long-lived npm tokens exist anywhere.
Sigstore provenance attestations (visible via
npm view @threadway/launcher) attach automatically once the source repository is public; npm cannot generate them from a private repo. - The local API is authenticated. The daemon listens on loopback only and
mints a per-OS-user token (
api-tokenin the data dir, mode 0600); every request must carry it, because a loopback port reachable by any local user is not a trust boundary for an API that can run a binary.threadway-launcher openpasses the token in the URL fragment and the UI exchanges it for an HttpOnly session cookie; automation sends it as thex-launcher-tokenheader. DNS-rebinding and cross-origin upgrades are refused. - The runner does its own trust work. Artifact verification (signature, provenance, transparency-log, revocation gates), signed audit events, and identity live in the runner binary, not here.
Configuration
Settings are editable in the UI and persisted to a per-user config file
(%LOCALAPPDATA%\ThreadwayRunnerLauncher\config.json on Windows,
$XDG_CONFIG_HOME/threadway-runner-launcher/config.json on Linux). All paths
the runner touches are expressed in the guest (Linux) execution view — WSL
paths on Windows, native paths on Linux — so no path translation is involved.
Set Pool id after an operator approves the runner into a pool. If your WSL
is not in mirrored-networking mode, set cpUrl to the Windows host IP instead
of localhost.
Development
This package lives in the Threadway monorepo under threadway-runner/launcher.
npm install
npm run dev # backend on :4319, Vite UI on :4318 (open this)
npm test # unit + integration (fake runner host, in-process signed feed)
npm run typecheck && npm run lint
npm run build # tsc -> dist/server, vite -> web-dist
npm run pack-audit # what npm would ship: whitelist, size cap, no binaries/keysThe Vite dev server proxies /api + /ws to the backend. Prerequisites are
checked at Start with actionable errors; the control plane must be reachable at
the configured cpUrl.
Releases: tag launcher-v<version> (matching package.json) →
.github/workflows/release-launcher.yml runs the checks + pack audit and
publishes via trusted publishing, then verifies the provenance attestation
attached. workflow_dispatch on the same workflow is a dry-run rehearsal.
Charter — what the launcher owns (and what it doesn't)
The launcher owns: acquisition + update of the runner platform, host environment management (WSL2/KVM/prereqs), resident supervision, the pairing handoff, and thin local health. It deliberately does not own operator approval, SVID minting, work dispatch/lease/signing, pool policy, fleet/session dashboards, or audit-chain verification — those are control-plane concerns; a launcher that reimplemented them would drift. The runner binary itself is self-sufficient: device-auth pairing, QUIC join, claiming signed work, booting the hardened microVM, brokering the model credential, signing audit events, heartbeating, reconnecting. A runner is single-identity / one org-pool.
Remaining polish: an optional tray icon (the login service already survives reboot), and OS installer packaging if npm distribution proves insufficient.
