A parallel computer-use agent fleet control plane. Submit plain-language tasks through a web UI — they're dispatched to remote GCP VMs running desktop apps, driven by H Company's hai computer-use agent. Watch live screenshots and agent reasoning stream back in real time.
- You type a task in the webapp (or pick a preset) — e.g. "Create a sales order for Acme Corp"
- The dispatcher finds an idle worker VM, creates an H Company agent session targeting that VM's desktop
- The agent drives the desktop (mouse, keyboard, screenshots) — Tryton ERP, LibreOffice, Thunderbird, Slack, Pitivi, or any Linux GUI app
- Live feed streams back: screenshots, agent thinking, tool actions, metrics — all visible in the UI and on the H Company platform
graph TB
subgraph Mac["Your Mac (browser)"]
FE["React/Vite Frontend<br/>:5173<br/>task submission + live feed"]
BE["FastAPI Backend<br/>:8787<br/>dispatcher + pool + watcher<br/>SQLite DB"]
FE <-. WebSocket /api .-> BE
end
subgraph GCP["GCP VMs"]
VM1["Worker VM 1<br/>Xvfb + openbox<br/>Tryton, LibreOffice, Thunderbird<br/>hai bridge"]
VM2["Worker VM 2<br/>Xvfb + openbox<br/>Pitivi, Slack<br/>hai bridge"]
end
subgraph Cloud["H Company Cloud"]
AGP["agp.hcompany.ai<br/>Holo3 Agent<br/>session runs here"]
end
BE -- "gcloud ssh<br/>read session_id" --> VM1
BE -- "gcloud ssh<br/>read session_id" --> VM2
BE -- "create_session<br/>(inline agent + session_id)" --> AGP
BE -- "get_session_changes<br/>(long-poll events)" --> AGP
AGP -- "commands<br/>(click, type, screenshot)" --> VM1
AGP -- "commands<br/>(click, type, screenshot)" --> VM2
VM1 -- "screenshots + results" --> AGP
VM2 -- "screenshots + results" --> AGP
BE -- "screenshot proxy<br/>(adds bearer token)" --> AGP
- Frontend (React + Vite + Tailwind): task submission, live feed with screenshots, worker status, voice-to-text via Gradium
- Backend (FastAPI + SQLite): FIFO dispatcher, worker pool (reads bridge session IDs via
gcloud ssh), event watcher (long-polls H Company AGP for session changes), screenshot proxy, verification - Worker VMs: headless Linux desktops (Xvfb + openbox), each running one or more GUI apps + the
hai local desktopbridge - H Company Cloud: the Holo3 agent brain runs in the cloud — only the bridge (hands/eyes) lives on the VM
- No
hai-agent-runtimebinary needed — the pip SDK's pure-PythonPyautoguiDesktopBridgeworks headless on Linux (seelinux-runtime-proof/FINDINGS.md) - One bridge = one desktop = one session — N VMs = N parallel agent sessions, removing the "one session at a time" bottleneck
- Inline agents — each session passes an
Agentwith the target VM'ssession_idinline, so the registeredlocal-desktopagent never needs updating per task - 404-tolerant polling — fresh sessions can transiently 404 on the AGP; the watcher retries through it
- Screenshot proxy — browser
<img>tags can't send the bearer token, so the backend proxies screenshot URLs fromagp.hcompany.ai
- GCP account with
gcloudCLI authed - H Company API key (
HAI_API_KEY) - One or more worker VMs with the
hai local desktopbridge running (seelinux-runtime-proof/)
cd web/backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env # fill in HAI_API_KEY, GCP_ZONE, etc.
.venv/bin/uvicorn app.main:app --port 8787cd web/frontend
npm install
npx vite --port 5173Declare them in web/backend/workers.json:
{
"workers": [
{ "name": "agent-vm-1", "ip": "10.150.0.7" },
{ "name": "agent-vm-2", "ip": "10.150.0.9" }
]
}Each VM must have:
Xvfbrunning on a display (e.g.:99)openboxwindow manager (for keyboard focus)scrotfor screenshots- The
hai local desktopbridge, writing its session ID to/opt/agent/session_id - See
linux-runtime-proof/for the full setup script and patches
After a VM boots, hit POST /api/workers/refresh (or the refresh button in the UI) to read its bridge session ID.
The pool (pool.py) reads each VM's bridge session_id over gcloud compute ssh. A worker is:
- idle — bridge registered, ready for tasks
- busy — running a task
- offline — no bridge session ID found
The dispatcher (dispatcher.py) is a FIFO queue:
- User submits a task →
POST /api/task - Dispatcher picks the next idle worker
- Creates an H Company session (inline
Agentwith the worker'ssession_id) - Watcher long-polls
get_session_changes→ streams events to the UI via WebSocket - On completion, runs task-specific verification (
verify.py) if configured - Worker is released back to idle
Pre-built tasks in prompts.py with app-specific context:
create_sale— Create & quote a sale (Office Chair x4) in Trytoncreate_customer— Create a customer with email contact in Trytoncreate_product— Create a salable product in Trytonattach_document— Attach a contract file to a sale in Tryton
Or type a free-text task — the dispatcher enriches it with the base preamble and answer guidance.
Set DEMO_MODE=1 to enable:
- Desktop reset before every task (rewinds to a pristine baseline tarball)
- Reset/capture buttons in the UI sidebar
- Per-worker reset via
POST /api/workers/{name}/reset
The New Task dialog has a microphone button (Gradium speech-to-text). Set GRADIUM_KEY in the repo root .env.
WorkFleet/
├── web/
│ ├── backend/ # FastAPI control plane
│ │ ├── app/
│ │ │ ├── main.py # Routes + WebSocket
│ │ │ ├── dispatcher.py # FIFO queue → idle worker → hai session
│ │ │ ├── pool.py # Worker pool (gcloud ssh session_id discovery)
│ │ │ ├── hai.py # hai-agents SDK wrapper (create_session, stream)
│ │ │ ├── prompts.py # Base preamble + app context + presets
│ │ │ ├── feed.py # Event → FeedEvent transformer
│ │ │ ├── verify.py # Post-task DB verification
│ │ │ ├── reset.py # Demo mode desktop reset
│ │ │ ├── stt.py # Gradium speech-to-text
│ │ │ ├── ws.py # WebSocket manager
│ │ │ ├── config.py # Settings from env/.env
│ │ │ ├── db.py # SQLite persistence
│ │ │ └── models.py # Pydantic models (Task, Worker, FeedEvent)
│ │ ├── workers.json # Worker VM declarations
│ │ └── requirements.txt
│ ├── frontend/ # React/Vite/Tailwind
│ │ └── src/
│ │ ├── App.tsx # Main layout
│ │ ├── components/ # Sidebar, NewTask, SessionView, Feed, etc.
│ │ ├── hooks/ # useFleet (WS state), useTypewriter
│ │ └── lib/ # api client, formatting, mic recorder
│ └── README.md # Detailed webapp docs
├── linux-runtime-proof/ # Proof that hai runs headless on Linux
│ ├── Dockerfile # X11 stack + hai bridge + patches
│ ├── FINDINGS.md # M0/M1 findings + fixes
│ └── scripts/ # Test scripts (e2e, driver, auth, kbd)
├── DESIGN.md # Full architecture design doc
└── README.md # This file
- Frontend: React 18, Vite, TypeScript, Tailwind CSS
- Backend: Python 3.12, FastAPI, uvicorn, SQLite (aiosqlite)
- Agent: H Company
hai-agentsSDK + Holo3 model (cloud) - Bridge:
hai local desktop(pyautogui + pyscreeze + Xvfb on Linux) - Voice: Gradium speech-to-text API
- VMs: GCP Compute Engine (e2-standard-2, Ubuntu 24.04)