A Linux sandbox that runs inside this browser tab: a shell, Python
3.14, NumPy, Jupyter. Nothing leaves the page.
Another tab of this site is already running a sandbox. Two share this
machine's CPU, so this boot will be much slower than the first; use that
tab, or close it first.
This looks like a tablet, so this page runs the Linux shell only:
Jupyter and the agent need more memory than a tablet browser gives one
tab. Even the shell may stall, or the tab may reload. A desktop browser,
or the desktop app, runs all of it.
The browser ran out of memory for new processes. Reload the page to
continue.
Terminalonline
Install a package from
YurtOS/yurt-packages into this sandbox: press
Install, wait for installed below the list,
then run the package's command in the terminal. The sandbox has no
network, so this page downloads the package, and the sandbox's own
pkg checks its signature and installs it, as
sudo pkg install NAME does in a sandbox with a network.
Installed packages live in this tab's memory, like every file here:
a reload loses them. A size is the package's own download; its
dependencies add theirs. A tablet or phone has much less memory, so
installs whose downloads add up to more than 16 MiB are refused
there.
loading the package list
$
BusyBox ash on a real PTY, on the Yurt kernel
compiled to WebAssembly. Loads about 90 MB once, then boots here
in the tab; the cell below starts a Jupyter kernel on the same
machine.
The sandbox has no network:
pip install and wget fail by design
(the desktop app below has both).
The sandbox did not start
Phone browsers give one tab too little memory for the sandbox and
reload the tab during the boot (iPhone Safari did, at
starting Jupyter), so this page does not start it
here. Open it in a desktop browser; the app and the command line
below run the same sandbox natively. To try on this phone anyway,
open this page with ?start-anyway.
This is a tablet, and mobile browsers rarely give a tab the memory
and background workers the sandbox needs. Open this page in a
desktop browser.
The browser reloaded this tab while the sandbox was starting. That
is what a browser does to a tab that outgrows the memory it is
allowed, and the boot needs a lot of it. The last thing the page
said before the reload:
The browser reported:
Reload the page to try again. If it keeps failing in a current
desktop browser, please
open an issue with the line above.
Run it on your machine
The same playground with the sandbox running natively: network access,
and a boot measured in seconds. Or the sandbox from a shell, the way
you would use docker.
It is not notarized, so macOS blocks the first open: allow it
under
System Settings → Privacy & Security → Open
Anyway (or run
xattr -dr com.apple.quarantine "/Applications/Yurt
Playground.app"), then open it again.
The playground as a Linux app
Download the package for
x86_64
or
arm64
(Debian, Ubuntu), then:
run, exec, ps,
logs, cp, stop,
kill, restart, commit,
inspect, stats, images,
pull — with ports, environment, a working directory
and an interactive terminal; commit turns a sandbox
you set up into an image to run again.
Download for Apple Silicon, then:
tar -xzf yurt-*.tar.gz && sudo yurt/install.sh
yurt pull playground # the image (about 90 MB), no account needed
yurt run -it playground # a shell in the sandbox
yurt run -d --name web -p 8080:8000 playground python3 -m http.server 8000
yurt logs -f web # Ctrl-C leaves it running
yurt stats --no-stream web # CPU % and memory, docker-stats style
yurt cp ./notes.txt web:/home/user/ # curl http://127.0.0.1:8080/notes.txt
yurt commit web my-image # yurt run my-image … starts from that disk
yurt stop web
Everything the command needs is in the package; the daemon starts
itself. yurt --help lists the verbs.
yurt on Linux
run, exec, ps,
logs, cp, stop,
kill, restart, commit,
inspect, stats, images,
pull — with ports, environment, a working directory
and an interactive terminal; commit turns a sandbox
you set up into an image to run again. Download the package for
x86_64
or
arm64
(Debian, Ubuntu), then:
sudo apt install ./yurt-*.deb
yurt pull playground # the image (about 90 MB), no account needed
yurt run -it playground # a shell in the sandbox
yurt run -d --name web -p 8080:8000 playground python3 -m http.server 8000
yurt logs -f web # Ctrl-C leaves it running
yurt stats --no-stream web # CPU % and memory, docker-stats style
yurt cp ./notes.txt web:/home/user/ # curl http://127.0.0.1:8080/notes.txt
yurt commit web my-image # yurt run my-image … starts from that disk
yurt stop web
Everything the command needs is in the package; the daemon starts
itself. yurt --help lists the verbs.
Is this really running in your browser?
Four checks you can do yourself
No badge can prove it, so here is what a server-backed fake could not
survive. Each takes under a minute, in Chrome, Edge, Firefox or
Safari.
Go offline, keep computing
Start the sandbox and wait for the cell to say ready.
Then turn off Wi-Fi (or, in Chrome, Edge and Firefox, pick
Offline in the developer tools' Network
panel). Run another cell.
It still answers. The terminal's bar shows offline
while you do it.
Watch the tab work
Watch this tab in the browser's task manager: in Chrome and Edge,
Shift+Esc; in Firefox,
about:processes; for Safari, macOS's Activity
Monitor, which lists each tab's web content process. Its memory
climbs by hundreds of MB as the sandbox boots, and its CPU spikes
when a cell runs.
A thin client stays flat.
See the workers
While the sandbox runs, list the page's workers: in Chrome, visit
chrome://inspect/#workers (in Edge,
edge://inspect/#workers); in Firefox,
about:debugging#/runtime/this-firefox; in Safari, the
Web Inspector's Sources tab. The coordinator and the
guest workers are listed with their script URLs, and you can
attach a debugger.
In the developer tools' Network panel, nothing moves
while a cell runs. The page's security policy forbids talking to
any other origin.
Check the bytes
The site publishes the SHA-256 of every file the sandbox runs on,
the 87 MB guest filesystem included. This fetches them again and
hashes them here, in your browser; the kernel and the image are
also checked against the hash their release was published with.
Suspend and resume
A notebook cell continues where it stopped
A sandbox can be sealed while it runs — the kernel's memory, each
program's memory and its unwound call stack — and brought back later
exactly where it was: no boot, no image to stage, no program
restarted. The
notebook
suspends and resumes a running cell; the
terminal demo
seals every two seconds.
In a notebook
Open the
suspend/resume notebook. Its kernel is one CPython 3.14
process (the Python 3 (Yurt, suspend/resume) kernelspec),
built so the sandbox can be sealed. Run the cell that prints
primes, press Suspend in the panel at the bottom right:
about 50 MB lands in IndexedDB in a quarter of a second and the
sandbox is gone. Press Resume: the cell carries on at
the next prime, in the same output, with every variable intact.
Interrupt works as usual, and primes is still there
afterwards.
What makes a cell suspendable is a syscall to seal at: printing
progress is enough; a loop that never leaves the interpreter has
to wait for its next print.
In a terminal
Open the demo. A prime finder prints
one prime every quarter second, testing each candidate against the
primes it has already found — its position is on the call stack,
its list is on the heap, and both come back. The bar counts the
seals as they land in your browser's IndexedDB (about 14 MB, under
50 ms each).
Close the tab. Open the page again: it says which seal it restored
from, draws a marker, and the next prime is the one after the last
sealed one. Forget the snapshot
drops the image so the next load boots fresh.
What is saved, and when
Nothing happens at close: the browser gives a closing tab no
chance to seal. Every seal overwrites the one before, so a restore
is at most one interval behind, and the interrupted syscall runs
exactly once, after the restore.
The image stays on this origin, in this browser, and is tied to
the kernel build that sealed it: after an update it is dropped and
the page boots fresh.
Use it from code
On the JS host, KernelHostInterface.sealSandbox()
returns the image and lets the sandbox continue;
KernelHostInterface.restore(kernelWasm, image)
brings it back in a fresh host with the processes ready to run. A
program opts in by being built with Asyncify on
yurt.syscall (it exports
yurt_asyncify_syscall); a seal of anything else is
refused rather than guessed at.
Today: one thread per program, terminals and timers carried;
sockets, in-flight host I/O and the stock image's shell are not
yet. See src/snapshot_worker.ts for the whole loop in
a page, and src/notebook_kernel_worker.ts for the
notebook kernel around a sealable CPython (scripts/build-python-seal.sh builds it).