hwatu
A warm browser daemon built for AI coding agents.
Real WebKit rendering, 35 ms verify passes, and the one thing
nobody else has: interruptible sessions, live hand-off
between agent and human, into your tiling WM.
curl -fsSL
https://raw.githubusercontent.com/hongnoul/hwatu/main/scripts/install.sh
| bash
GitHub · Releases · AGPL-3.0 · Linux
Onboarding
Install → Detect workflow → Connect agent → Verify page → Hand off to human
curl -fsSL https://raw.githubusercontent.com/hongnoul/hwatu/main/scripts/install.sh | bash
hwatu setup # detect; changes nothing
hwatu doctor # dependency + rendered smoke test
hwatu setup --client claude --scope project --dry-run
hwatu setup --client claude --scope project # explicit opt-in
hwatu demo # headless verification tour
- Install. Download the client and daemon; check WebKitGTK.
- Detect. Find Claude Code, Cursor, Jcode, and generic MCP workflows.
- Connect. Native Jcode socket, standard MCP, or plain CLI.
- Verify. Open, inspect, and screenshot a real rendered page headlessly.
- Hand off. Promote the same live session only when human judgment is needed.
Bare hwatu setup never edits agent configuration. Every
mutation names its target first, supports --dry-run, and
can be reversed with --undo. Use
--scope project for shareable config or
--scope user for personal config.
Why
Browsers conflate two things: the engine (slow to start, RAM-hungry)
and the window (what you actually ask for). hwatu splits them, the
same way emacsclient and wezterm do:
-
hwatudowns WebKitGTK 6, a prewarmed WebView pool, and all windows. -
hwatuis a thin client: one Unix-socket roundtrip to open a window.
Measured: 13 ms median (p90 35 ms) from
hwatu <url> to a mapped, loading window on a warm
daemon; background and headless opens land at 14-16 ms. The
first-ever window pays a one-time engine/GPU init. Full data:
docs/benchmarks.md.
For coding agents
This is the primary use case. The daemon is a verification harness for AI coding agents: real WebKit, 14 ms headless windows, an 87 ms open→wait→eval→screenshot→close loop, one JSON protocol over the Unix socket. Full guide: docs/agents.md; plan of record: docs/roadmap.md; research outline: docs/research-chick-and-chicken.md.
hwatu --headless localhost:3000 # live session, no window at all
hwatu --background localhost:3000 # window mapped, focus stays put
hwatu eval 'document.title' # run JS in the page, JSON out
hwatu goto /checkout # navigate + wait for the load
hwatu shot /tmp/page.png # screenshot (--full = whole document)
hwatu scroll h2 --contains Total # scroll into view, reports what it hit
hwatu upload 'input[type=file]' ./fixture.png
hwatu focus 3 # hand the session to the human
Agent CLIs (jcode, Claude Code, Codex, Cursor, ...) are
auto-detected and default to --headless, so
verification never steals your focus. focus promotes
any headless or background window to a normal one, session
intact.
Compared
As a minimal human browser for a tiling WM (Hyprland, sway, i3, river), the usual suspects trade differently. hwatu's human UI is deliberately scoped to receiving agent hand-offs; for a keyboard-maximalist daily driver, qutebrowser remains the pick.
| hwatu | surf | qutebrowser | luakit | |
|---|---|---|---|---|
| Window spawn | 13 ms median (warm daemon) | engine start per window | engine start | engine start |
| Engine | WebKitGTK 6 | WebKitGTK 2 | QtWebEngine | WebKitGTK 2 |
| Tabs | none, WM tiles | none | built-in | built-in |
| Keyboard UI | your WM's binds | patches | vim binds | lua config |
| Memory model | one shared engine, N views | process per window | one big process | one process |
Philosophy
- Agent-first. The primary user opens windows over a socket and reads pages as JSON. The human UI serves the hand-off.
- No tabs. A tab is a window. Your tiling WM is the tab manager.
- No chrome. The WebView is the whole window.
- Real rendering. Full WebKit: JS, CSS, media, WebGL, as the frontend intended. No custom half-engine.
- Spawn speed precedes everything. Every engine knob is paid on the daemon's idle path, never at window open. A spawn-latency benchmark gates every change.
Usage
hwatu # open the launcher (autostarts hwatud)
hwatu example.com # open a URL (https:// implied)
hwatu list # id, url, title of every window
hwatu close 2 # close window 2
hwatu quit # stop the daemon
Ctrl+w (or Ctrl+q) closes the focused window.
Ctrl+Shift+j / Ctrl+Shift+k scroll the
page down/up by half a viewport. The daemon and engine
stay warm.
Tuning
No config file. Engine knobs are set to their correct values in code (GPU compositing always on). The only surface is environment variables:
| Variable | Meaning |
|---|---|
HWATU_HOME |
Page opened by a bare hwatu. Defaults to the
built-in launcher (keybind cheat sheet + URL bar).
|
HWATU_DISCARD_SECS |
Seconds an unfocused window keeps its live WebView before being suspended to save RAM (default 120, 0 disables). |
HWATU_WEBKIT_FEATURES |
Ident:on,Other:off: flip individual WebKit runtime
features on odd hardware. Unknown identifiers are ignored.
|
Standard WEBKIT_* and GSK_RENDERER
variables pass through untouched. Scrolling smoothness scales with
your distro's WebKitGTK: 2.46+ paints with Skia on the GPU.
Architecture
hwatu <url> --unix socket--> hwatud (GTK main loop)
├── prewarmed WebView (adopted instantly,
│ next one warmed in idle time)
├── window registry (id -> WebView)
└── WebKit: shared network process,
per-site web processes
Unfocused windows are suspended after a timeout: history is serialized, the web process is killed, and RAM comes back. Focus restores them instantly from the prewarm pool.