# Quickstart Source: https://wack.sh/docs wack gives the agent you already use its own cloud computer, called a **box**. It is a persistent Linux machine with the usual tools already installed. It sleeps when idle, wakes in about a second, and keeps its files. ## 1. Get your box Sign up at [https://wack.sh](https://wack.sh). Your 7-day trial starts right away, with no card. It includes 10 awake hours on the Hobby plan. Your first box, called "my box", is set up in the background. **Get set up** on Boxes shows **Box ready** once it's done, usually within about 20 seconds. ## 2. Connect your agent Click **Connect agent** under **Get set up** on Boxes (or **+** next to **Connect an agent** in the sidebar) and pick your agent: Claude Code, Codex, Cursor, Claude, VS Code or any other MCP client. Copy the one command, config or link it shows. For Claude Code that's: ``` claude mcp add --transport http --scope user wack https://api.wack.sh/mcp/wac_… ``` The page waits for your agent and turns to **Connected** by itself. Your agent gets all your boxes, and can make new ones when a job needs more than one. [Connect](/docs/connect) has the exact steps for each client, and how to connect an agent to just one box. The URL at the end is a secret: anyone who has it can use your boxes. Keep it out of shared repos and screenshots. If it leaks, rotate it under **Settings › Connection**. ## 3. Give it a job Ask for something that needs a real computer. For example: - "Turn this CSV into a chart and a one-page PDF report." - "Take screenshots of our pricing page on mobile and desktop." - "Clone my repo, run the tests and summarize the failures." The box keeps what your agent makes. Deliverables go to `/mnt/user-data/outputs`, and you can download them from the box's **Files** tab in the dashboard. ## 4. Let it work while you're away Your box keeps its files while you're away, but it sleeps 10 minutes after your agent's last call, and anything left running in it pauses until it wakes. To have work happen without you, create an [automation](/docs/automations): a prompt or a script that runs on a schedule or from a webhook, then emails you a summary with a link to the files. The quickest way in is one of the templates on the Automations page, such as **Daily digest**. ## Where to go next - [Boxes](/docs/boxes): what's installed, how sleep works and where files go. - [Sizes and GPU boxes](/docs/sizes): bigger machines for builds and dev servers, and a GPU when the job needs one. - [Screen](/docs/screen): watch your agent drive a real browser, and take control when you need to. - [Setups and images](/docs/setups): the same Chrome version, repos and fonts in every box. - [Limits](/docs/limits): what each plan includes and what happens when you reach a limit. --- # Connect your agent Source: https://wack.sh/docs/connect Your agent reaches wack through a **connect URL**, an MCP server address. There are two kinds: - **Your account's URL** looks like `https://api.wack.sh/mcp/wac_…`. It gives an agent all your boxes, and lets it make new ones and delete the ones it no longer needs. This is the one to use. - **A box's URL** looks like `https://api.wack.sh/mcp/wbx_…`. It gives an agent just that box, for when it should see nothing else. The URL is the credential. Anyone who has it can run commands in the boxes it reaches, so treat it like a password. Both kinds stay hidden until you click **Show**. **Rotate URL** gives out a new one, and the old one stops working at once; update every agent that used it. Rotating your account's URL leaves box URLs alone, and the other way round. ## The guided setup Click **+** next to **Connect an agent** in the sidebar, or **Connect agent** under **Get set up** on Boxes. Pick your agent, then copy the one command, config or link it shows. The page waits for your agent and says **Connected** as soon as it makes contact. Your account's URL is also under **Settings › Connection**, with **Show**, copy and **Rotate URL**, and every client's setup under **Manual setup**. To connect an agent to just one box, open the box: its **Overview** walks you through it while no agent is connected, and **Connect an agent** in the box's ⋯ menu starts it again. The box's URL is in its **Settings** tab, under **Connection**. ## The one-line setup The quickest way to set it up by hand is to let your agent do it. Copy the setup line (from the guided setup, under **Other ways to connect**) and paste it into your agent: ``` Read https://wack.sh/SKILL.md and connect me to my wack boxes: https://api.wack.sh/mcp/wac_… ``` The agent reads [SKILL.md](https://wack.sh/SKILL.md), adds the server to its own settings without overwriting your other servers, tells you if it needs a restart, and then runs a check in a box. The guided setup shows the connection as soon as the agent makes contact. To set it up yourself, follow the steps for your client below (the same ones are under **Manual setup** in **Settings**). Replace `` with your connect URL. ## Claude Code ``` claude mcp add --transport http --scope user wack ``` `--scope user` makes wack available in every project. Start a new session, or run `/mcp` in a running one, to see the wack tools. ## Codex Add this to `~/.codex/config.toml`: ``` [mcp_servers.wack] url = "" tool_timeout_sec = 900 ``` `tool_timeout_sec` lets long commands finish: `box_exec` can run for up to 900 seconds, which is longer than Codex waits by default. Restart Codex after saving. ## Cursor Pick **Cursor** in the guided setup for an **Add to Cursor** button that installs the server in one click. To do it by hand, add this to `~/.cursor/mcp.json`: ``` { "mcpServers": { "wack": { "url": "" } } } ``` ## Claude (desktop, web and mobile) Claude adds remote servers as custom connectors, and they follow your Claude account: 1. In Claude, open Customize › Connectors. 2. Click Add custom connector, name it wack and paste the URL. 3. If Claude asks how people sign in, choose No sign-in. Then click Add. On Team and Enterprise plans an owner adds it under Organization settings › Connectors (Add › Custom › Web); members then click Connect. ## VS Code ``` code --add-mcp '{"name":"wack","type":"http","url":""}' ``` Or add it to `.vscode/mcp.json` in a workspace: ``` { "servers": { "wack": { "type": "http", "url": "" } } } ``` ## Other clients wack speaks MCP over streamable HTTP, so any client that supports remote servers can connect. Clients name the fields differently: - Most clients take `"type": "http"` (some say `"streamable-http"`) and a `"url"`. - Gemini CLI uses `"httpUrl"`. Also set `"timeout": 900000` so long commands can finish. - Windsurf and Devin use `"serverUrl"`. - VS Code puts servers under `"servers"`, not `"mcpServers"`. For a client that only supports local (stdio) servers, bridge it with `mcp-remote`: ``` npx -y mcp-remote ``` ## Agent skill The wack skill tells Claude Code and Codex when a job belongs in a box: heavy builds and dev servers, parallel jobs, headed browsers and visual tests, or a clean Linux machine. It is optional and holds no secret; the connect URL above is still what gives your agent the boxes. Install it with: ``` curl -fsSL https://wack.sh/install | sh ``` It saves the skill to `~/.claude/skills/wack/SKILL.md`, and to `~/.codex/skills/wack/SKILL.md` if you use Codex. Add `-s -- --project` after `sh` to install it into the current project's `.claude/skills` instead. Run it again to update, then restart your agent. You can read the skill at [/skill/SKILL.md](https://wack.sh/skill/SKILL.md). ## The tools | Tool | What it does | Box URL | | --- | --- | --- | | `box_docs` | Returns the box manual. Agents call it once before their first job. | Yes | | `box_list` | Lists your boxes and their state. On a box URL it marks the one the URL belongs to. | Yes | | `box_create` | Makes a new box and returns once it is awake. Optional `name`, `size`, `screen`, `image` (start as a copy of a saved image), `setup` (a recipe applied before it returns), and `ephemeral` for a box that deletes itself when it goes to sleep. | No | | `box_delete` | Deletes a box and everything on its disk. It can't be undone. | No | | `box_start` | Wakes the box. Safe to call when it is already awake. | Yes | | `box_stop` | Puts the box to sleep. Refuses while something is running. | Yes | | `box_exec` | Runs a bash command as root. Default timeout 300 s, up to 900 s. | Yes | | `box_upload` | Writes a file into the box from text or base64 content. | Yes | | `box_download` | Reads a file from the box. Images come back as images. | Yes | | `box_screenshot` | Screenshots a URL or an HTML file in the box and returns the image. | Yes | | `box_setup` | Applies a setup recipe to the box: apt packages, a Chrome version, private repos, fonts, environment variables and commands. | Yes | | `box_screen` | Turns on the box's screen (a virtual display) if needed, clicks, types or scrolls on it, and returns a picture of it. | Yes | | `box_image_save` | Saves a box as an image, so new boxes can start as a copy of it. | No | | `box_image_delete` | Deletes a saved image. Boxes started from it keep everything. | No | On your account's URL, every tool that works on a box takes `box`: the box's id (`box_…`) or its exact name, as `box_list` shows them. On a box's URL, every tool works on that box. Tools that need a box running wake it first, which takes about a second. ## Boxes your agent makes Boxes your agent makes with `box_create` are ordinary boxes: they show up on **Boxes**, count toward your plan and have their own Overview, Files, Screen, Activity and Settings. Agents make extra boxes to run work side by side, such as testing several repos at once or trying ideas that must not touch each other. `box_create` takes the same choices as the **New box** dialog, and a few more: - `size`: `standard`, `large`, `xl` or `gpu` ([Sizes and GPU boxes](/docs/sizes)). - `screen`: turns the box's [screen](/docs/screen) on right away. - `setup`: a recipe applied before the box is handed over, and `image`: a saved image to start from ([Setups and images](/docs/setups)). - `ephemeral`: a throwaway box, described below. A box made with `ephemeral: true` is a throwaway. It says **Temporary** on its card and deletes itself, with everything on its disk, the first time it goes to sleep. Your agent copies out what it needs before then. Your plan caps how many boxes you have and how many are awake at once ([Limits](/docs/limits)). At the cap, `box_create` fails with a message that says so, and your agent can put a box it made to sleep, delete one it made or ask you to upgrade. wack never stops or deletes a box to make room. ## Troubleshooting - **"This connect URL is no longer valid."** The URL was rotated. Copy the current one from **Settings › Connection** (or the box's **Settings**, for a box's URL) and update the client. - **A tool asks for `box`.** It's on your account's URL, which reaches every box. Your agent calls `box_list` and passes the box's id or name. - **Long commands time out in Codex or Gemini CLI.** Raise the client's tool timeout as shown above. - **The tools don't show up.** Most clients load MCP servers at startup. Restart the client or start a new session. - **A tool result says your awake hours are used up or your trial has ended.** The message explains what happened and links to billing, so your agent can pass it on to you. - **`box_create` says a limit is reached.** Your plan caps boxes, boxes awake at once and GPU boxes ([Limits](/docs/limits)). Your agent can put a box it made to sleep or delete one, or you can upgrade. - **Calls answer "Too many requests".** Your account's URL allows 3,000 MCP requests a minute and a box's URL 300. The agent waits and tries again. --- # Boxes Source: https://wack.sh/docs/boxes A box is a persistent Linux computer that belongs to you. Your agent works in it as root, and everything it installs or writes stays there: files, packages, cloned repos and logins. A standard box has 1 vCPU and 4 GB of memory (about 3.8 GB usable). Bigger sizes and GPU boxes come with Pro and Power; see [Sizes and GPU boxes](/docs/sizes). ## What's already installed Every box starts with the tools agents reach for most: - **Office documents**: LibreOffice, pandoc, python-docx, openpyxl and python-pptx, docx and pptxgenjs for Node - **PDF & print**: TeX Live (pdfLaTeX, XeLaTeX), poppler (pdftoppm, pdftotext), qpdf, wkhtmltopdf, Tesseract OCR - **Data & ML**: pandas, NumPy, SciPy, scikit-learn, matplotlib and seaborn, Graphviz - **Browser automation**: Playwright with Chromium, for Python and Node - **Media**: FFmpeg, ImageMagick, sharp, Mermaid CLI - **Languages & runtimes**: Python 3.12 with uv, Node.js 22 with npm, Java 21 - **Agent CLIs**: Claude Code & Codex (installed on first use) Optional add-ons, such as database clients, the GitHub CLI and extra codecs, are under **Toolhouse → Add-ons**. Turning one on installs it in all your boxes. Your agent can also install anything else itself: use `uv run --with ` or `uv pip install --system --break-system-packages ` for Python packages (plain `pip install` is blocked by the system Python) and `npm` or `apt-get` for the rest. ## Make a box Your first box, "my box", is made when you sign up. **New box** on the **Boxes** page makes another. Give it a name of up to 40 characters, or leave it blank and wack picks one. The dialog also lets you choose a size, turn on a screen and start from a saved image, described below. Your plan sets how many boxes you can have and which sizes you can make ([Limits](/docs/limits)). ### Boxes your agent makes An agent connected with your account's URL can make boxes itself, to run work side by side, and delete the ones it made. They appear on **Boxes** like any other and count toward your plan. A box it marks as temporary says **Temporary** on its card and deletes itself, with its files, the first time it goes to sleep. [Connect](/docs/connect#boxes-your-agent-makes) has the details. ## Sizes A box is Standard, Large, XL or GPU, and keeps its size for life. Standard suits most work. Large and XL are for dev servers, big builds and test suites, and a GPU box has one NVIDIA RTX 4090 for work that needs graphics hardware. An awake hour on a bigger box counts as more than one plan hour. [Sizes and GPU boxes](/docs/sizes) has the table and what's different about each. ## Screen A box can have a screen: a display at 1440 × 900 that your agent opens real windows on, such as a headed browser. You watch it live on the box's **Screen** tab and can take control to click and type yourself. [Screen](/docs/screen) covers turning it on and what your agent can do with it. ## Setups and images A setup is a recipe your agent applies to a box: apt packages, an exact Chrome version, repos, fonts, environment variables and commands. **Save as image** keeps a copy of a box that's set up, and new boxes can start from it. [Setups and images](/docs/setups) has the recipe and how images work. ## Pixel tests Screenshots compare well between two runs in the same setup: the same image, Chrome build, fonts, screen size and kind of box. Record reference images in a box, never on another machine. [Pixel tests](/docs/setups#pixel-tests) has the launch flags for boxes with and without a GPU. ## Sleep and wake A box is in one of these states: - **Setting up**: a new or reset box getting ready. This usually takes under 20 seconds. - **Awake**: running and counting toward your awake hours. - **Asleep**: paused with its disk kept. It uses no awake hours. [GPU boxes](/docs/sizes#gpu-boxes) never sleep: they end. - **Needs attention**: something went wrong. The box's Overview says what, and **Try again** usually fixes it. A box wakes when something needs it: a tool call from your agent, an automation run, a cloud session, or **Wake box** on the box's Overview (also in its ⋯ menu). Waking takes about a second. A box goes back to sleep after 10 minutes without a command, an automation run or a cloud session, and never during one. **Sleep now** on the Overview puts it to sleep right away, unless something is running. What sleep does to running programs depends on the size: - **Standard boxes** keep their memory. Jobs left running in the background pause while the box sleeps and carry on when it wakes. - **Large and XL boxes** keep their files but restart their programs. Background jobs, servers and anything open on the screen stop, and your agent starts them again after the wake. Either way, background jobs don't keep a box awake. For work that should go on without an agent calling, use an [automation](/docs/automations). Your plan sets how many boxes can be awake at once. Waking one more fails with a message that says so: put a box to sleep, or wait for one to go idle. wack never puts a box to sleep or deletes one to make room for another. ## Files Two folders matter: - `/mnt/user-data/uploads`: where files for the agent to work on go. - `/mnt/user-data/outputs`: where deliverables go. Automation runs write to `/mnt/user-data/outputs/runs/`. The box's **Files** tab lets you browse `/mnt/user-data/outputs` and download files up to 100 MiB. Browsing needs the box awake, so an asleep box shows a **Wake** button instead of files. Agents move files with `box_upload` (up to 10 MiB) and `box_download` (up to 5 MiB inline). Apps can move files up to 100 MiB through the [API](/docs/api#files). ## Commands `box_exec` runs a command with bash as root, in a clean environment. A command gets 300 seconds by default and up to 900 seconds when the agent asks for more. A command that runs out of time, or that the agent cancels, is stopped along with everything it started. For longer work, the manual tells agents to start the job in the background, write its log under `/mnt/user-data/outputs` and check back at least every few minutes, so the box doesn't fall asleep under it. Very long output is trimmed to its beginning and end. [Limits](/docs/limits) has the exact numbers. A box can reach the internet, but nothing on the internet can reach into it: there is no inbound network. To look at a page a box is serving, have your agent take a `box_screenshot`, or open it in a browser on the box's [screen](/docs/screen). ## Reset **Reset box** (in the box's **Settings**, under **Danger zone**, or its ⋯ menu) throws away the box's disk: every file, installed package and login is gone, and the next wake starts from a fresh box with the standard tools. The box keeps its name and its connect URL, so connected agents keep working. Reset asks you to type the box's name first. GPU boxes can't be reset: end one and make a new one. If a box is ever lost on our side, wack sets up a fresh one in its place the same way. The box's activity shows the reset, and you get an email about it. ## Delete **Delete box** (in the same places) removes the box and everything in it for good, including its automations, activity and connect URL. It can't be undone, so it also asks you to type the box's name. A GPU box has **End** in its place, which deletes the box and its disk the same way. ## Activity The box's **Activity** tab lists what happened in the box, with its cloud sessions above: wakes and sleeps, commands your agents ran, files moved, agents that connected, automation runs and cloud sessions. It shows the command text and exit code, not the output. --- # Sizes and GPU boxes Source: https://wack.sh/docs/sizes A box's size is its machine: how many CPUs, how much memory, and whether it has a GPU. You pick it when you make the box, in the **New box** dialog (agents pass `size` to `box_create`), and the box keeps it for life. | Size | Machine | An awake hour counts as | Plans | | --- | --- | --- | --- | | Standard | 1 vCPU · 4 GB | 1 hour | Every plan | | Large | 4 vCPU · 16 GB | 4 hours | Pro, Power | | XL | 8 vCPU · 16 GB | 6 hours | Power | | GPU | RTX 4090 · 4 vCPU · 8 GB | 8 hours | Pro, Power | ## Which size to pick - **Standard** suits most work: scripts, data, documents, files and small builds. Start here. - **Large** is for dev servers, big builds, long test suites and several browsers at once. - **XL** is for the heaviest builds. - **GPU** is only for work that needs graphics hardware: WebGL and WebGPU performance and pixel tests, and CUDA or machine learning jobs. Most canvas and WebGL work runs fine without one. A bigger box uses your awake hours faster, so agents are told to start small and ask for more only when the job needs it. ## How awake hours count An hour awake on a bigger box counts as more than one of your plan's awake hours, as the table shows. A Large box that is awake for 30 minutes uses 2 plan hours, and a GPU box uses 4. Sleeping boxes use none, whatever their size. Your plan sets which sizes you can make. A size your plan doesn't include can't be picked in the dialog, which links to the upgrade, and `box_create` fails with a message that says so. ## Large and XL boxes Large (4 vCPU · 16 GB) and XL (8 vCPU · 16 GB) boxes work like standard ones, with two differences: - **Programs restart on wake.** Their files stay, but background jobs, servers and anything open on the [screen](/docs/screen) stop when the box sleeps. Your agent starts them again after the wake. - **Ask the box how big it is.** Commands see the box's CPU count in `$WACK_CPUS` and its memory in `$WACK_MEMORY_GIB`, because `nproc` and `free` can report the host's. Agents use them for `-j`, worker counts and heap sizes. Standard, Large and XL boxes have no GPU. Browsers on them draw WebGL and WebGPU on the CPU, which is slower but very repeatable ([Pixel tests](/docs/setups#pixel-tests)). ## GPU boxes A GPU box has one NVIDIA RTX 4090 (24 GB) with 4 vCPU and 8 GB of memory. CUDA, Vulkan and EGL work, and Chrome is already installed with the flags that make it draw on the GPU. Pick **GPU** in the **New box** dialog, or have your agent pass `size: "gpu"` to `box_create`. GPU boxes run on spare capacity, which makes them cheap enough to run several at once. That comes with differences from other boxes: - **It's always temporary.** A GPU box never sleeps. When it stops (after 10 idle minutes, 12 hours awake, or **End** in its ⋯ menu), the box and its disk are deleted. Its card says **GPU · Temporary**. - **It can be reclaimed.** Spare capacity can be taken back at any time, without warning. The box and everything on its disk go with it, and your agent's next call to it says it was reclaimed. Nothing restarts by itself, so have your agent download results as it goes (checkpoints, screenshots, perf numbers), not only at the end. To carry on, make a new GPU box from the same image or setup. - **It has its own limit.** Your plan sets how many GPU boxes can run at once: 0 on Hobby, 2 on Pro and 6 on Power. They don't count toward your other box limits, and each of their awake hours counts as 8. - **Running several.** Each GPU box is its own box, so your agent can make several side by side for parallel jobs. When no GPU is free, making one fails with a message that says so; try again in a few minutes. - **Images.** **Save as image** works on a GPU box without stopping it. It takes from under a minute to about 10 minutes, depending on how much is on its disk, and programs running in it can end, so check them afterwards. Boxes started from a GPU image are GPU boxes. For most jobs a [setup](/docs/setups) is quicker. - **No reset.** A GPU box can't be reset: end it and make a new one. ### Chrome on the GPU Launch `$CHROME_PATH` with `--no-sandbox $WACK_CHROME_GPU_FLAGS`, plus `--headless=new` for headless, or with the [screen](/docs/screen) on for a window you can watch. Both draw on the GPU. Browser libraries add defaults that turn the GPU off, so the box manual tells agents which ones to strip. - **Check the GPU on every run.** WebGL's renderer should name the RTX 4090 and WebGPU's adapter shouldn't be the fallback one; otherwise Chrome quietly fell back to the CPU. Right after launch the adapter can be missing for about half a second, so request it with `powerPreference: "high-performance"` and ask again every 500 ms for up to 10 seconds before judging. - **Check WebGPU in its own page.** With `--headless=new`, asking for a WebGPU adapter after a page has made a WebGL context loses that context. Keep the WebGPU check apart from WebGL work, and run a page that uses both in `chrome-headless-shell` (same flags) or on the screen, which don't have this problem. - **Keep the driver with the results.** `/root/.wack/gpu.txt` has the GPU and driver version. Store it next to every baseline and perf result, since the driver can differ from box to box. ### CUDA and machine learning `nvidia-smi` shows the GPU. PyTorch installs in about 15 seconds: ``` uv pip install --system --break-system-packages torch --index-url https://download.pytorch.org/whl/cu128 ``` Write long jobs so they can resume from a checkpoint, and have your agent download checkpoints as they are made. --- # Screen Source: https://wack.sh/docs/screen A box can have a screen: a virtual display at 1440 × 900. With it on, your agent can run programs that need a real window, such as a headed browser, and you can watch them live from the dashboard. Most work doesn't need one. Scripts, builds and headless browsers run without a screen, and `box_screenshot` takes a picture of a page on any box. Turn the screen on when something has to run in a window: clicking through an app, canvas and WebGL work, a login a headless browser can't get past, or a job you want to watch. ## Turn it on Any of these turns the screen on: - The **Screen** switch in the **New box** dialog, when you make the box. - **Turn on screen** on the box's **Screen** tab. The box has to be awake; the first time can take up to a minute. - Your agent, by calling `box_screen`, or by passing `screen: true` to `box_create`. Once it's on, every command your agent runs has `DISPLAY` set, so programs it starts draw on the screen. The screen stays on until you or your agent turn it off, and it comes back on when the box wakes. ## Watch and take control The box's **Screen** tab shows the display live. It starts view-only, so you can't disturb your agent by accident. **Take control** lets you click and type too, for example to sign in to a site yourself or get past a prompt, and your agent carries on from there. - Up to three people can watch a box's screen at once. - Watching doesn't keep a box awake. An idle box still sleeps after 10 minutes, so a bigger box isn't billed just because its tab is open. Clicking and typing while you're in control count as activity. - If the connection drops, the viewer reconnects by itself. When the box goes to sleep, the tab offers **Wake box**. **Turn off** on the Screen tab closes the display and the programs on it. ## What your agent does with it `box_screen` returns a picture of the whole display. It can also drive it, with a list of actions that run in order before the picture is taken: | Action | Example | | --- | --- | | Click | `{"click": {"x": 200, "y": 120}}` (add `"button": 3` for a right click, `"double": true` for a double click) | | Move the pointer | `{"move": {"x": 10, "y": 10}}` | | Type text | `{"type": "hello"}` | | Press keys | `{"key": "ctrl+l"}` | | Scroll | `{"scroll": {"dy": 5}}` (positive scrolls down) | | Wait | `{"wait_ms": 500}` | `off: true` turns the screen off. A typical job starts a headed browser with `box_exec`, then looks and clicks with `box_screen`. Playwright's Chromium is already installed, and a [setup](/docs/setups) with a `chrome` step adds an exact Chrome build at `$CHROME_PATH`. Two launch flags matter on the screen: `--app=URL` opens a window without tabs or an address bar, and `--test-type` hides the warning bars that would otherwise push the page down and move every click. ## Sleep and the screen The screen comes back on when the box wakes. On a standard box, the programs on it are still there. Large and XL boxes restart their programs on wake, so your agent opens the browser again ([Sizes](/docs/sizes#large-and-xl-boxes)). ## Screens and the GPU On a [GPU box](/docs/sizes#gpu-boxes), browsers on the screen draw on the GPU when launched with `$WACK_CHROME_GPU_FLAGS`. On other sizes the screen has no GPU: browsers draw WebGL and WebGPU on the CPU, which is slower but very repeatable. [Pixel tests](/docs/setups#pixel-tests) explains how to compare screenshots between runs. ## Who can see it Only you, signed in to the dashboard. Each viewer connection uses a ticket that works once and expires after a minute, and your browser connects to wack, never to the box itself. [Security](/docs/security) has the rest. --- # Setups and images Source: https://wack.sh/docs/setups A new box has the standard tools and nothing of yours. Two things make it yours without doing the work again each time: - A **setup** is a recipe: packages, a Chrome version, repos, fonts, environment variables and commands. Your agent applies it to a box. - An **image** is a saved copy of a box that is already set up. New boxes start as a copy of it. Use a setup for anything quick to install. Save an image when getting a box ready takes minutes, or when several boxes have to be exactly alike. ## Setups Your agent applies a setup with `box_setup`, or passes it as `setup` to `box_create` so the box is ready before it's handed over. You can ask for it in plain words ("set this box up with Chrome 153 and our app repo"), and the agent writes the recipe: ``` { "packages": ["jq"], "chrome": "153.0.8010.52", "repos": [{ "url": "https://github.com/acme/app", "ref": "main" }], "fonts": { "packs": ["inter", "noto-emoji"], "dirs": ["/root/app/test/fonts"] }, "env": { "NODE_OPTIONS": "--max-old-space-size=8192" }, "run": ["cd /root/app && npm ci"] } ``` Every field is optional. The steps run in this order: | Step | What it does | | --- | --- | | `packages` | Installs apt packages by name, up to 50. | | `chrome` | Installs a Chrome for Testing build: an exact version, or a milestone such as `"153"` for its latest build. `chrome` runs it, and `$CHROME_PATH` points to it in every later command. | | `repos` | Clones up to 10 repos over HTTPS, each to `/root/` unless it has a `path`. `ref` picks a branch, tag or commit. Running the setup again fetches instead of cloning again. | | `fonts` | Installs fonts from `packs` (`noto-cjk`, `noto-emoji`, `ms-core`, `inter`), from `urls` (`.ttf`, `.otf`, `.woff2` or `.zip` files) and from `dirs` (folders in the box, such as a cloned repo's), then rebuilds the font cache. | | `run` | Runs up to 20 shell commands in order and stops at the first one that fails. | `env` isn't a step. Its variables are set for every command that runs in the box afterwards. Each step gets up to 15 minutes and is reported on its own. A step that fails doesn't undo the ones that worked, and only the steps that worked become part of the box's setup. Fix the failed one and apply the setup again: that's safe to repeat. ### Private repos Private GitHub repos are cloned with the GitHub token you add under **Toolhouse → Accounts**. The token is only sent to github.com, and it sits in the box in a file only root can read, only while the clone runs. When no token is saved, the step says so. ### Environment variables `env` is for settings, not secrets: it is stored in plain text. Names are upper-case letters, digits and underscores. Variables wack sets itself can't be changed (`PATH`, `HOME`, `DISPLAY`, `CHROME_PATH`, `PWD`, `OLDPWD`, `TERM` and anything starting with `WACK_`). ### What a box has The box's **Settings** tab shows its setup under **Installed**: the Chrome version, repos, fonts and packages. Setups add up: applying another one later merges into what's there. ## Images **Save as image** (in the box's **Settings**) keeps a copy of the box: its disk, its setup and its size. Agents do the same with `box_image_save`. Saving takes about a minute for most boxes. To start a box from an image, pick it under **Start from** in the **New box** dialog, or have your agent pass `image` to `box_create`. The new box has the image's files, setup and size, and is usually ready within a minute. The first box from a new image can take longer. - Your images are listed in **Settings**, where you can delete them. Agents see them in `box_list` and delete them with `box_image_delete`. - Deleting an image doesn't touch boxes that started from it. - Your plan sets how many images you can keep: 1 on Hobby, 5 on Pro and 20 on Power. - If an agent asks for an image that is gone, the box starts from the standard tools with the image's setup applied again, and the agent is told. That box may differ from older copies, so save a new image. ### Images of GPU boxes **Save as image** works on a [GPU box](/docs/sizes#gpu-boxes) without stopping it. It takes from under a minute to about 10 minutes, depending on how much is on its disk. Programs running in the box can end while it saves, so check them afterwards. Boxes started from a GPU image are GPU boxes. ## Pixel tests Screenshots compare well between two runs in the same setup: the same image, the same Chrome build, the same fonts, the same screen size, and the same kind of box. So: - **Use one image for every box you compare.** Build one box with a setup, save it as an image, and start each test box from it. - **Record reference images in a box**, never on another machine. A box's pixels won't match a Mac's exactly. - **Bring your own fonts.** Apple's fonts can't be installed on a box. Have test pages load their own fonts, or ship them in a repo and list the folder in the setup's `fonts.dirs`. - **Compare like with like.** Timings and pixels from a box without a GPU say nothing about a machine with one, and the other way round. ### On the CPU Standard, Large and XL boxes draw with SwiftShader, which is very repeatable. Launch Chrome with: ``` --use-angle=swiftshader --enable-unsafe-swiftshader --force-device-scale-factor=1 --font-render-hinting=none --test-type --disable-infobars ``` ### On a GPU box For hardware rendering and real performance numbers, use a GPU box. Launch `$CHROME_PATH` with: ``` --no-sandbox $WACK_CHROME_GPU_FLAGS --force-device-scale-factor=1 --font-render-hinting=none --test-type --disable-infobars ``` Add `--headless=new` for headless, or run it on the [screen](/docs/screen) for a window you can watch. Both draw on the GPU; compare pixels only between runs launched the same way, and only GPU box to GPU box. [Chrome on the GPU](/docs/sizes#chrome-on-the-gpu) lists the checks to make on every run. --- # Automations Source: https://wack.sh/docs/automations An automation is a job your box runs without you: a prompt for an agent, or a shell script, plus a trigger. The result lands as files in the box and, if you like, you get an email with a summary and a link to them. **New automation** walks you through three steps: **What** (Claude Code, Codex or a shell command, and what it should do), **When** (the trigger and schedule) and **Finish** (a name, the box, email and more options). Editing an automation shows the same settings on one page. ## Agent or shell - **Agent** runs Claude Code or Codex headless in the box with your prompt. Describe the outcome you want, such as "Write a one-page digest of…" or "Check… and note what changed", and the agent does the work. - **Shell** runs a bash script. Use it when you already know the exact commands. Agent runs use your own Claude or OpenAI account. Add a token or API key once under **Toolhouse → Accounts**: a Claude subscription token from `claude setup-token` or an Anthropic API key for Claude Code, and an OpenAI API key or your Codex sign-in for Codex. If an account is missing, the first step of the new-automation wizard asks for it inline. You can still create the automation and connect later: it won't run until you do, and its page and the Automations list say so. Automations and cloud sessions can read these values, but agents connected over MCP never see them. You can also choose how much the agent may do on its own: **full access** (no permission prompts) or **edits only** (it can change files, and anything that would need your approval is refused). ### Automations from apps A desktop app connected to your account, such as monocode, can schedule its own automations in your box with any agent it runs, [through the API](/docs/api#external-automations). The app says how to start and install the agent and brings the agent's sign-in from your computer, so nothing needs connecting in Toolhouse. These automations show **Managed by: An app connected to your account**. You can pause, resume and run them here; everything else is changed in the app. ## Triggers ### Schedule Pick a preset (every hour, every day at 9:00, weekdays, Mondays), or under **More ways to start it** choose **Custom schedule** and write a five-field cron expression. Every schedule has a time zone, which defaults to your browser's (press **Change** next to it to pick another), so "9:00" stays 9:00 across daylight-saving changes. The automation page lists the next three run times. If wack was unavailable when a run was due and it's now too late, the run is recorded as **skipped** with the reason, so gaps are visible. ### Webhook In the wizard this is **From another app**, under **More ways to start it**. A webhook automation gets a secret URL: ``` POST https://api.wack.sh/hooks/whk_… ``` Send any body up to 256 KiB. wack saves it in the box for the run, outside the run folder. A shell script finds its path in `$WACK_PAYLOAD`, and an agent's prompt gets a line telling it where the payload is. ``` curl -X POST https://api.wack.sh/hooks/whk_… \ -H 'content-type: application/json' \ -d '{"event": "deploy", "ref": "main"}' ``` | Response | Meaning | | --- | --- | | `202` with `{ "runId": "…" }` | The run is queued. | | `404` | No automation has this URL. It may have been rotated or deleted. | | `409` | The automation is paused. | | `429` | A run is already queued for it, or the URL is being called too often. | Treat the URL as a secret. **Rotate webhook URL** on the automation page replaces it. ### Manual **Run now** starts a run right away, even when the automation is paused. Apps can do the same [through the API](/docs/api#automations). ## Runs Runs on the same box take turns, one at a time and in order. Each automation can have one run waiting; extra scheduled runs are recorded as skipped instead of piling up. A run: 1. wakes the box; 2. creates its own folder, `/mnt/user-data/outputs/runs/`, and passes its path as `$WACK_RUN_DIR`; 3. runs the agent or script, streaming its output to the run page as it goes; 4. collects every file saved in the run folder as the run's **files**; 5. lets the box go back to sleep if nothing else is using it. A run can take up to 60 minutes and is stopped after that. **Cancel** on the run page stops it sooner. Either way, everything the run started in the background is stopped too. A run that finishes on its own leaves its background jobs running. The run page shows a summary (the agent's final message, or the last lines of a script's or an app's agent's output, when they add to the transcript), the files, the full transcript and, for a failed run, what went wrong and how to fix it. Agents are asked to save anything meant for you in the run folder and finish with a two-line summary. ### Snapshot runs By default an agent works in its folder as it finds it, and whatever it changes stays there for the next run. An automation created [through the API](/docs/api#automations) can ask for `"workspace": "snapshot"` instead. Its folder (`cwd`) must be a git repository, and each run then: 1. records the folder's current commit as the run's base, and discards uncommitted changes and untracked files (ignored files stay), so the agent starts from the last commit; 2. runs the agent; 3. saves everything the agent changed since the base, including any commits it made, as `changes.patch` among the run's files; 4. puts the folder back to the base commit. `changes.patch` is a binary git diff (`git apply --binary` applies it) whose first line names the base commit: ``` # wack-base 3f2c9a0e… diff --git a/src/app.ts b/src/app.ts … ``` A run that changed nothing has no patch. If the folder isn't a git repository, the run fails to start and says so. Shell automations always use their folder as it is. ## Notifications Each automation emails you **always**, **on failures** or **never**. The email has the summary, the list of files and a link to the run. Apps that create automations through the API choose this with `notify`, which is `always` unless they say otherwise. ## Templates The Automations page offers ready-made starting points, grouped by category: **Daily digest**, **Weekly report**, **Watch a page** and **Nightly check**. **Use** on a template opens the new-automation wizard with every step filled in, ready to edit. Until you have an automation, the page also features three of them: pick one and press **Get started**. --- # Cloud sessions Source: https://wack.sh/docs/cloud-sessions Desktop apps that drive coding agents, such as monocode, usually start the agent CLI on your laptop. A **cloud session** starts the same process in your box instead. The app keeps its interface and talks to the process the same way, but the work runs in the cloud and keeps going when the laptop sleeps. This page is for people building such an app. As a user, all you do is approve the app once, and its sessions then show up on the box page, where you can stop them. ## 1. Sign in with a code Apps get an API key through a device sign-in, the same flow TVs and CLIs use ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)). No password or key is typed into the app. Start a request with your app's name: ``` curl -X POST https://api.wack.sh/v1/device/code \ -H 'content-type: application/json' \ -d '{"clientName": "monocode"}' ``` ``` { "deviceCode": "…", "userCode": "BCDF-GHJK", "verificationUri": "https://wack.sh/device", "verificationUriComplete": "https://wack.sh/device?code=BCDF-GHJK", "expiresIn": 600, "interval": 5 } ``` Open `verificationUriComplete` in the browser and show `userCode` in the app so the person can check they match. They sign in to wack if needed and click **Allow**. Meanwhile, poll for the key every `interval` seconds: ``` curl -X POST https://api.wack.sh/v1/device/token \ -H 'content-type: application/json' \ -d '{"deviceCode": "…"}' ``` Until the request is approved, the response is `400` with one of these errors: | `error` | What to do | | --- | --- | | `authorization_pending` | Not approved yet. Keep polling. | | `slow_down` | You polled too fast. Wait longer between polls. | | `access_denied` | The person clicked Deny. Stop. | | `expired_token` | The code expired after 10 minutes. Start again. | Once approved, you get the key, and you get it only once: ``` { "accessToken": "wack_…", "tokenType": "bearer", "keyId": "…" } ``` Store it in the system keychain. It appears under **Settings → API keys** with the app's name, and the person can revoke it there at any time. Send it as `Authorization: Bearer wack_…` on every `/v1` call. When the person signs out of the app, revoke the key from the app too: ``` DELETE /v1/key ``` This revokes the key that makes the call and returns `204`. From then on the key gets `401`. Revoking a key, here or under **Settings → API keys**, also stops the processes it started. It doesn't change a box's connect URL that the key read (see [Give local agents the box](#give-local-agents-the-box)): rotate that before you revoke the key. ## 2. Bring the agent's sign-in The agent should run in the box signed in the way it is on the person's computer, without signing in again. wack keeps no list of agents or of where they keep their sign-in: the app knows that already, and uploads it per agent. `agent` is any lowercase name the app uses for it: ``` PUT /v1/agents/codex/sign-in { "source": "computer", "env": {}, "files": [{ "path": ".codex/auth.json", "content": "{…}" }], "hash": "9f2c…" } ``` - `env` holds the agent's own variables (API keys, tokens), and `files` its sign-in files by their path under the home folder. - `hash` is the app's hash of the whole sign-in. Sending the same one again changes nothing, so the app can call this before every start and only a real change is stored. - `source` is `computer` for a copy from the person's computer, or `override` for a token the person gave for wack, such as a long-lived one for scheduled runs. A `computer` upload never replaces an override: wack answers with the override's status, and keeps using it until it is replaced by another override or deleted. `GET /v1/agents` shows what wack holds for each agent, `{ "codex": { "source", "hash", "updatedAt" } }`, never the values. `DELETE /v1/agents/{agent}/sign-in` removes one. In the box, the files are written readable by root only, and only when the saved sign-in changed. An agent that refreshes its token in place keeps the refreshed one. Before an agent starts, the other agents' sign-in files are taken out of the box, and a token one of them refreshed there is saved to its sign-in first, so an agent finds only its own sign-in. Everything in a box runs as root, though: a plain process or `box_exec` can read the files of the agent that last started, and two agents running side by side can read each other's. Files that leave the sign-in are removed from awake boxes right away and from the others before the agent's next start. A file the person made in the box, say by signing in there, is left alone. ## 3. Start a process Pick a box from `GET /v1/boxes`, then start the agent there, exactly as you would locally: ``` POST /v1/boxes/{boxId}/procs { "sessionId": "tab-42", "command": "codex", "args": ["app-server"], "cwd": "/root/projects/app", "install": "npm install -g @openai/codex", "signIn": "codex" } ``` - `install` runs when `command` isn't in the box yet, with up to 10 minutes. It also finds programs an installer only added to the login shell's `PATH`. - `signIn` writes that agent's sign-in files and puts its variables in the process's environment. The process gets nothing from Toolhouse unless you send `"secrets": "all"`, which is safer for chats that read content you don't control. Without a saved sign-in the call fails with `409 needs_credentials`. - `env` adds your own environment variables on top. - `sessionId` makes the call idempotent: while a process with that id is running on the box, calling again returns it instead of starting another. - `idleTimeoutSeconds` (60 to 86400) stops the process once it has gone that long with no output and no stdin. Watching its events doesn't count. Set it for agents that wait on stdin between turns, such as `--input-format stream-json`: if the app goes away (the laptop sleeps or quits), the process stops and the box can sleep instead of using awake hours. The timeout carries over if wack restarts while the process runs. Scheduled jobs work the same way through [external automations](/docs/api#external-automations): the app sends the agent's `command`, `args` and `install`, and wack runs it on schedule with the saved sign-in, even when the app is closed. The response is a `Proc` with an `id` and `status: "running"`, and its `client` is the API key's name. While it runs, the box stays awake and never sleeps under the session. ## 4. Stream output and send input ``` GET /v1/procs/{procId}/events?since=0 Accept: text/event-stream ``` This is a server-sent event stream. Each event's `id` is its sequence number, and its data is one of: ``` { "t": "out", "seq": 12, "line": "…" } { "t": "err", "seq": 13, "line": "…" } { "t": "exit", "seq": 14, "code": 0 } ``` Output is split into lines per stream, so a JSON-lines protocol arrives one message per event. The stream replays what you missed and then follows live output until `exit`. To reconnect, pass the last `seq` you saw as `since` (or send it as `Last-Event-ID`). The last 1,000 lines replay instantly. Write to the process's stdin with: ``` POST /v1/procs/{procId}/stdin { "data": "{\"type\":\"user\",…}\n" } ``` Add your own newlines: `data` is written exactly as sent. ## 5. Stop `POST /v1/procs/{procId}/kill` stops the process and everything it started in the background. A process that exits on its own leaves its background jobs running. `GET /v1/boxes/{boxId}/procs?sessionId=…` finds a session's process again after the app restarts. The person can also stop any session from the dashboard. If wack restarts while a process is running, it reconnects to the process when it comes back. A process it can't find again is marked `failed`. ## Give local agents the box Agents that run on the person's computer can use the box as a tool too, through its MCP server. Read the box's connect URL with the API key: ``` GET /v1/boxes/{boxId}/connect ``` ``` { "boxId": "box_…", "boxName": "my box", "mcpUrl": "https://api.wack.sh/mcp/…", "connections": [], "firstExecAt": null } ``` Add `mcpUrl` to the agent's MCP servers as a streamable HTTP server; [Connect](/docs/connect) has the setup for each agent. The URL is a secret scoped to that box, so keep it out of command lines and logs. The person can rotate it on the box page, after which you read it again. When they disconnect your app or turn the box off for local agents, rotate it yourself with `POST /v1/boxes/{boxId}/connect/rotate`, so no agent that kept a copy can still use the box. A key revoked under **Settings → API keys** can't do that, so the URL keeps working until the person uses **Rotate URL** in the box's **Settings**. The full request and response shapes are in the [API reference](/docs/api#cloud-sessions). --- # API reference Source: https://wack.sh/docs/api The API lives at `https://api.wack.sh/v1`. It speaks JSON with camelCase fields, and times are ISO 8601 strings with an offset. Agents don't need it: they use the [MCP tools](/docs/connect#the-tools). It's for apps, scripts and integrations. ## Authentication Every `/v1` call (except the device sign-in) needs an API key in the `Authorization` header: ``` Authorization: Bearer wack_… ``` Create a key under **Settings → API keys**. It's shown once, and wack stores only a hash. Apps can get a key through the [device sign-in](/docs/cloud-sessions#1-sign-in-with-a-code) instead. Keys are never accepted in query strings. ``` curl https://api.wack.sh/v1/me -H "Authorization: Bearer $WACK_API_KEY" ``` `DELETE /v1/key` revokes the key that makes the call and returns `204`. Apps call it when someone signs out, and the key gets `401` from then on. Revoking a key, here or under **Settings → API keys**, also stops the [processes](#cloud-sessions) it started. `GET /v1/me` returns the key owner's email, plan and usage: ``` { "email": "you@example.com", "plan": { "plan": "pro", "status": "active", "trialEndsAt": null, "periodStart": "2026-09-01T00:00:00.000Z", "periodEnd": "2026-10-01T00:00:00.000Z", "cancelAtPeriodEnd": false, "billingEnabled": true }, "usage": { "awakeSeconds": 44640, "awakeLimitSeconds": 540000, "boxes": 2, "boxesLimit": 3, "awakeBoxes": 1, "awakeBoxesLimit": 2, "automations": 4, "automationsLimit": 25 } } ``` ## Boxes | Method and path | Returns | | --- | --- | | `GET /v1/boxes` | `Box[]` | | `GET /v1/boxes/{id}` | `Box` | | `POST /v1/boxes/{id}/wake` | `Box`, once it's awake | | `POST /v1/boxes/{id}/sleep` | `Box`, or `409 box_busy` while something runs | | `GET /v1/boxes/{id}/connect` | `ConnectInfo`: `{ boxId, boxName, mcpUrl, connections, firstExecAt }` | | `POST /v1/boxes/{id}/connect/rotate` | `ConnectInfo` with a new `mcpUrl` | ``` { "id": "box_…", "name": "my box", "status": "awake", "busy": false, "awakeSince": "2026-09-22T09:00:04.000Z", "lastActiveAt": "2026-09-22T09:03:10.000Z", "sleepsAt": "2026-09-22T09:13:10.000Z", "createdAt": "2026-09-01T12:00:00.000Z", "lastError": null, "clients": [{ "name": "claude-code", "lastSeenAt": "2026-09-22T09:03:10.000Z" }] } ``` `status` is `provisioning`, `awake`, `asleep` or `error`. `sleepsAt` is when an idle box will go to sleep. A box also has `size` (`standard`, `large`, `xl` or `gpu`), `screen` (whether its [screen](/docs/screen) is on), `ephemeral` when an agent made it as a throwaway, and its `setup` and `image` when it has them ([Setups and images](/docs/setups)). Boxes are made, set up and deleted in the dashboard or by agents through the [MCP tools](/docs/connect#the-tools). The API reads them, wakes them and puts them to sleep. `mcpUrl` is the box's [connect URL](/docs/connect). Apps use it to give the agents they run on your computer the box as an MCP server. Treat it as a secret: anyone who has it can use the box. When the person disconnects your app, or stops giving its agents the box, call `POST /v1/boxes/{id}/connect/rotate`: the old URL stops working at once, including any copy an agent kept. ## Files | Method and path | Body | Returns | | --- | --- | --- | | `GET /v1/boxes/{id}/files?path=/abs/path` | | The file's bytes | | `PUT /v1/boxes/{id}/files?path=/abs/path` | The raw bytes | `{ "path": "…", "bytes": 1234 }` | Paths are absolute. Both calls wake the box if it's asleep and handle files up to 100 MiB. `PUT` replaces an existing file. ``` curl -X PUT "https://api.wack.sh/v1/boxes/$BOX/files?path=/mnt/user-data/uploads/data.csv" \ -H "Authorization: Bearer $WACK_API_KEY" \ --data-binary @data.csv ``` ## Automations | Method and path | Body | Returns | | --- | --- | --- | | `GET /v1/automations` | | `Automation[]` | | `POST /v1/automations` | `AutomationInput` | `Automation` (201) | | `POST /v1/automations/{id}/runs` | | `Run` (202) | | `PUT /v1/automations/external/{clientId}` | `ExternalAutomationInput` | `Automation` | | `GET /v1/automations/external/{clientId}` | | `Automation`, or `404` | | `DELETE /v1/automations/external/{clientId}` | | 204 | | `POST /v1/automations/external/{clientId}/runs` | | `Run` (202), or `409` while the last one hasn't finished | | `GET /v1/automations/external/{clientId}/runs` | | `{ "runs": RunSummary[], "nextBefore": "…" }` | An `AutomationInput` is what the dashboard form sends: ``` { "name": "Daily digest", "boxId": "box_…", "kind": "agent", "agent": "claude", "permission": "full", "prompt": "Write a one-page digest of…", "trigger": "schedule", "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Berlin" }, "notify": "always" } ``` - `kind` is `agent` (needs `agent` and `prompt`) or `shell` (needs `script`). - `trigger` is `schedule` (needs `schedule`), `webhook` or `manual`. - `permission` is `full` or `edits`, and `notify` is `always`, `failures` or `never`. - `workspace` is `shared` (the default) or `snapshot`. See [snapshot runs](/docs/automations#snapshot-runs). - Optional fields: `model`, `cwd` (an absolute path), `workspace` and `enabled`. ### External automations Apps that keep their own list of scheduled jobs can mirror them into wack with `PUT /v1/automations/external/{clientId}`, where `clientId` is the app's own id for the job. Calling it again with the same id updates the automation instead of creating another, so the app can sync without keeping wack's ids. The app says exactly how to start its agent: a program and its arguments, run as given with stdin closed, and how to install the program when the box doesn't have it. wack keeps no list of agents, so any CLI works. ``` { "name": "Morning triage", "prompt": "Triage new issues and…", "agent": "codex", "command": "codex", "args": ["exec", "--json", "--skip-git-repo-check", "Triage new issues and…"], "install": "npm install -g @openai/codex", "schedule": { "kind": "weekdays", "time": "08:30", "dayOfWeek": 1, "tz": "America/New_York" }, "missedRunGraceMinutes": 30, "notify": "failures", "workspace": "snapshot", "enabled": true } ``` - `agent` names the agent (lowercase letters, digits and `-`). Its runs get the agent's [sign-in](#agent-sign-ins), which the app saves first; without one, a run fails with `needs_credentials` before the box wakes. - `command` is a program name. `args` (up to 256) are passed to it one by one, so nothing in them is read by a shell. Models, permission modes and the prompt all go in `args`: `prompt` is only what wack shows on the automation's page. - `install` (optional) is a shell command that runs when `command` isn't found, for example the agent's install script. It has 10 minutes. - `schedule.kind` is `hourly` (runs at `minute`), `daily`, `weekdays` or `weekly` (on `dayOfWeek`, where 0 is Sunday). `time` is `HH:MM` in `tz`. - `notify` is `always` (the default), `failures` or `never`. `workspace` is `shared` (the default) or `snapshot`, which needs `cwd` to be a git repository and saves what each run changed as `changes.patch`. See [snapshot runs](/docs/automations#snapshot-runs). `secrets` is `agent` (the default: only the agent's sign-in) or `all` (every Toolhouse secret as well). Every `PUT` replaces the whole automation, so leaving any of these out sets it back to its default. - `boxId`, `cwd` and `missedRunGraceMinutes` are optional. Without `boxId`, the automation runs on your oldest box; with no box at all the call fails with `409 conflict`. `missedRunGraceMinutes` is how late a missed run may still start before it is recorded as skipped. The `Automation` it returns has `agent: null` and the app's `launch`: `{ "agent", "command", "args", "install" }`. A run's `summary` is the last lines the agent printed to stdout. In the dashboard these automations can be paused and resumed, and run now; anything else is changed in the app. `POST /v1/automations/external/{clientId}/runs` starts a run now. It answers `409 conflict` while a run is waiting to start, or while the last run started this way is still going, so a double click starts one run. `GET /v1/automations/external/{clientId}/runs` lists that automation's runs newest first, 20 at a time. Pass `nextBefore` back as `?before=` to get the next page; it is `null` on the last one. A `RunSummary` is a [`Run`](#runs) without the automation and box names, `summary`, `errorCode` and `artifacts`. It keeps `errorMessage`, which says why a run failed or was skipped. ## Runs | Method and path | Returns | | --- | --- | | `GET /v1/runs?since={cursor}&automation={id}` | `{ "runs": Run[], "cursor": "…" }` | | `GET /v1/runs/{id}` | `Run` | | `POST /v1/runs/{id}/cancel` | `Run`, or `409` once it has finished | | `GET /v1/runs/{id}/events` | Server-sent events | `GET /v1/runs` returns runs oldest first. Pass the `cursor` from each response as `since` on the next call to get only newer runs, which makes it a cheap way to poll for results. A run is listed a few seconds after it is queued. `automation` narrows the list to one automation. ``` { "id": "run_…", "automationId": "aut_…", "automationName": "Daily digest", "automationClientId": null, "status": "succeeded", "trigger": "schedule", "queuedAt": "2026-09-22T07:00:00.000Z", "startedAt": "2026-09-22T07:00:01.000Z", "finishedAt": "2026-09-22T07:03:40.000Z", "exitCode": 0, "boxId": "box_…", "boxName": "my box", "summary": "Wrote digest.md with 9 items.", "errorCode": null, "errorMessage": null, "artifactCount": 1, "artifacts": [{ "path": "/mnt/user-data/outputs/runs/run_…/digest.md", "name": "digest.md", "bytes": 5120 }] } ``` `automationClientId` is the `clientId` of an [external automation](#external-automations), and `null` for any other run. `POST /v1/runs/{id}/cancel` cancels a queued run at once and stops a running one. `status` is `queued`, `running`, `succeeded`, `failed`, `canceled` or `skipped`. A failed run's `errorCode` is one of `needs_credentials`, `box_unavailable`, `limit_reached`, `timeout`, `interrupted`, `canceled` or `failed_to_start`. Download files through the [files API](#files) using their `path`. ### Run events `GET /v1/runs/{id}/events` streams a run as server-sent events. Each event has an `id`, so you can resume with the `Last-Event-ID` header after a disconnect. | Event | Data | | --- | --- | | `run.started` | The run | | `harness.line` | `{ "stream": "stdout" or "stderr", "line": "…" }`, the agent's raw output (Claude Code and Codex emit JSON lines) | | `run.artifact` | An `Artifact`: `{ "path", "name", "bytes" }` | | `run.finished` | `{ "status", "exitCode", "summary" }` | | `run.error` | `{ "code" }` | ## Cloud sessions | Method and path | Body | Returns | | --- | --- | --- | | `POST /v1/device/code` | `{ "clientName" }` | `DeviceCodeResponse` (no key needed) | | `POST /v1/device/token` | `{ "deviceCode" }` | `{ "accessToken", "tokenType", "keyId" }` or an error (no key needed) | | `GET /v1/agents` | | `{ [agent]: AgentSignInStatus }` | | `PUT /v1/agents/{agent}/sign-in` | `AgentSignInInput` | `AgentSignInStatus` | | `DELETE /v1/agents/{agent}/sign-in` | | 204 | | `POST /v1/boxes/{id}/procs` | `SpawnProcInput` | `Proc` (201, or 200 for a running `sessionId`) | | `GET /v1/boxes/{id}/procs?sessionId=…` | | `Proc[]` | | `GET /v1/procs/{id}` | | `Proc` | | `GET /v1/procs/{id}/events?since={seq}` | | Server-sent events | | `POST /v1/procs/{id}/stdin` | `{ "data" }` | 204, or `409` once the process has exited | | `POST /v1/procs/{id}/kill` | | `Proc` | A `SpawnProcInput` is `{ sessionId?, command, args?, cwd?, env?, install?, signIn?, secrets?, idleTimeoutSeconds? }`, with up to 256 `args`. `install` is a shell command run when `command` isn't found. `signIn` is an agent whose [sign-in](#agent-sign-ins) the process gets. With `signIn`, `secrets` is `agent` (the default: only the sign-in) or `all` (every Toolhouse secret as well). `idleTimeoutSeconds` (60 to 86400) stops the process once it has gone that long with no output and no stdin. A `Proc` is: ``` { "id": "prc_…", "boxId": "box_…", "boxName": "my box", "sessionId": "tab-42", "client": "monocode", "apiKeyId": "key_…", "command": "claude", "args": ["--output-format", "stream-json"], "cwd": "/root/projects/app", "status": "running", "exitCode": null, "startedAt": "2026-09-22T09:00:00.000Z", "endedAt": null, "lastSeq": 118, "idleTimeoutSeconds": 900 } ``` ### Agent sign-ins An agent's sign-in is what its CLI keeps on the person's computer: environment variables, and files under the home folder. An app saves it once per agent, and wack gives it to that agent's processes and automation runs. ``` PUT /v1/agents/codex/sign-in { "source": "computer", "env": {}, "files": [{ "path": ".codex/auth.json", "content": "{…}" }], "hash": "9f2c…" } ``` - `source` is `computer` (copied from the person's computer) or `override` (a token the person gave for wack). A `computer` upload never replaces an `override`: the call answers `200` with the override's status. - `env` maps variable names to values, and `files` lists up to 16 files of up to 256,000 characters each. `path` is relative to the home folder, with no `.` or `..` parts. At least one variable or file is needed. - `hash` is the app's own hash of the sign-in. Uploading the same `source` and `hash` again changes nothing, so an app can send it before every start. The answer is an `AgentSignInStatus`, `{ "source", "hash", "updatedAt" }`, and `GET /v1/agents` lists one per agent. Values never come back. The files are written to the box readable by root only, and only when the saved sign-in changed, so a token the agent refreshed in the box stays. Files that leave a sign-in, or all of them after `DELETE`, are removed from awake boxes right away and from the others before the agent's next start. The sign-in is stored encrypted and apart from your Toolhouse secrets. `apiKeyId` is the API key that started the process, so two installs of an app with the same name can tell their processes apart. Killing a process, by hand or through `idleTimeoutSeconds`, also stops everything it started in the background. A process that exits on its own leaves its background jobs running, like any job left in the box. [Cloud sessions](/docs/cloud-sessions) walks through the whole flow. ## Errors Every error has the same shape and a message that is safe to show to people: ``` { "error": { "code": "limit_reached", "message": "Your plan includes 3 boxes. Upgrade to add more: https://wack.sh/settings/billing" } } ``` Some errors add a `details` field. For `invalid_request` it lists each field's problem. | Code | Status | Meaning | | --- | --- | --- | | `unauthorized` | 401 | The API key is missing, malformed or revoked. | | `forbidden` | 403 | The key is valid but can't do this. | | `not_found` | 404 | No such resource, or it belongs to someone else. | | `invalid_request` | 400 | The body or query failed validation. `details` lists the fields. | | `conflict` | 409 | The resource is in the wrong state, for example a run already queued. | | `rate_limited` | 429 | Too many requests. Wait for the seconds in `Retry-After`. | | `plan_required` | 402 | The trial ended or the subscription isn't active. | | `limit_reached` | 403 | A plan limit: awake hours, boxes, awake boxes or automations. | | `box_busy` | 409 | The box is running something and can't sleep yet. | | `box_asleep` | 409 | The operation needs the box awake. Wake it first. | | `box_unavailable` | 503 | The box couldn't start. Retry after `Retry-After` if present. | | `box_reclaimed` | 410 | The GPU box was reclaimed (its spare capacity was taken back) and its disk is gone. Make a new one. | | `needs_credentials` | 409 | The agent has no credentials. Add them in Toolhouse, or its sign-in via `/v1/agents`. | | `billing_unavailable` | 503 | Billing is temporarily unavailable. | | `internal` | 500 | Something failed on our side. The message includes a reference id. | ## Rate limits | Scope | Limit | | --- | --- | | `/v1` | 600 requests a minute per API key | | `/v1/device/*` | 10 requests a minute per IP address | | Webhooks | 30 requests a minute per webhook URL | | MCP, your account's URL | 3,000 requests a minute | | MCP, a box's URL | 300 requests a minute | Past the limit you get `429 rate_limited` with a `Retry-After` header. JSON request bodies can be up to 1 MiB. --- # Limits Source: https://wack.sh/docs/limits ## Plans | | Hobby ($19/mo) | Pro ($49/mo) | Power ($149/mo) | | --- | --- | --- | --- | | Awake hours a month | 40 | 150 | 500 | | Boxes | 1 | 3 | 10 | | Boxes awake at once | 1 | 2 | 4 | | Automations | 5 | 25 | 100 | | Box sizes | Standard | Standard, Large, GPU | Standard, Large, XL, GPU | | GPU boxes at once | 0 | 2 | 6 | | Saved images | 1 | 5 | 20 | The 7-day trial has Hobby limits with 10 awake hours, and needs no card. **Awake hours** count the time your boxes are awake, added up across boxes. Sleeping boxes don't use any. A box goes to sleep after 10 minutes with nothing running, so most plans last far longer than the hours suggest. Hours reset at the start of each billing period and don't roll over. **Boxes** counts every box on your account, including the ones your agents make through your account's connect URL. Temporary boxes count until they delete themselves. **Boxes awake at once** caps how many boxes run at the same moment. Waking one more fails with a message that says so. wack never puts a box to sleep or deletes one to make room. **GPU boxes at once** caps how many [GPU boxes](/docs/sizes#gpu-boxes) run at the same moment. GPU boxes count only here, not toward **Boxes** or **Boxes awake at once**, because a GPU box exists only while it runs. Their awake hours still count, at 8 plan hours each. Making one more fails with a message that says so: end one, or wait for one to finish. **Box sizes** are the sizes you can make. An hour awake on a bigger box counts as several awake hours: | Size | Machine | An awake hour counts as | Plans | | --- | --- | --- | --- | | Standard | 1 vCPU · 4 GB | 1 hour | Every plan | | Large | 4 vCPU · 16 GB | 4 hours | Pro, Power | | XL | 8 vCPU · 16 GB | 6 hours | Power | | GPU | RTX 4090 · 4 vCPU · 8 GB | 8 hours | Pro, Power | **Saved images** caps how many [images](/docs/setups#images) you keep. Saving one more fails with a message that says so: delete one, or upgrade. ## Trial and billing - **Trial.** A new account starts a 7-day trial with Hobby limits and 10 awake hours. It needs no card. We email you two days before it ends. - **Choosing a plan.** Pick one under **Settings → Billing**. Plans are billed monthly, in advance, and renew until you cancel. - **Changing or canceling.** Switch plans or cancel under **Settings → Billing** at any time. A canceled plan stays active until the end of the period you paid for. - **A payment fails.** Your boxes keep working while the payment is retried, and we email you a link to update your payment method. - **No plan.** When a trial ends or a plan runs out, boxes can't wake and the dashboard is read-only. Your boxes, files and automations are kept until you delete them or your account, and choosing a plan picks up where you left off. ## When you reach a limit Nothing is ever billed extra. Instead: - **Awake hours used up.** Work that is already running finishes, but boxes can't wake again until your hours reset or you upgrade. Agents get a message that says so, with a link to billing. At 80% your agent's tool results start to carry a short usage note, and we email you. - **Boxes or automations.** Creating another one fails with a message naming the limit. Delete one or upgrade. - **Trial ended or subscription inactive.** Boxes can't wake and the dashboard is read-only, but your boxes and files are kept. Choose a plan to pick up where you left off. ## Box and job limits | Limit | Value | | --- | --- | | Standard box size | 1 vCPU, 4 GB of memory (about 3.8 GB usable) | | Screen | 1440 × 900, drawn by the GPU on a GPU box and by the CPU on other sizes | | Each setup step | 15 minutes | | A GPU box's longest time awake | 12 hours | | Idle time before a box sleeps | 10 minutes | | `box_exec` timeout | 300 s by default, 900 s at most | | Command output returned | 100,000 characters (the first 20,000 and the last 80,000) | | `box_upload` file size | 10 MiB | | `box_download` inline file size | 5 MiB | | Dashboard and API file transfers | 100 MiB | | Automation run time | 60 minutes | | Webhook body | 256 KiB | | Transcript lines kept per run | 5,000 | | Cloud session lines replayed on reconnect | 1,000 | Commands that need longer than `box_exec` allows should run in the background with their log in `/mnt/user-data/outputs`, and the agent checks back. Background jobs don't keep the box awake: after 10 minutes without a call it sleeps and they pause until it wakes, so the agent should check at least every few minutes. For work that should go on without an agent, use an [automation](/docs/automations). ## Rate limits The API allows 600 requests a minute per key. MCP allows 3,000 requests a minute on your account's connect URL, which is shared by every box it reaches, and 300 on a box's URL. [API reference](/docs/api#rate-limits) has the full list. --- # Security Source: https://wack.sh/docs/security ## Your box is yours Each box is its own virtual machine, not a shared server. Your agent runs as root inside it, and nothing else of yours or anyone else's runs there. wack reaches the box only to do what you, your agents, your automations or your apps ask for, and to install the add-ons you turn on. ## Connect URLs A connect URL is the credential for wack's MCP tools. Your account's URL (`https://api.wack.sh/mcp/wac_…`) reaches every box you have and can make and delete boxes; a box's URL (`https://api.wack.sh/mcp/wbx_…`) reaches only that box. Each contains 32 random characters, and wack stores it hashed for lookups and encrypted so the dashboard can show it to you again. - Share your account's URL only with agents you trust with all your boxes. For an agent that should see one box, give it that box's URL instead. - **Rotate URL** replaces a URL: under **Settings › Connection** for your account's, in the box's **Settings** for a box's. The old URL stops working immediately and answers with "This connect URL is no longer valid." - Requests from web pages on other sites are refused, so a page you visit can't drive your box. ## Accounts and secrets Tokens and keys you add in **Toolhouse** (Claude, OpenAI, GitHub and your own environment variables) are encrypted at rest with AES-256-GCM. They are write-only: after saving, the dashboard shows only the last four characters. These secrets go into your box only when an automation or a cloud session starts an agent there, as environment variables for that process. Agents connected over MCP don't receive them. ## API keys, webhook URLs and device codes - API keys (`wack_…`) are shown once when created. wack stores a SHA-256 hash, so a lost key can't be recovered, only revoked and replaced. Keys work in the `Authorization` header only, never in a URL. - Webhook URLs contain a random token that is stored hashed and encrypted. Rotate one from the automation page at any time. - Device sign-in codes expire after 10 minutes and turn into an API key only after you click **Allow** while signed in. ## Signing in Sign-in is handled by Auth0. wack never sees or stores your password. The dashboard talks to the wack API on the server side with your session's access token. Your browser calls the API directly for one thing only, the live [screen](/docs/screen) viewer described below. ## The screen viewer A box's screen is only reachable through wack. To show it, the dashboard asks for a ticket for that box while you're signed in, and your browser opens one connection to wack with it. A ticket works once and expires after a minute, and connections from other sites are refused. The viewer starts view-only, and no more than three viewers can watch a box at once. ## What we log The API writes one log line per request: time, request id, method, path, status, duration and your user id. Connect URL secrets, webhook tokens and similar values in URLs are masked before the line is written. Request and response bodies, command output and file contents are not logged. Inside your box, wack keeps a history for you, not for us: your activity feed (commands with their exit codes, file transfers, wakes and sleeps) and the transcripts of automation runs and cloud sessions, which you can read on the dashboard. Errors shown to you never include internal details. When something fails on our side, you get a reference id you can send us. ## Deleting data Deleting a box destroys the machine and its disk, along with its activity, runs and sessions. Deleting your account from **Settings** removes all of it, plus your secrets and keys, and cancels any subscription. ## Reporting a problem Found a vulnerability? Email [hello@wack.sh](mailto:hello@wack.sh). We'll reply and keep you posted on the fix. --- # FAQ Source: https://wack.sh/docs/faq ## What is a box, exactly? A small Linux computer in the cloud that belongs to you, with 1 vCPU and 4 GB of memory (about 3.8 GB usable). It has a disk that persists, runs your agent as root and comes with the usual tools installed. It sleeps when idle and wakes in about a second. ## Which agents work with wack? Claude Code, Codex, Cursor, Claude (desktop, web and mobile), VS Code and any other client that supports remote MCP servers. [Connect](/docs/connect) has the steps for each. ## Does wack include the AI model? No. wack is the computer, not the model. Your agent keeps using your own Claude, ChatGPT or other subscription or API key. Automations and cloud sessions use the account you add in Toolhouse. ## Can my agent reach my laptop? No. Your agent sends work to the box, and files move only when it uploads or downloads them. wack never connects to your computer. ## What happens when the box goes to sleep? Its disk is kept, and it uses no awake hours. It never sleeps during a command, an automation run or a cloud session. The next tool call wakes it in about a second, with every file where it was. On a standard box, a job left running in the background is paused while the box sleeps and carries on when it wakes. Large and XL boxes restart their programs on wake, so background jobs and servers are started again. GPU boxes don't sleep at all: they end, and their disk is deleted. ## Can I get a bigger box, or a GPU? Yes. Pro adds Large boxes (4 vCPU · 16 GB) and GPU boxes (one RTX 4090), and Power adds XL (8 vCPU · 16 GB). An awake hour on a bigger box counts as more than one plan hour. [Sizes and GPU boxes](/docs/sizes) has the table. ## Can I watch what my agent is doing? Yes. Turn on the box's [screen](/docs/screen) and its **Screen** tab shows the display live, with **Take control** when you want to click and type yourself. The box's **Activity** tab lists every command your agents ran. ## How do I get the same setup in every box? Have your agent apply a [setup](/docs/setups): packages, an exact Chrome version, repos and fonts. Save a finished box as an image and new boxes start as a copy of it. ## How long can a command run? One `box_exec` call can run for up to 900 seconds. For longer work, the agent starts the job in the background, keeps a log in `/mnt/user-data/outputs` and checks back at least every few minutes: after 10 minutes without a call the box sleeps, and the job pauses until it wakes. An automation run can take up to 60 minutes. ## Can I install my own tools? Yes. Your agent can install anything with `uv`, `npm` or `apt-get`, and it stays installed. Common extras are one switch away under **Toolhouse → Add-ons**. Resetting a box brings it back to the standard setup. ## What happens if I run out of awake hours? Nothing is billed extra. Running work finishes, and boxes can't wake again until your hours reset at the start of your next billing period or you upgrade. See [Limits](/docs/limits). ## Do I need a card for the trial? No. The trial lasts 7 days with 10 awake hours. When it ends, your boxes stop waking until you choose a plan. They and their files are kept. ## Can I use wack from my own app? Yes. The [API](/docs/api) covers boxes, files, automations and runs, and [cloud sessions](/docs/cloud-sessions) let a GUI app run agent sessions in a box. ## Who can see my files? You and the agents and apps you connect. wack doesn't read your files or use them to train anything. [Security](/docs/security) explains how secrets, keys and URLs are protected. ## How do I get help? Email [hello@wack.sh](mailto:hello@wack.sh).