Wanix: a Plan 9 style namespace of files in the browser running Wasm, Go and Linux tasks
A compute toolkit for the web with the depth of Plan 9 from Bell Labs
At a glance
- What is it?
- Wanix is an embeddable runtime that brings a Unix-like environment to the browser, where processes, terminals, virtual machines and browser APIs are all addressed through one composable namespace declared in HTML. Tasks can be Go compiled to Wasm, WASI binaries, JavaScript workers or x86 Linux in an emulator, and no backend is required.
- Who is it for?
- Wanix fits a developer who wants to ship a reproducible sandbox into a page, an in-browser development environment, or an isolated place to run untrusted agent work without giving it a server. It does not fit a production application that needs a server-side backend, since the whole design assumes client-side execution, and it does not fit a team that wants the Plan 9 model without its learning curve.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The smallest possible namespace
Wanix is an embeddable runtime that brings a Unix-like environment to the browser. The whole system is declared in HTML, and the smallest useful example is four elements:
<wanix-namespace>
<wanix-bind type="file" dst="helloworld.wasm" src="./helloworld.wasm"></wanix-bind>
<wanix-task cmd="helloworld.wasm" term start></wanix-task>
<wanix-term path="#task/1/term"></wanix-term>
</wanix-namespace>There are three verbs in there. A bind mounts a source at a destination path inside the namespace. A task allocates and runs something shaped like a process, with args, environment and standard streams, and the start attribute makes it run as soon as the system is ready. A term attaches a terminal to that task through its path.
That composition is the Plan 9 idea applied to the browser, as the project's own tagline says, and the README describes the guiding principle as everything is a file: processes, terminals, VMs, browser APIs and storage are all exposed through one namespace you compose with binds, the same idea as Plan 9 with improvements.
The paths are worth understanding because they are not filesystem paths. System paths use a hash prefix, for example #ramfs or #web/opfs, and a task terminal lives at #task/<id>/term. Paths do not start with a slash.
Four bind types and four task drivers
The bind element has a type attribute that decides how the source becomes a namespace entry, and the default is ns, which binds another namespace path.
The file type writes the element's text content, or the content of a fetched URL when src is set, to the destination path. That is what makes an inline script possible: the element body becomes the file. The archive type fetches a .tar or .tar.gz and mounts it as a directory tree, which is how a Linux userland gets into the page. The import type pulls in a remote Wanix namespace over a WebSocket with a ws:// or wss:// URL, or through an iframe with 9P when the src URL carries a #system-id.
A bind also takes dst, src, perm and union. The permission mode for file binds defaults to 0644, and union mode when binding onto an existing directory defaults to after, meaning the new entries come after what is already there.
Task drivers decide what actually executes. The default type is auto, which determines the driver automatically. The js driver runs plain JavaScript as a task. The gojs driver runs Wasm compiled by Go using GOOS=js GOARCH=wasm, and the wasi driver runs any Wasm compiled for wasi or wasip1. A task can also carry a type attribute of gojs or wasi explicitly, a role attribute, an id or alias for referencing it, environment variables as space-separated KEY=VALUE pairs, a working directory, a filesystem base path, and stdin, stdout and stderr redirection to namespace paths.
Isolation is structural rather than a setting. Tasks run in their own namespace, by default inheriting the current or root namespace, and virtual machines export a guest namespace of their own.
Running it from a CDN with no build step
The quick start loads the runtime from a CDN as an ES module, which means an embedding page needs no build tooling at all. The published CDN paths are versioned:
<!DOCTYPE html>
<html>
<head>
<script type="module"
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/wanix.min.js">
</script>
</head>
<body style="height: 100vh; margin: 0;">
<wanix-namespace>
<!-- bind alloc ramfs to namespace root -->
<wanix-bind dst="." src="#ramfs/new"></wanix-bind>
<!-- bind inline file into namespace -->
<wanix-bind type="file" dst="hello.sh" perm="0755">
echo "Hello from Wanix!"
</wanix-bind>
<!-- bind wasm executable from url -->
<wanix-bind type="file" dst="rc.wasm"
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/rc.wasm">
</wanix-bind>
<!-- declare a task that will autostart -->
<wanix-task id="shell" cmd="rc.wasm -c hello.sh" term start></wanix-task>
<!-- show a terminal wired up to the task -->
<wanix-term path="#task/shell/term"></wanix-term>
</wanix-namespace>
</body>
</html>The runtime consists of wanix.min.js plus wanix.wasm, and it runs entirely client-side, so static assets can be hosted on any CDN with no backend. The example allocates a ramfs at the namespace root, binds an inline shell script as a file with mode 0755, binds a Wasm executable fetched from a URL, runs it as a task with a terminal, and then points a terminal element at that task.
The namespace element fires a ready event once it is usable, and an error event on load failure. At that point the filesystem handle is available from JavaScript:
<script type="module">
const sys = document.querySelector('wanix-namespace');
sys.addEventListener('ready', async () => {
console.log(await sys.root.readDir('.'));
await sys.root.writeFile('note.txt', 'saved from JS');
});
</script>So the same namespace that the HTML describes can be read and written from script, which is how a page embeds an environment and then interacts with it.
Virtual machines, terminals and the workbench
Three element types cover the visual and machine-facing parts. The wanix-vm element allocates and runs a virtual machine, with the backend given by a type attribute that defaults to v86. The wanix-term element shows a terminal, and the wanix-workbench element provides a VS Code style workbench. The README states that those are the only visual elements, with everything else being structural.
A namespace element has attributes for the Wasm module URL, defaulting to ./wanix.wasm next to the bundle, a debug flag that enables DevTools helpers and verbose logging, an id used as the system identifier for cross-origin import and export, and allow-origins listing the origins permitted to import the system via postMessage. The allow-origins attribute requires an id, and a wildcard value allows all origins.
Browser integration is handled through a #web namespace, where OPFS persistence, DOM control, web workers, service workers and fetch are first-class. That is a notable design choice: the browser's own capabilities are mounted as files rather than called through an API, so a program inside the sandbox addresses them the same way it addresses a file on disk.
The repository layout confirms the breadth of the implementation. There are directories for fs, term, vm, v86, wasi, wasm, web, workbench, gojs, rc, elements, api, native, cmd, examples, extras, docs and dev. The examples folder is a good map of the features, with basic namespace, terminal, VM and workbench examples, standalone variants of each, bind-import and bind-types examples, and separate REPL examples for gojs, rc and wasi.
Building the runtime and the Go toolchain behind it
The server side of Wanix is written in Go, with the module path tractor.dev/wanix and a go directive of 1.26. The JavaScript side is TypeScript built with esbuild, and the package manifest lists the browser-side dependencies: a WASI shim, the xterm terminal emulator with its fit addon, cbor-x for serialisation, a duplex helper and the ws WebSocket library. The npm package version is 0.4.0-beta.
The Dockerfile shows a three-way build. There is a TinyGo stage at version 0.43.0 and a Go stage at 1.26.0 on Alpine 3.22, and the runtime is produced by make targets named wasm-tinygo, js wasm and cmd. The Dockerfile also pulls a kernel image and an i386 Alpine image for the Linux VM extras, and builds two small Go binaries for the host side, wexec and hostexport, the latter built for GOOS=linux GOARCH=386.
The Makefile is described as self-documenting, so running make lists the available tasks with descriptions. The main targets are link, which installs the local Wanix command into a bin directory, all, which builds js wasm and cmd, examples, which builds and serves the example projects, and smoketest, which runs the Go test suite against those examples. Variables at the top can be overridden from the environment or the command line, for example make GOOS=linux, and the distribution targets cover darwin, windows and linux on arm64 and amd64.
The Go dependency list is unusual for a browser project and tells you what the runtime does under the hood: the 9P protocol implementation, a FUSE library for the filesystem layer, a pty library, a network stack, u-root's uio package and a TCP proxy.
Where Wanix is the wrong choice
The first thing to accept is that this is a research-scale project on an early version line. The releases go v0.3-preview, v0.3, then v0.4-preview, and the CDN example references a 0.4.0-rc2 build while the npm package is at 0.4.0-beta. There is no tagged 1.0, and the version numbers move between preview, beta and release candidate. Adopting it means accepting that an upgrade can change behaviour.
The second is the learning curve the Plan 9 model imposes. A team used to a normal application would find the namespace-and-bind approach unusual, and the fact that paths do not begin with a slash, that system paths use a hash prefix, and that a terminal is addressed as #task/<id>/term all need explaining. The README is explicit that the design goal is the depth of Plan 9 from Bell Labs, which is a feature for the people who want that and a cost for everyone else.
The third is the client-side assumption. The runtime runs entirely client-side and needs no backend, which is the point, but it means everything a task needs must arrive over the network, storage goes to OPFS, and CPU work happens on the visitor's machine. That is right for a sandbox, a demo or an agent environment, and wrong for a system where you need server-side control of resources.
The realistic alternatives are different in kind. A WebAssembly build of a real toolchain hosted on a service gives you the same binary without the namespace model. A container in the cloud gives isolation with a real operating system. And a plain web worker with a bundler gives you JavaScript execution without an operating system metaphor. Wanix is worth the extra machinery when you specifically want to run Linux in the page, or to compose an environment from binds rather than configure one, and it is worth less if all you need is to run a program.
Finally, the licensing is simple, MIT, and the project is active with the last push on 2026-10-20.
Editorial conclusion
Wanix fits a developer who wants to ship a reproducible sandbox into a page, an in-browser development environment, or an isolated place to run untrusted agent work without giving it a server. It does not fit a production application that needs a server-side backend, since the whole design assumes client-side execution, and it does not fit a team that wants the Plan 9 model without its learning curve. Before adopting it, try the small namespace example rather than the full quick start, confirm your Go code can be compiled with GOOS=js GOARCH=wasm or with TinyGo, and check the release you pin, because the published versions move quickly between preview, beta and release candidates.
Frequently asked questions
What is Wanix and what problem does it solve?
Wanix is an embeddable runtime that brings a Unix-like environment to the browser. You declare a namespace, bind files and archives into it, run Wasm, Go or JavaScript tasks, optionally boot Linux in an x86 emulator, and attach terminals and a VS Code workbench, all from HTML with no backend.
How do I run a Wasm task in a Wanix page?
Bind the binary into the namespace with a file bind, declare a task with the cmd attribute pointing at it and the term and start attributes, then point a wanix-term element at the task path. The task driver can be auto, js for plain JavaScript, gojs for Wasm compiled with GOOS=js GOARCH=wasm, or wasi for wasi and wasip1 builds.
What do the different Wanix bind types do?
The ns type, which is the default, binds another namespace path. The file type writes the element's text content or a fetched URL to the destination. The archive type fetches a .tar or .tar.gz and mounts it as a directory tree. The import type brings in a remote Wanix namespace over a WebSocket or through an iframe with 9P.
Does Wanix need a server to run?
No. The runtime is wanix.min.js plus wanix.wasm and runs entirely client-side, so static assets can be hosted on any CDN, and browser persistence comes from OPFS exposed through the #web namespace.
Can I write to the Wanix filesystem from JavaScript?
Yes. The namespace element fires a ready event, and the handler receives a filesystem handle where you can call readDir and writeFile, for example awaiting sys.root.readDir('.') and sys.root.writeFile('note.txt', 'saved from JS'). The full API is in api/handle.js.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tractordev-wanix)Was this useful?