Instant computers powered by Omarchy that your AI agents can see, control, and operate.
A full Linux desktop, driven entirely through an API. Each machine runs Arch Linux and Hyprland in its own virtual machine, on hardware you already own. Creating one takes about a second. It accepts commands about seven seconds later.
$ npx mola-core fetching guest image omarchy-4.0.1-aarch64 guest image ready API http://127.0.0.1:4141/v1 Token 7_NmOSph4PWtcZP5QpVW4rJJ-9VIlIx3 $
Each machine boots Arch Linux with Hyprland under your platform's hypervisor. You get root, systemd, pacman, a compositor, and a browser. Install what you need. If you break the machine, delete it and create another.
The engine clones every machine from one base image using copy-on-write. A new machine therefore costs about a second and a few megabytes rather than a 16 GB copy. Machines share nothing after creation.
The engine listens on loopback only. You do not create an account, and no hosted service sits in the middle. Your data stays on your computer unless you send it elsewhere.
Two commands on macOS. One on Linux, if you already installed QEMU.
brew install qemu # macOS, Apple Silicon
sudo pacman -S qemu-full # Linux, and check /dev/kvm exists
Any QEMU build works. QEMU applies the macOS hypervisor entitlement to itself when it builds, so you need no Developer ID and no separate runtime.
npx mola-core
The first run downloads a 1.1 GB guest image and verifies its checksums as the bytes arrive. Expect a few minutes. The engine then prints an address and a token, and stays in the foreground until you stop it.
To check your machine first, run npx mola-core doctor. It tests each requirement and names whatever is missing.
export API=http://127.0.0.1:4141/v1
export TOKEN=$(cat ~/.mola/token)
curl -s -X POST $API/machines \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"name": "scratch"}'
A machine gets 4 vCPU, 4 GB of memory, and a 40 GB disk by default. Set vcpus, memory_mb, or disk_gb to change that. The response returns booting. The machine reaches ready about seven seconds later.
The engine writes its token, keys, guest image, and machine disks to ~/.mola. Set MOLA_HOME to move them. That directory sits outside the installed package on purpose: npx clears its cache without warning, and a machine's disk must outlive the tool that created it.
Send one action per request to POST /v1/machines/{id}/actions. The engine accepts nine.
| Verb | Body | What you get |
|---|---|---|
| exec | {"command": "ls ~"} | exit code, stdout, stderr |
| read_file | {"path": "~/notes.md"} | base64 bytes and size |
| write_file | {"path": "~/a", "content": "hi\n"} | bytes written |
| screenshot | {} | a PNG, base64 |
| click | {"x": 640, "y": 400} | ok |
| move | {"x": 640, "y": 400} | ok |
| scroll | {"direction": "down"} | ok |
| type | {"text": "hello"} | ok |
| key | {"key": "super-return"} | ok |
curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
-d '{"action": "key", "key": "super-return"}'
curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
-d '{"action": "type", "text": "fastfetch"}'
curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
-d '{"action": "key", "key": "enter"}'
Those keys are Omarchy's own bindings. super-return opens a terminal, super-space opens the launcher, and super-k lists every binding.
curl -X POST $API/machines/$ID/desktop -H "Authorization: Bearer $TOKEN"
{"data": {"desktop_url": "http://127.0.0.1:4141/desktop#t=...", "expires_in": 60}}
Open that URL in a browser to see the live desktop. The mouse and keyboard work. The ticket travels in the URL fragment, which browsers never send to a server, so it stays out of access logs and referrer headers. Each ticket works once and expires after 60 seconds.
The engine ships an MCP server, so Claude Code, Claude Desktop, Cursor and anything else that speaks MCP can drive a machine. You write config, not code.
claude mcp add mola -- npx -y mola-core mcp
Or, for a client that wants JSON:
{
"mcpServers": {
"mola": {
"command": "npx",
"args": ["-y", "mola-core", "mcp"]
}
}
}
Then ask for something that needs a computer.
> make me a linux machine, install neovim, and show me the desktop create_machine ready in 7s start_task pacman -S neovim check_task downloading... installed open_desktop http://127.0.0.1:4141/desktop#t=... Done. neovim 0.11.4 is installed. The link opens the live desktop, and it expires in a minute.
Seventeen tools: machines, the shell, files and the screen. screenshot hands the agent a picture of the desktop, which is what lets a model that has never heard of Mola look at a window and click the right thing.
run_command stops at 120 seconds, so installs and builds go through start_task, which detaches the process and returns a handle to poll. That is also how the agent reports progress instead of going quiet for four minutes.
The engine has to be running. If it is not, the tools say so and name the command, rather than failing in a way that looks like a broken server.
Full MCP setup, including Claude Desktop, Cursor, and what to do when something goes wrong.
Send Authorization: Bearer <token> with every request under /v1. GET /v1 returns the endpoint list, so an agent can discover the API itself.
| Endpoint | Does |
|---|---|
| POST /v1/machines | make one and start it |
| GET /v1/machines | list them |
| GET /v1/machines/{id} | describe one |
| POST /v1/machines/{id}/start | start a stopped one |
| POST /v1/machines/{id}/stop | shut down, {"force": true} cuts power |
| POST /v1/machines/{id}/actions | do something inside it |
| POST /v1/machines/{id}/desktop | a browser URL for the screen |
| DELETE /v1/machines/{id} | destroy it and its disk |
| Variable | Default | Does |
|---|---|---|
| MOLA_HOME | ~/.mola | token, keys, images, machine disks |
| MOLA_PORT | 4141 | listen port |
| MOLA_QEMU | found on PATH | use a particular QEMU |
| MOLA_IMAGE_URL | GitHub release | where the guest image comes from |
| MOLA_MAX_RUNNING | 2 | how many may run at once |
| MOLA_MAX_MEMORY_MB | 8192 | memory they may reserve between them |
A machine reports one of four states. Two of them need explanation.
| Status | Means |
|---|---|
| booting | QEMU is running, the guest has not reported in for this boot |
| ready | the guest reported in and its shell works |
| stopped | off, disk intact |
| unknown | the runtime cannot see it |
ready does not mean "the process started". QEMU can run while the guest still starts its network or its compositor. The engine waits for a heartbeat from the current boot before it reports ready. Your first command therefore reaches a machine that can answer it.
unknown reports that the runtime cannot see the machine. The engine returns it instead of guessing. A runtime that cannot see a machine has not reported that the machine stopped, and a platform that treats those two answers as the same loses track of machines it still runs.
Whoever uses a machine has root inside it. The engine therefore treats everything a guest reports as a claim about itself rather than a fact about the platform. It records the capabilities a guest reports, then decides readiness on its own.
A delete that cannot tear a machine down returns 502 and keeps the machine registered, with the reason attached, so you can retry. Dropping the record instead would leave a virtual machine consuming memory that you could no longer name or stop.
Read the line it names. On macOS, run brew install qemu. On Linux, check that /dev/kvm exists and that your user can open it. Inside a cloud VM, your provider must expose nested virtualisation, and many do not.
tail -40 ~/.mola/runtime/machines/<id>/console.log
HVF does not support GICv2 emulation means your QEMU needs a different interrupt controller. Set MOLA_GIC=3 and create the machine again. QEMU writes its own errors to runtime.log, beside the console log.
The capture reached the compositor mid-draw. Send an input event first, such as {"action": "key", "key": "super"}, then capture again.
Request a new ticket. Each one works once and lasts 60 seconds. Note that changing only the #fragment of a URL does not reload the page, so pasting a fresh ticket over a spent one in the address bar does nothing. Open the URL in a new tab.
The engine allows 2 running machines and 8192 MB between them. Raise either limit with MOLA_MAX_RUNNING or MOLA_MAX_MEMORY_MB, or stop a machine you are not using. Stopped machines consume disk only and count against neither limit.
At startup the engine adopts any machine the runtime holds but the registry does not list, and names it adopted-<id>. A previous engine died before it wrote the record. The machine runs normally, so delete it the usual way.
Most people use the downloaded image. Build your own to change the package set, or to avoid the download.
git clone https://github.com/obaid/mola-core
cd mola-core && npm install
python3 bin/native-prepare --output ~/.mola
The script writes ~/.mola/image, where the engine looks for it. If you pass a different --output, set MOLA_HOME to match. The script never overwrites an existing image, so move the old directory aside to rebuild.