Hanafuda July card: bush clover with boar

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
13 ms median window spawn
35 ms verify pass: open→eval→screenshot→close (warm Playwright: 82 ms)
~56 MB per extra window (PSS)
117k adblock rules, 0 JS

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

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:

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

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.