Gull Runtime: Browser Automation
Gull is a containerized browser automation runtime that wraps the agent-browser CLI with automatic session isolation and browser state persistence. It exposes a thin HTTP API (/exec, /exec_batch) for executing browser commands.
Container Base Image
- Base:
python:3.11-slim-bookwormwith uv package manager - Browser Engine: Chromium (via Playwright)
- CLI:
agent-browser(globally installed via npm) - Node.js: v20 LTS
- Workspace:
/workspace(shared Cargo Volume with Ship container)
Architecture
┌──────────────────────────────────────┐
│ Gull Container │
│ │
│ ┌──────────────────────────────┐ │
│ │ FastAPI Wrapper │ │
│ │ POST /exec │ │
│ │ POST /exec_batch │ │
│ └──────────┬───────────────────┘ │
│ │ subprocess │
│ ┌──────────┴───────────────────┐ │
│ │ agent-browser CLI │ │
│ │ + Playwright Chromium │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ ┌──────────┴───────────────────┐ │
│ │ /workspace (Cargo Volume) │ │
│ │ ├── .browser/profile/ │ │
│ │ └── (shared with Ship) │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
Execution Model (Critical)
Treat cmd as a single command line that is split into arguments and executed directly. It is NOT a shell script.
What this means
- No shell operators:
>,|,&&,;, subshells, heredocs — none of these work - No environment expansion:
$VAR,~, globbing (*.png) are not interpreted - Quote arguments with spaces:
fill @e1 "hello world"✅ vsfill @e1 hello world❌ - Orchestrate control flow in the agent loop, not inside
cmd
Critical Rules (Non-negotiable)
- Never include
agent-browserprefix — Gull auto-injects it. Sendingagent-browser open ...becomesagent-browser agent-browser open ...and fails. - Never use
execute_shellfor browser commands — Ship container does not have agent-browser installed. - Always re-snapshot after DOM changes — Element refs (
@e1,@e2) are invalidated by navigation, form submissions, and dynamic content loading. - Never include
--sessionor--profileincmd— These are automatically injected by Gull for isolation and persistence.
Auto-injected Parameters
| Parameter | Value | Purpose |
|---|---|---|
--session |
Sandbox ID | Browser instance isolation |
--profile |
/workspace/.browser/profile/ |
Persistent browser state (cookies, localStorage, IndexedDB, cache) |
Core Workflow: Navigate → Snapshot → Interact → Re-snapshot
Every browser automation follows this cycle:
open https://example.com/form
snapshot -i
# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Submit"
fill @e1 "user@example.com"
fill @e2 "password123"
click @e3
wait --load networkidle
snapshot -i # MUST re-snapshot — previous refs are now invalid
Command Reference
Navigation
open <url> Navigate to URL (aliases: goto, navigate)
back Go back
forward Go forward
reload Reload page
close Close browser
Snapshot (Page Analysis)
snapshot -i Interactive elements with refs (recommended)
snapshot -i -C Include cursor-interactive elements (divs with onclick, cursor:pointer)
snapshot -s "#selector" Scope to CSS selector
snapshot -i --json JSON output for structured parsing
snapshot @e9 Show subtree under a container ref
Interaction (use @refs from snapshot)
click @e1 Click element
dblclick @e1 Double-click element
fill @e2 "text" Clear field and type text
type @e2 "text" Type without clearing
select @e1 "option" Select dropdown option
check @e1 Check checkbox
uncheck @e1 Uncheck checkbox
press Enter Press key
press Control+a Key combination
hover @e1 Hover over element
scroll down 500 Scroll page (direction + pixels)
scrollintoview @e1 Scroll element into view
drag @e1 @e2 Drag from one element to another
upload @e1 /workspace/file.pdf Upload file to input element
Get Information
get text @e1 Get element text
get text body Get all visible page text
get html @e1 Get element HTML
get value @e1 Get input value
get attr @e1 href Get element attribute
get title Get page title
get url Get current URL
get count ".item" Count matching elements
get box @e1 Get element bounding box
get styles @e1 Get computed styles
Wait
wait @e1 Wait for element to appear
wait --load networkidle Wait for network idle
wait --url "**/dashboard" Wait for URL pattern
wait --text "Success" Wait for text to appear
wait --fn "window.ready" Wait for JS function to return truthy
wait 2000 Wait milliseconds
Capture (Artifacts)
Write artifacts to /workspace so they are accessible cross-container:
screenshot Screenshot to temp dir
screenshot /workspace/page.png Screenshot to specific path
screenshot --full /workspace/full.png Full-page screenshot
pdf /workspace/page.pdf Save page as PDF
Semantic Locators (Alternative to Refs)
When refs are unavailable or unstable:
find text "Sign In" click
find label "Email" fill "user@test.com"
find role button click --name "Submit"
find placeholder "Search" type "query"
find testid "submit-btn" click
find first ".item" click
find last ".item" click
find nth 2 "a" hover
State Checks
is visible @e1
is enabled @e1
is checked @e1
JavaScript Evaluation
eval document.title
eval 2+2
Tabs / Windows / Frames / Dialogs
tab List tabs
tab new [url] Open new tab
tab 2 Switch to tab
tab close Close current tab
window new Open new window
frame "#iframe" Switch to iframe
frame main Switch back to main frame
dialog accept [text] Accept dialog
dialog dismiss Dismiss dialog
Storage & Network
cookies List cookies
cookies set name value Set cookie
cookies clear Clear all cookies
storage local List localStorage
storage local key Get localStorage value
storage local set k v Set localStorage value
storage local clear Clear localStorage
network route <url> Intercept requests
network route <url> --abort Block requests
network route <url> --body '{}' Mock response body
network unroute [url] Remove route
network requests Show captured requests
network requests --filter api Filter by pattern
Browser State Management
state save /workspace/auth-state.json Export browser state
state load /workspace/auth-state.json Import browser state
Security note: State files may contain sensitive tokens. Do not commit or share them.
Ref Lifecycle (Must Understand)
Refs (@e1, @e2, etc.) are ephemeral. They become invalid when:
- Clicking links or buttons that cause navigation
- Form submissions
- Dynamic content loading (dropdowns, modals, AJAX updates)
- Page refresh
Always re-snapshot after any action that may change the DOM:
snapshot -i
click @e1 # may navigate or change DOM
snapshot -i # MUST re-snapshot
click @e1 # use the NEW page's @e1
Persisting Text Output to Files
Because cmd is not a shell, get text body > page.txt will not work.
Correct workflow:
- Run a
get ...command and capture the returned stdout - Write the captured text into a file via filesystem tools (e.g.,
write_file)
Single vs Batch Execution
| Scenario | Approach | Reason |
|---|---|---|
| First visit to a page | Single: open → snapshot -i |
Need to see page structure before interacting |
| Known form with stable refs | Batch: fill → fill → click |
Deterministic, saves round trips |
| Login with unknown form structure | Single: multiple calls with snapshots | Need intermediate analysis |
| Page navigation + verification | Single: click → snapshot -i |
Must re-snapshot after navigation |
| Scraping multiple items | Single: snapshot → get text per item |
Need to analyze each result |
| Open page + wait + screenshot | Batch: open → wait → screenshot |
Deterministic sequence |
Browser State Persistence
Gull automatically persists browser state to /workspace/.browser/profile/ on the shared Cargo Volume:
| Persisted Data | Details |
|---|---|
| Cookies | Session and persistent cookies |
| localStorage | Key-value storage |
| IndexedDB | Structured storage |
| Service Workers | Background workers |
| Cache | HTTP cache |
This state:
- Survives container restarts within the same sandbox
- Is automatically cleaned up when the sandbox is deleted
- Enables "login once, reuse later" within the same sandbox lifecycle
Common Patterns
Form Submission
open https://example.com/signup
snapshot -i
# Analyze output to identify form fields
fill @e1 "Jane Doe"
fill @e2 "jane@example.com"
select @e3 "California"
check @e4
click @e5
wait --load networkidle
snapshot -i # Verify submission result
Authentication Flow
# Step 1: Navigate
open https://app.example.com/dashboard
snapshot -i
# Step 2: Analyze snapshot
# → If "Welcome" visible → already logged in → proceed
# → If "Sign In" form visible → fill credentials:
fill @e1 "user@example.com"
fill @e2 "password"
click @e3
wait --load networkidle
snapshot -i
# Step 3: Verify login result
# → Success → continue task
# → Failure → analyze reason and adapt
Data Extraction
open https://example.com/products
snapshot -i
get text @e5 # Get specific element text
get text body # Get all page text
snapshot -i --json # JSON output for structured parsing
Screenshot and Cross-Container Processing
# Gull: take screenshot (writes to shared volume)
screenshot /workspace/page.png
# Ship: process it via Python
# execute_python: from PIL import Image; img = Image.open('page.png')
Deterministic Batch
# No intermediate reasoning needed — use batch
commands: [
"open https://example.com",
"wait --load networkidle",
"screenshot --full /workspace/full.png",
"snapshot -i"
]
Error Reference
| Error | Cause | Solution |
|---|---|---|
agent-browser agent-browser ... |
Included agent-browser prefix in cmd |
Remove prefix |
Element not found: @e5 |
Ref invalidated by DOM change | Re-snapshot with snapshot -i |
Command timed out after Ns |
Page/element load too slow | Increase timeout parameter |
agent-browser not found |
Ran browser command on Ship (wrong container) | Use execute_browser, not execute_shell |
Sandbox does not have browser capability |
Profile lacks browser support | Use browser-python profile |
Deep-Dive Reference
For complex workflows, conditional branching, file uploads/downloads, debugging flaky automation, and advanced topics (tabs, frames, network interception, proxy, recording), see references/browser.md.