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; verification plan: docs/roadmaps/verification.md; portfolio: docs/roadmap.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, niri, river), the usual suspects trade differently. Since v0.7.0 hwatu's human UI is a credible primary browser: mainstream rebindable keybinds, media-correct video, unified shortform controls, and Chromium-curve scrolling, while still excluding tabs, sync, and an extension platform. For a keyboard-maximalist daily driver with history completion and password fill today, 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 mainstream binds + WM binds patches browser 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.