HyperKitty Chromium
An Erlang/OTP-native browser agent platform. Headless Chromium wrapped in an explicit supervision tree — so browser control, agent orchestration, search, and messaging are separate, independently-supervised subsystems rather than library calls inside one monolithic process.
24
tests passing
7
OTP supervisors
11
search pipeline stages
14
browser capability ops
6
REST + WS API domains

Overview
The core principle
An LLM-driven agent is a planning function that proposes tool calls. It is never trusted with direct, unchecked access to a browser process. Every tool call crosses an explicit, typed capability boundary — enforced by Erlang code, not by the prompt — before it reaches Chromium.
🌐
HK-BROWSER
Launches a real headless Chromium OS process per session. Drives it over CDP (via gun) and Chrome's HTTP control endpoint. 14-operation typed capability interface with auth + argument validation.
gen_server
🤖
HK-AGENT
A gen_statem agent loop enforcing configurable action, time, network, and search-iteration limits before any tool call reaches the browser. 7-state FSM from created → completed/failed/terminated.
gen_statem
🔍
HK-SEARCH
An 11-stage pipeline where each stage is a literal state. Emits an ordered event per stage with a pluggable provider behaviour and a mock reference implementation.
pipeline
📡
HK-MSG
Append-only event bus with WebSocket streaming. Every action, state transition, and result is published before it is acted on — making the system fully observable.
event bus
🔌
HK-API
6-domain REST + WebSocket API built on Cowboy. Agents, browsers, search, messages, events, health — all independently addressable. OpenAPI-compatible route layout.
Cowboy + REST
♥
HK-HEALTH
Aggregate subsystem health — all 7 subsystems up or down in one call. Used by the observability frontend and external monitoring. No external dependency.
monitoring
OTP
Supervision tree
rest_for_one at the root — if the event bus dies, everything that depends on it restarts too. Each browser and agent is its own isolated process with its own restart budget.
hyperkitty_sup (rest_for_one, intensity 8/10)
  ├── hk_event_sup (one_for_one)
  │     └── hk_event_bus                    [gen_server] append-only audit log + pub/sub
  ├── hk_messaging_sup (one_for_one)
  │     └── hk_messaging_portal              [gen_server] HK-MSG
  ├── hk_browser_sup (one_for_one)
  │     ├── hk_browser_registry              [gen_server] session_id → pid
  │     └── hk_browser_session_sup (simple_one_for_one, transient)
  │           └── hk_browser_session [gen_server] owns a Chrome OS process per session
  ├── hk_agent_sup (one_for_one)
  │     ├── hk_agent_registry                [gen_server] agent_id → pid
  │     └── hk_agent_fsm_sup (simple_one_for_one, transient)
  │           └── hk_agent_fsm [gen_statem] one per agent, 7-state FSM
  ├── hk_search_sup (one_for_one)
  │     ├── hk_search_registry
  │     └── hk_search_job_sup (simple_one_for_one)
  │           └── hk_search_job [gen_statem] 11-stage pipeline per job
  ├── hk_api_sup
  │     ├── hk_api_listener                  [Cowboy] REST + WS (trap_exit for clean teardown)
  │     └── hk_frontend_*                    connected client registry
  └── hk_health                          [gen_server] aggregate subsystem health
gen_statem
Agent state machine
Every agent is a gen_statem process. No tool call reaches the browser without passing through this FSM — configurable action limits, time limits, and network limits are enforced structurally, not by the LLM.
created
→
ready
→
planning
→
executing
→
waiting
→
completed
failed
terminated
— reachable from any state on limit exceeded
Bugs found and fixed
Bug 1: ready state's tool-call handler routed to wrong next state — silently dropping the action. Fixed: correct state transition target.
Bug 2: OTP race where a mid-tree supervisor killed abruptly outran its children's cleanup. Fixed: hk_sup_util retry-on-death-wait helper + trap_exit on hk_api_listener.
Chrome PUT fix
Modern Chrome requires PUT for /json/new — newer Chrome dropped support for GET on that endpoint. hk_chrome_http.erl updated accordingly.
integration fix
HTTP + WebSocket
API reference
All routes served by Cowboy. WebSocket at /api/events/ws streams every event in real time.
MethodPathDescription
GET/api/healthAll 7 subsystems up/down in one call
POST/api/agentsCreate agent with config (limits, browser_session_id)
GET/api/agents/:idAgent state + current FSM state
DELETE/api/agents/:idTerminate agent (transitions to terminated)
POST/api/browsersSpawn headless Chromium session
POST/api/browsers/:id/capabilityExecute typed browser capability (14 ops)
DELETE/api/browsers/:idKill Chromium process, clean up session
POST/api/searchStart 11-stage search pipeline job
GET/api/search/:idSearch job state + stage progress
GET/api/messagesRecent messages from event bus
GET/api/events/recentLast N events across all subsystems
WS/api/events/wsReal-time event stream (all subsystems)
EUnit
24 tests — 0 failures
Includes real headless Chromium integration tests and chaos tests that kill live processes and verify recovery. Run with rebar3 eunit.
✓hk_agent_fsm_testsFSM
✓hk_browser_capability_testsintegration
✓hk_browser_session_testsintegration
✓hk_messaging_portal_testsunit
✓hk_search_job_testspipeline
✓hk_search_pipeline_testspipeline
✓hk_supervision_testschaos
✓hk_test_helperharness
Test output
24/24 tests pass
SUPERVISOR/CRASH/ERROR REPORTs in output are expected —
chaos tests deliberately kill live processes to verify recovery.
Setup
Quickstart
Requires Erlang/OTP 25+, rebar3, and Chromium. Bootstrap script handles the rest.
# Bootstrap environment (macOS via Homebrew) bash bin/bootstrap.sh # Build rebar3 compile # Run tests rebar3 eunit # Start rebar3 shell # Health check curl http://localhost:8080/api/health
View on GitHub →