DEV Community

Zira
Zira

Posted on

Your Browser Agent Has State. Make It Prove Which State It Used

Browser automation failures are often blamed on the model, the selector, or the website. In long-running agents, a quieter failure is more dangerous: the browser profile is stateful, but the agent treats it as disposable.

A worker can restart with an old cookie jar, a half-completed upload, a different account, or a stale tab. The next action may be syntactically valid and still be directed at the wrong session.

The fix is not “clear the browser every time.” That destroys useful continuity. The fix is to make browser state explicit, scoped, and verifiable before every side effect.

Separate the three kinds of state

Keep these inventories separate:

  1. Agent state: the task ID, plan, checkpoints, request key, and last known outcome.
  2. Browser state: profile directory, cookies, local storage, open tabs, downloads, and browser version.
  3. Remote state: what the website actually accepted, such as a saved draft, sent message, or completed checkout.

A restart is safe only when the worker can reconcile all three. A browser profile existing on disk does not prove that it belongs to the same task or identity. A successful click does not prove that the remote side accepted the side effect.

Give every browser session an identity

Create a session record before launching the browser:

run_id=run-2026-08-09-001
browser_session_id=bs-7f2c
profile_path=/var/lib/agent/profiles/bs-7f2c
expected_origin=https://example.test
expected_account_marker=acct-test-42
profile_generation=3
last_checkpoint=before-upload
Enter fullscreen mode Exit fullscreen mode

The important fields are not the names. They are the values the worker can check after a crash. Never infer identity from a profile directory name alone.

Verify the target before a side effect

Before a browser action that changes remote state, run a safe probe. The probe should verify at least:

  • current origin and expected route
  • authenticated account marker, not just “a cookie exists”
  • browser session ID or generation where the application exposes one
  • task-specific object ID
  • absence of an unexpected interstitial, login page, or consent screen

Then record the probe result with the request key for the side effect.

For Playwright, the shape is deliberately boring:

async function assertTarget(page, expected) {
  const url = new URL(page.url());
  if (url.origin !== expected.origin) throw new Error('wrong origin');
  if (!url.pathname.startsWith(expected.pathPrefix)) {
    throw new Error('wrong route');
  }

  const marker = await page.locator('[data-account-id]').getAttribute('data-account-id');
  if (marker !== expected.accountId) throw new Error('wrong account');

  if (await page.getByText('Sign in').count()) {
    throw new Error('unexpected login state');
  }
}
Enter fullscreen mode Exit fullscreen mode

A URL check alone is weak. Multi-tenant applications frequently serve the same route to multiple identities.

Checkpoint around side effects

Use a durable checkpoint before and after each mutation:

Checkpoint Meaning
INTENT_RECORDED request key and normalized arguments are durable
TARGET_VERIFIED origin, route, identity, and object marker passed
ACTION_SUBMITTED browser action was dispatched
REMOTE_CONFIRMED the application returned an authoritative result
UNKNOWN the worker crashed or timed out before confirmation

Do not turn UNKNOWN into an automatic retry. Reopen the same profile, verify the target, and reconcile using the request key or remote object ID. If the site has no idempotency primitive, use a read-only search or status page before deciding whether to retry.

This is especially important for actions such as sending a message, creating an issue, publishing a post, or submitting a payment. A browser timeout means “the worker lost knowledge,” not “the website did nothing.”

Test the failures you actually have

A useful browser-agent test rig injects failure at these boundaries:

  1. after profile creation but before navigation
  2. after login but before the identity probe
  3. after target verification but before the click
  4. after the click but before the response is observed
  5. after the remote side effect but before the local checkpoint
  6. after a browser crash and profile lock recovery
  7. when the profile is restored from an older backup
  8. when two workers try to use the same profile

For every fixture, assert four things:

  • no action is sent to the wrong account
  • a confirmed action is not duplicated
  • an ambiguous action is visible as UNKNOWN
  • the next worker can recover or safely stop

Also test negative paths: expired cookies, a reused profile with a different account marker, a changed browser version, a stale tab, and a page that looks correct but has a different tenant ID in its API responses.

Make profile lifecycle an operational decision

For a disposable task, use a fresh profile and delete it after evidence is retained. For a long-lived OpenClaw or browser agent, persist the profile only with:

  • an explicit owner and purpose
  • a backup and restore test
  • a lock or lease preventing concurrent use
  • a credential rotation procedure
  • a maximum profile age or generation policy
  • logs that never expose cookie values or authorization headers

An always-on runtime such as managed OpenClaw hosting on Ampere can solve the “where does the worker live?” part of this problem. It does not prove that the profile is isolated, that credentials survive a restart correctly, or that a remote side effect was not duplicated. Those checks remain part of your application contract.

A ten-minute acceptance drill

Before calling a browser agent production-ready:

  1. Start a task with a known test account and object.
  2. Persist the run, browser session, profile generation, and request key.
  3. Kill the worker after target verification.
  4. Restart it with the same profile and verify the identity marker again.
  5. Inject a kill immediately after a known mutation.
  6. Reconcile using a read-only remote check.
  7. Confirm the final state and classify the local result as confirmed or UNKNOWN.
  8. Restore the profile from backup and prove the wrong-account fixture fails closed.

If these steps are not repeatable, the browser is not just an execution surface. It is an untested database containing credentials and side-effect context.

The model can choose the next action. The control plane must prove which browser state authorized it, which remote object received it, and what happened when the worker disappeared.

Top comments (0)