Skip to main content
AgentMailr is now Lumbox. Same product, new name. Learn more →
Endpoint // POST /v1/agent-tasks

browser_act

Plan-then-execute browser automation. One Anthropic call generates a deterministic step plan. The executor runs it against Steel Chromium. Zero LLM calls during execution.

Architecture

Most browser agents send the current page to the LLM at every step and ask "what next?" That works, but it costs 20-50 LLM calls per task and the model drifts off-task halfway through. browser_act picks a different architecture: ask the LLM once to write the whole plan, then run that plan deterministically.

┌──────────────────────────────────────────────────────────────┐
│  POST /v1/agent-tasks  { type: "browser_act", goal }         │
└──────────────────────────────────────────────────────────────┘
                              │
                              ▼
              ┌─────────────────────────────┐
              │  ① Planner (1 LLM call)     │
              │  Anthropic claude-sonnet-4-6│
              │  goal → JSON step list      │
              └─────────────────────────────┘
                              │
                              ▼
              ┌─────────────────────────────┐
              │  ② Executor (zero LLM)      │
              │  Steel Chromium, stealth=on │
              │  Runs each step in order    │
              └─────────────────────────────┘
                              │
                              ▼
              ┌─────────────────────────────┐
              │  Status: queued → running   │
              │  → succeeded | failed       │
              │  GET /v1/agent-tasks/:id    │
              └─────────────────────────────┘

Request

Async by design. POST returns immediately with a task_id. You poll the GET endpoint for status.

POST /v1/agent-tasks
X-Org-Id: org_xxx
Content-Type: application/json

{
  "type": "browser_act",
  "goal": "Go to vercel.com/signup, sign up with the bound inbox, paste the OTP, click verify",
  "inbox_id": "inb_xxx",
  "llm_provider": "anthropic",
  "llm_model": "claude-sonnet-4-6"
}

→ 201 Created
{ "task_id": "task_xxx", "status": "queued" }

inbox_id is optional but enables the inbox-aware verbs below. llm_provider and llm_model default to anthropic + claude-sonnet-4-6.

The 10-Verb Plan DSL

The planner emits a JSON array of steps. Each step is one of 10 verbs:

navigate Go to a URL. { type, url }
click Click element by text or selector. { type, describe, text?, selector? }
fill Type into an input. { type, describe, selector?, label?, value }
wait_seconds Sleep. { type, seconds }
wait_for_text Block until text appears on page. { type, text, timeout_seconds? }
wait_for_email Long-poll the bound inbox. { type, inbox_id?, from?, subject?, timeout_seconds? }
use_otp_from_inbox Pull OTP from most recent inbox email, fill it. { type, inbox_id?, selector, timeout_seconds? }
open_link_from_inbox Navigate to magic link from inbox. { type, inbox_id?, from?, timeout_seconds? }
extract_text Pull text from a selector. { type, describe, selector? }
done Terminator. { type, summary }

The last three (wait_for_email, use_otp_from_inbox, open_link_from_inbox) cross the browser/email boundary. They read from the bound inbox in the same runtime - no glue code between agent → email → browser.

Example: Sign Up for Vercel

The planner takes the goal "Go to vercel.com/signup, sign up with the bound inbox, paste the OTP, click verify" and emits something like this:

[
  { "type": "navigate", "url": "https://vercel.com/signup" },
  { "type": "fill", "describe": "email field",
    "selector": "input[name=email]", "value": "{{inbox.address}}" },
  { "type": "click", "describe": "submit button", "text": "Sign Up" },
  { "type": "wait_for_email", "from": "vercel.com", "timeout_seconds": 120 },
  { "type": "use_otp_from_inbox", "selector": "input[name=otp]" },
  { "type": "click", "describe": "verify button", "text": "Verify" },
  { "type": "wait_for_text", "text": "Dashboard", "timeout_seconds": 30 },
  { "type": "done", "summary": "Vercel signup completed" }
]

The executor runs these in order against a stealth Steel Chromium session. Variables like {{inbox.address}} are substituted with values from the bound inbox before execution.

What This Buys You

01
One LLM call per task. The planner runs once. Execution is deterministic. Cost is one Sonnet call regardless of plan length.
02
No drift mid-flow. When the executor hits a missing element it throws. No LLM rationalizing its way through a broken page.
03
Inbox + browser in one runtime. The verbs wait_for_email, use_otp_from_inbox, open_link_from_inbox read from the bound inbox directly. No webhook plumbing between systems.
04
Stealth Chromium. Sessions launch with stealth: true on Steel. Standard anti-bot patches applied at session start.

When Not To Use This

Plan-then-execute is the right architecture for linear, repeatable goals. It is the wrong architecture for:

  • close Goals requiring mid-flow decisions ("find the cheapest plan, then sign up for it")
  • close Sites with adversarial anti-bot challenges that require human-like interaction
  • close Long-running exploratory tasks ("research this product and report what you found")

For those, run a step-level agent loop on top of the same 10 verbs and replan when an executor step fails.

Cancel a Running Task

POST /v1/agent-tasks/:task_id/cancel

The runner checks for cancellation between steps. Task transitions to cancelled on next step boundary.