The Boardwalk self-hosted runner: your machines execute runs that hosted Boardwalk schedules — for data residency, special hardware, or internal-network access. The mental model is a CI self-hosted runner; the contract is Boardwalk-native.
Not to be confused with the Boardwalk engine: the engine runs the entire control plane on your hardware, with no hosted Boardwalk involvement. This runner executes hosted-Boardwalk-scheduled work on your machines.
Pre-release, functional. The package now carries all three layers:
@boardwalk-labs/runner/contract— the canonical registration / offer / claim / heartbeat payload types (Zod schemas, derived TS types).CONTRACT.mdis the prose half: flows, the lease state machine, and the security invariants.@boardwalk-labs/runner/runtime— the Boardwalk worker runtime itself: the same code a Boardwalk-hosted Fargate worker boots executes each claimed run here (one worker, two homes).boardwalk-runner(the bin) +@boardwalk-labs/runner/daemon— the machine daemon: register once, then poll → claim → run → heartbeat → clean.
An org admin mints a one-time registration token (Boardwalk Settings > Runners), then on the machine:
boardwalk-runner register --url https://api.boardwalk.sh --token bwkreg_...
boardwalk-runner start --url https://api.boardwalk.sh --pool defaultstart polls for runs targeting runs_on: { kind: "self-hosted" } and executes one at a
time; run more daemons (or machines) for concurrency. Each run executes in a container by default
(Docker or Podman required); pass --host to run it directly on the machine instead. Ctrl-C
drains: the current run
finishes, nothing new is claimed. Useful flags: --once (execute one run, then exit),
--verbose (debug-level daemon logs), --debug (also debug-logs inside each run process),
--work-dir, --identity-dir. Behind a corporate proxy, launch with NODE_USE_ENV_PROXY=1
and HTTPS_PROXY set; the daemon and the run processes both honor it. Runs inherit this
machine's network — a model or service reachable from the box is reachable from the run.
computer.openBrowser() / computer.openDesktop() and agent({ session }) drive an in-VM
browser or a whole desktop.
Container mode (the default) works out of the box from runner image 0.3.32 on: the image
carries the full desktop stack (Xvfb, Chromium, Playwright MCP, xdotool, ffmpeg) plus the tier
env contract, and starts its own in-container display — never your machine's — before the run.
A BOARDWALK_* tier variable set in the daemon's environment overrides the image default (e.g.
BOARDWALK_RECORDING_ENABLED=0 to disable session recording, BOARDWALK_SCREEN_WIDTH=1920).
Note a computer-use session in a container still has the machine's network (--network host) —
that reach is the point of self-hosting, and it is on you.
--host mode (full machine access) drives your real environment instead — real Chrome, the
machine's own display, and on macOS any app on screen (an iOS Simulator needs no special
support: launch it with shell, then the agent sees and clicks it like anything else). You
provide the pieces and point the runner at them with the same env contract the hosted image
sets, so the code path is identical:
| Variable | Meaning |
|---|---|
BOARDWALK_BROWSER_TIER=1 |
Enable the per-run browser-session manager. Unset (the default) means computer.openBrowser() fails with a clear "not available on this runner". |
BOARDWALK_BROWSER_CHROME_PATH |
Path to a Chromium/Chrome binary the run launches with a CDP endpoint (headful, so it renders on a display). |
BOARDWALK_BROWSER_MCP_COMMAND |
Command to launch Playwright MCP (e.g. a playwright-mcp bin, or npx). The runner attaches it to the browser over CDP. |
DISPLAY |
The X display the browser renders on (e.g. a headless Xvfb :0). |
So a --host machine that wants the browser tier installs Chromium + Playwright MCP and sets
those variables (on Linux, also a display — a real X session or Xvfb; on macOS, DISPLAY is
ignored and Chrome opens on the real screen). Nothing else changes; a machine without them runs
every non-browser workflow exactly as before.
computer.openDesktop() drives your real Mac screen: input via CGEvent, screenshots via
screencapture, no native addon or build toolchain required. Set BOARDWALK_DESKTOP_TIER=1 in
the daemon's environment and grant the runner's terminal app two permissions in
System Settings > Privacy & Security:
| Permission | Without it |
|---|---|
| Accessibility | clicks and keystrokes are silently swallowed by the OS |
| Screen Recording | screenshots fail (could not create image from display) |
Both fail silently at the OS level, so they are checked twice rather than discovered mid-run:
boardwalk-runner start reports either missing grant before the machine goes online, and
openDesktop() refuses at the call with the fix rather than letting a run click into the void.
Grant them to the app that launches the runner (Terminal, iTerm, …), then restart it. Retina
displays need no configuration: the model sees true pixel dimensions and the driver converts
coordinates.
The agent sees and clicks the main display only; a second monitor is not captured, and the runner logs a note when it spots one. Because it drives the real machine, an agent with a desktop session can act on anything on that screen — the point of self-hosting, and worth deciding deliberately.
One macOS behavior to plan for on an unattended Mac: recent macOS versions periodically re-ask for screen-capture consent ("… is requesting to bypass the system private window picker and directly access your screen"). Captures keep working, but the prompt reappears on its own schedule and wants a human. For a long-lived headless Mac runner, log in as the runner user and acknowledge it, or expect to click Allow every so often.
Current platform limits, stated plainly: the desktop tier and session recording / live
view both work on Linux and macOS (x11grab and avfoundation respectively); a Windows
driver (SendInput/DXGI) is not built yet. Off a supported platform each declines cleanly instead
of failing runs, and a run whose recording could not start says so in its own log.
This part of the contract is settled:
- The runner never receives broad credentials: a single-purpose registration token to join, a standing runner token that can only poll/claim/heartbeat, and a short-lived run-scoped token as the only credential with reach into an org — authorized per call against that run's manifest.
- Secrets are never in an assignment and never at rest on the runner; they resolve per run through the control plane, fail-closed.
- Programs are content-addressed built artifacts: digest verified before extraction, no raw source, no runtime installs.
- All control signals (cancel, drain) arrive in heartbeat responses — there are no inbound connections to your machines.
- Per-run isolation of workspace, credentials, artifacts, and logs; cross-run leakage is treated as a critical (P0) bug.
pnpm install
pnpm test
pnpm lint && pnpm typecheck && pnpm buildboardwalk— the open-source single-node engine: cron scheduling, webhooks, durable runs, run historysdk-typescript—@boardwalk-labs/workflow, the TypeScript API a workflow program importscli—boardwalk: scaffold, validate, deploy, runexamples— copyable workflow templates (boardwalk init --template)plugins— coding-agent skills (Claude Code, Codex, Cursor, OpenClaw, OpenCode) + a control-plane MCP serverrunner-images— reproducible base images hosted runners execute in
Hosted platform and docs: boardwalk.sh.
Apache-2.0