← all articles

Browser agent production checklist: from demo to real traffic

browser agents production checklist

A browser agent demo needs one happy path. Production needs an answer for the run at 2am where the login cookie has expired, the site answers with a 429, and the model decides the “Place order” button didn’t work and clicks it again.

This is the list I’d go through before letting a browser agent loose on real traffic. It applies whether you’re running Playwright plus a model you call yourself, a computer-use API, or a hosted agent platform. Most of it is ordinary distributed-systems hygiene. The agent makes each failure stranger, because the thing deciding what to do next is a model looking at a screenshot.

Not every item applies to every agent. A read-only research agent can skip idempotency. An agent that spends money can’t.

What you need

  • A browser automation layer you control, such as Playwright or the DevTools protocol, even if a hosted service runs the browsers.
  • A task runner with states (queued, running, paused, done, failed) that survives a worker restart.
  • Storage for session state, traces and screenshots, with access control.
  • A secrets manager.
  • A budget number: what one successful task is allowed to cost.

Sessions and identity

Decide what one identity is. Usually it’s one end user, or one account you own. Each identity gets its own browser context, cookies and storage, and never shares them with another.

  • Persist state between runs. Playwright’s storageState saves cookies, local storage, IndexedDB and passkey-based auth. It does not persist sessionStorage; the docs include a workaround if a site depends on it.
  • Keep the state files out of git. Playwright’s docs warn that they “may contain sensitive cookies and headers that could be used to impersonate you.” Handle them like passwords.
  • Pin the network identity to the session. A logged-in session that hops IP addresses mid-task gets logged out or challenged on a lot of sites. One identity, one stable exit for the length of the session.
  • Check for a logged-out state explicitly after each load. Look for a known element instead of letting the model discover on step 9 that it has been staring at a login form.
# once, after a human-supervised login
context = browser.new_context()
page = context.new_page()
# ... log in ...
context.storage_state(path="state/user-123.json")

# every later run for that identity
context = browser.new_context(storage_state="state/user-123.json")

If it breaks: a session that dies every run usually means something the site uses isn’t in the saved state (sessionStorage is the usual suspect), or the exit IP changed between save and load.

Retries

Classify the failure before you retry anything.

  • Network errors and 5xx responses: retry with exponential backoff and jitter, and a ceiling. Three attempts covers most steps.
  • 429: wait for Retry-After if the server sent it, and lower your concurrency for that domain.
  • 403 or a challenge page: stop. Hammering a site that just blocked you is the quickest way to turn a short block into a long one. Read why AI browser agents get blocked before adding more attempts; retries don’t fix a reputation problem.
  • Model errors (a malformed tool call, a refusal): retry once with the error in context, then fail the step.

Retry the step, not the whole task, where you can. Re-running a 20-step task from the top because step 18 timed out pays for 17 steps again and repeats every side effect on the way.

Cap retries per task and per domain, so one bad site can’t tie up the whole worker pool.

Idempotency

Sooner or later the model will repeat an action it already took. The screenshot came back before the confirmation rendered, the page looked unchanged, so it clicked again.

  • List every step with a side effect: submit, send, pay, book, post, delete.
  • Before each one, check whether it already happened (the order list, the sent folder, a status page). The model’s memory of what it did is not evidence.
  • Where you control the backend, use idempotency keys. Stripe’s API is the standard example: send an Idempotency-Key header, and later requests with the same key get back the saved result of the first one, even if that was an error. Generate one key per logical action and store it with the task.
  • Mark each tool the agent can call as safe or unsafe to repeat, and make the runner enforce it.

Timeouts

You need a timeout per action (one click or navigation), per step (a model turn plus the actions it chose) and per task.

Wait for concrete states instead of fixed sleeps. Playwright locators auto-wait for elements to be actionable, so a sleep(5) in agent code is almost always a bug waiting for a slow day.

Give each task a wall-clock budget and kill it cleanly when it runs out, saving the trace on the way down.

Sandboxing

Anthropic’s computer use documentation lists precautions that apply to any browser agent, whichever model runs it:

  • a dedicated virtual machine or container with minimal privileges
  • no model access to sensitive data such as account logins
  • internet access limited to an allowlist of domains
  • a human confirming decisions with real-world consequences, like financial transactions or agreeing to terms of service

The same page warns that instructions on web pages or inside images can override yours. That’s prompt injection, and every agent that reads arbitrary pages is exposed to it. Isolation is the part you control.

In practice: one container per task or per identity, no host filesystem mounts, egress through a proxy you own so you can log and allowlist, and nothing inside the sandbox you’d mind a hostile page reading.

Secrets

Credentials never go in the prompt. Keep them in the secrets manager and give the agent a tool that types the password straight into the field, so the model sees “password entered” and never the value.

Scope every credential to the minimum. A read-only agent gets a read-only account.

Redact screenshots and logs before storing them if they can contain personal data or tokens, and rotate anything the agent touched when you retire it.

Logging and traces

You’ll need to answer “what did it do?” for any task, on demand. At minimum log the task input, each model turn, each action with its target, a screenshot per step, network errors and the final outcome. Playwright’s trace viewer records DOM snapshots, a screencast, network requests and console output for every action:

context.tracing.start(screenshots=True, snapshots=True)
# ... run the task ...
context.tracing.stop(path=f"traces/{task_id}.zip")

Open one with npx playwright show-trace traces/<id>.zip. What to log beyond that, and how to use it, is in observing AI agents: traces, replays and cost per task.

Cost caps

  • Set a hard ceiling on model spend per task. Count tokens as the task runs and stop it when it crosses the line.
  • Cap steps too. A task that normally finishes in 15 steps and is on step 60 is stuck, and stopping it early is cheaper than letting the model keep trying.
  • Screenshots are usually the expensive part of a computer-use loop. Send smaller images and drop old screenshots from context once they’ve done their job; Anthropic’s docs recommend both for long loops.
  • Report cost per completed task. Per-call spend hides the retries and the failures, and cost per completed task is the number that says whether the agent is worth running.

Human checkpoints

Decide up front which actions need a person: payments, messages sent to other people, accepting terms, deleting data, anything you can’t undo. The agent pauses, shows the human a screenshot of what it’s about to do, and waits.

The same applies to CAPTCHAs and surprise verification prompts. A CAPTCHA is the site asking whether a human is there, so hand that step to the human the agent works for. Don’t route it to a solving service.

Build the pause as a real state in the task runner (paused, awaiting approval, resumed or expired), with its own timeout. Otherwise a task waiting on someone who has gone home holds a browser open all weekend.

Common pitfalls

  • Testing only from your laptop. Your home IP and logged-in browser profile hide every access problem the production container will hit.
  • Sharing one browser profile across identities to save memory. Sessions bleed into each other and you get logged out in ways that look random.
  • Treating the agent’s “done” as the outcome. Check independently that the file exists or the order shows up.
  • No per-domain concurrency limit, so a batch of 200 tasks against one site arrives all at once.

Scaling this

At 10 tasks a day you can read every trace. At 1,000 you can’t, so the stops, caps and alerts have to be automatic, and you review a sample. By then the bottleneck is usually access and session health more than model quality. If some of your tasks need a real phone instead of a browser, the checklist shifts again; phone-use agents on real Android covers that side.

What I haven’t covered here is whether the agent’s answers are correct. That’s evaluation, a separate problem from keeping it running, and it gets its own piece. More in the article index.

Written by Xavier Fok

disclosure: The Agent Ops Report is published by the team that runs Singapore Mobile Proxy and cloudf.one. Any link to those two products is ours and is marked sponsored. No vendor paid for a mention in this article. Last reviewed by Xavier Fok on 2026-09-11.

from the team behind this site
A Singapore mobile IP for browser agents

Singapore Mobile Proxy runs real mobile IPs on SingTel, StarHub and M1, with sticky sessions so one task keeps one IP. Singapore only: a fit for SEA or location-agnostic work, the wrong tool if you need a US IP.

see plans →
from the team behind this site
A real Android phone for phone-use agents

cloudf.one hosts real Android phones in Singapore on dedicated hardware, each with a persistent Singapore mobile IP. For agents that need an actual device and a stable carrier identity.

get a phone →
read on
More from The Agent Ops Report

Blocks, sessions, retries, traces, cost per task and phone-use agents. Browse all articles →