ego (lite) is just a browser, ego is your personal agent across devices.
Join waitlist
Browser sessionsPlaywrightstorageStateBrowser automationLogged-in sessions

Persistent Browser Sessions Across Agent Runs

Sep 11, 202611 min read
Pixel-art ego agent connecting local and cloud browser windows to illustrate persistent browser sessions

A persistent browser session is one whose authentication state outlives the process that created it. The session is established once, usually by a human; the cookies, local storage, and profile data are written somewhere durable; and a later run starts already signed in instead of starting at a login form.

Getting that to work the first time is easy. Keeping it working is the hard part, because four different things kill sessions and they all look identical from the outside: the profile directory is not being reused, the site issued a session cookie that never reaches disk, the profile is locked by another browser, or the session simply expired on the site's own schedule. This article walks the full lifecycle so you can tell those apart.

What makes a browser session persistent?

Two things have to be true: the authentication state has to be written somewhere that survives the process, and the next run has to be pointed at that same place. If either half is missing, you get a one-time session.

That distinction matters more for agents than for tests. A test suite that signs in at the start of every run is merely slow. An agent asked to check a dashboard every morning cannot re-authenticate unattended, because the sign-in itself is the part that needs a human. Persistence is what converts a one-shot authenticated action into a repeatable one.

The six stages of a session lifecycle

Most persistence problems get misdiagnosed because the failure is reported as a symptom rather than a stage. Sorting the work into six stages makes the diagnosis mechanical, because each stage has its own failure mode and its own fix.

StageWhat it decidesTypical failure at this stage
EstablishWhere the session is created and by whomAutomating the sign-in, then tripping on MFA or a bot check
PreserveWhich state is written, and where the next run reads itA fresh temporary profile each run; state written but never loaded
VerifyWhether the restored session is still acceptedA restored but expired session; the agent reports an application bug
IsolateWho may use the state at the same timeTwo agents on one profile: a lock error, or a silent collision
ExpireHow the session ends and how you detect itSession cookies never reached disk; the site revoked the session
RecoverHow the session gets re-establishedNo re-auth path exists, so the workflow stalls until someone notices

The rest of this article takes the stages in order, with the mechanism comparison first, because the stage that fails is usually decided by the mechanism you picked at the start.

userDataDir, storageState, or a managed session?

Four mechanisms show up in practice. They differ on what they persist, whether they can run concurrently, and how much of your real browsing environment they borrow.

MechanismPersistsDurable acrossNot for
Persistent user data directoryThe whole profile: cookies, local storage, cache, extensions, installed stateProcess restarts that reuse the same directory pathParallel runs, or any task that needs a clean, reproducible environment
Exported storage stateCookies plus per-origin localStorage, optionally IndexedDB and passkeysAnything that can read the file, including CI and another machineSites that bind sessions to device signals the snapshot does not carry
Managed remote sessionA live browser kept running on remote infrastructure, or a named context saved server-sideRequests that arrive before the provider's idle timeout closes itPer-user isolation, unless the provider routes specific users to specific instances
A task Space in a local agent browserThe login environment the browser already holds, reused inside a task-scoped Space with separate tabsRuns inside that browser, for as long as the underlying session lastsHeadless CI, or any setup where the browser cannot stay installed and signed in

What the state file actually contains

In Playwright, browserContext.storageState() returns cookies as an array whose entries carry name, value, domain, path, expires, httpOnly, secure, and sameSite, plus an origins array holding origin and localStorage name/value pairs. Passing a path writes the JSON to disk; without a path the state is returned but not saved. The guide describes reused state as covering cookies, local storage, IndexedDB, and passkey-based authentication.

That file is credential-grade. The official guide warns it "may contain sensitive cookies and headers that could be used to impersonate you or your test account" and strongly discourages checking it into any repository. Treat it exactly like a password: outside the working tree, outside logs, outside the agent's context.

How do you establish the session?

Establish it once, with a human in the loop, in the same environment the agent will later use. This is the stage where automating the sign-in looks tempting and usually costs more than it saves, because MFA prompts, device checks, SSO redirects, and bot checks all interrupt a scripted sign-in, and each workaround pulls credentials closer to the agent's context.

The practical rule is that the human types the credentials and the agent inherits the result. That is also why the environment matters: a session established in a headed browser on your laptop and then restored in a headless container on a server is a session moving between two different device fingerprints, which some sites notice.

If you need to choose where that first sign-in should happen, and which of the connection routes to use, that decision has its own article: How to connect AI agents to your existing browser. It covers DevTools auto-connect, extension injection, CDP takeover, and state copies. This article assumes you picked one and now need it to keep working.

How do you preserve it between runs?

Preservation is a path problem before it is a browser problem. In Playwright, a normal browser context lives in memory and shares nothing with other contexts, so it is gone when the process ends. A persistent context changes that by taking a user data directory as its first argument, described in the API reference as the path that "stores browser session data like cookies and local storage." Cookies and local storage go to that directory and are read back on the next launch.

The alternative is to leave the browser stateless and carry the state in a file: export with storageState(), then seed the next context with it. The official authentication guide builds its whole recommended workflow on this, writing state to a playwright/.auth directory that belongs in .gitignore.

The two are not interchangeable, and the decision comes down to one question: does the target site care that it is the same browser, or only that it is the same account? If the site only checks the cookie, an exported state file is lighter, portable, and safe to run in parallel. If the site correlates device, timezone, language, and cache signals across visits, the full directory is what keeps those signals coherent.

How do you verify the session is still valid?

Nothing in the persistence mechanism checks validity. A restored cookie that the site has since revoked restores perfectly and fails on first use, and the agent then reports whatever the site rendered, which is usually a login page or an empty dashboard. Verification is therefore a step you add, not a feature you enable.

Three checks cover most cases. Navigate to a page that requires authentication and assert on a post-login element you know exists, rather than on the absence of an error. Compare the final URL against the login URL, since a redirect to sign-in is the clearest possible signal. And keep an authenticated marker outside the page itself, such as an account identifier in the response, so a page that renders an empty shell does not read as success.

What that looks like in practice is less dramatic than it sounds. In a live run for this article, a Space held an authenticated store session with one item in the cart, and the same state was still present after a page reload in that Space. The check was not "did the page load" but "is the badge still 1 and does the product row still read Remove": state that only exists if the session is genuinely alive.

A terminal on the left running an agent-controlled browser task beside a live browser Space on the right showing a signed-in store's product list with an empty cart
The establishing frame for the run behind this article, captured 2026-09-11 on ego (lite) 0.5.0.31. The controller is on the left and the live Space is on the right, in agent control, showing the authenticated product list with an empty cart.
A terminal beside a live browser Space showing a cart containing one Sauce Labs Backpack after the authenticated session survived a page reload
The verified page state after the reload: one backpack remained in the cart and the cart badge still read 1. This observation is limited to the same Space, tab, and running browser process; it is not evidence of cross-process persistence.

The same run provides the contrast case. After the session ended, navigating to the protected inventory route did not render an empty page: it returned a login page carrying an explicit rejection message. That is what a lost session looks like from the page side, and it is far easier to assert on than a blank screen.

A live browser Space showing a store login page with a red error banner stating the inventory page can only be accessed when logged in
The failure state, observed in the same run: the session had ended, and the protected route returned a visible rejection banner rather than an empty page. A reusable check can assert on this message instead of guessing from a blank screen.

Add that verification to the start of every run and the failure moves from "the agent gave a wrong answer" to "the session needs re-establishing", which is a much cheaper problem.

What happens when two agents share one profile?

A persistent profile is single-tenant, and this is documented rather than incidental. The Playwright API reference states plainly that "browsers do not allow launching multiple instances with the same User Data Directory." The Playwright MCP documentation is more specific about the consequence: "A profile can only be used by one browser at a time," and if it is already locked, "the server fails to start." That is the good version of the failure, because it fails loudly at launch instead of silently sharing cookies.

The bad version is the silent one. If two tasks end up pointed at the same state, they can write over each other's cookies and localStorage: one task's logout, or its switch to a different account, becomes the other task's session. This is the mechanism behind a class of bug that looks like flakiness, where two runs pass individually and fail together.

Managed remote sessions have a related but different isolation problem. A session-reuse pattern that picks any free browser off a list, rather than routing a specific user to a specific instance, can hand one request a browser another user just used. The vendor documentation for that pattern describes it as best for "stateless workloads where any available browser session will do," which is an honest description of when the isolation question does not apply.

The three workable answers are: one profile per agent, one account per parallel worker, or one task-scoped workspace with separate tabs. The official authentication guide takes the second route, allocating a separate account per parallel worker with its own state file, and tells you to authenticate in a clean environment by unsetting storage state first. An agent Space provides the third route inside a browser. It separates task tabs and control, but it is not by itself a process, VM, or tenant-security boundary.

Isolation needApproachCost
Two tasks, same account, no overlap in timeOne shared profile, serialized with a queue or a lock you ownThe tasks cannot run in parallel; you must handle lock contention yourself
Two tasks running at once, same siteSeparate profiles, or one account per worker with its own state fileMore accounts or more disk; each profile re-authenticates separately
Many tasks, shared login, no shared writesPer-task Space that reuses explicitly provisioned browser state while keeping task tabs separateRequires a browser that models per-task workspaces natively; this does not provide remote host isolation

Why did the session stop working?

Four causes account for most cases, and they are distinguishable by when the failure appears.

SymptomLikely causeWhat to check
Signed in within one process, signed out on the nextSession cookies were never written to disk; the profile only holds persistent cookiesExport cookies through the browser API and re-add them after launch, rather than trusting the profile file
The browser refuses to start, or the server exits at launchAnother browser instance holds the profile lock, often an orphaned process from a crashed runRemove or wait out the stale process, and give each concurrent task its own directory
Worked for days, then stopped with no code changeThe site expired or revoked the session on its own scheduleNothing in your code. This is the re-establish path, and it needs a human
Works locally, fails in CI or on a serverState file missing or not loaded there, or the session is bound to signals the new environment does not reproduceConfirm the file is present at runtime and that the run actually reads it; then check whether the site correlates device signals

How do you recover an expired session?

Recovery is not the inverse of persistence, and it cannot be fully automated. The official guidance is direct about the maintenance burden: "you need to delete the stored state when it expires." What follows is the procedure that turns that into something an agent can drive.

  1. Detect, do not assume. Run the verification check at the start of the task, before any work that depends on being signed in.
  2. Stop at the boundary. An expired session is a stop condition, not a retry condition. Retrying against a revoked session produces the same failure and, on sites that count attempts, makes things worse.
  3. Hand the visible window to a human for the sign-in, including whatever second factor the site requires, then resume in the same session.
  4. Re-export the state after the human finishes, and replace the stored copy rather than appending to it.
  5. Re-run the verification check to confirm the new state works, so the next unattended run starts from a known-good position.

Step five is the one most setups skip, and it is the one that decides whether the failure recurs on the next run or not. In the run behind this article, the recovery path was exactly this shape: after the session ended, a human signed in again in the same Space, and the authenticated state was re-confirmed afterwards rather than assumed.

A live browser Space on the right showing a signed-in product list with a Remove button on one product, beside a terminal on the left listing verification results for each lifecycle step
After a human signed in again in the same Space, the authenticated state and the cart were re-verified rather than assumed. Observed 2026-09-11 in ego (lite) 0.5.0.31, scoped to one Space, one tab, and one running browser process.

When should a human take over?

Persistence changes what a human has to do, not whether one is needed. The moments that require a person are stable and predictable: a first sign-in to establish the session, any second factor, QR or hardware-key logins, consent and permission screens, payment or irreversible actions, and re-authentication after expiry.

Design for those as handoffs instead of exceptions. The practical test is whether the human is asked to act inside the same session the agent will continue in. If the person signs in somewhere else and the agent keeps a different browser, the handoff did not happen; the agent is simply waiting for a state it will never see.

Where ego (lite) fits

ego (lite) is a complete local Chromium browser designed for people and AI agents; in product-category terms, it is an agent browser. You can use it as an everyday browser, while compatible agents control it through ego-browser. During onboarding, ego (lite) can import Chrome tabs, bookmarks, passwords, extensions, cookies, login sessions, and profiles, so an agent can start from browser state you explicitly provision instead of signing in from scratch. That import does not guarantee that every website session remains valid: cookie expiry, re-authentication, 2FA, CAPTCHA, site policy, and account permissions still apply.

Each agent task runs in its own visible Space with separate tabs, while you continue browsing elsewhere. When a site requires sign-in or verification, you can take over that same Space, complete the step yourself, and return control to the agent. A Space makes that handoff practical, but its workspace boundary is not a remote security sandbox and it does not guarantee that a particular session is still valid.

ego (lite) fits local browser work that benefits from user-provisioned login state, visible execution, several parallel Spaces, and quick human takeover. It is not the right tool when the task must run headless in CI, start from a reproducible clean image, scale as an elastic remote fleet, or keep untrusted pages off the local machine. In those cases, use an exported test state with ordinary browser contexts, a remote sandbox, or a hybrid architecture.

Troubleshooting checklist

Work down the list in order. The earlier items are cheaper to check and cause more failures than the later ones.

  1. Confirm the launch actually reused your state path. A fresh temporary profile each run is the most common cause, and it looks exactly like expiry.
  2. Verify the session instead of trusting it: assert on a post-login element and on the final URL, not on the absence of an error.
  3. If it fails across processes but works within one, suspect session cookies that never reached disk, and export cookies through the browser API.
  4. If the browser will not start, check for a profile lock from a crashed or still-running instance on the same directory.
  5. If two tasks interfere, they are sharing state. Give each one its own profile or its own account before looking anywhere else.
  6. If it fails only on a server, confirm the state file is present and loaded at runtime, then consider whether the site correlates device signals.
  7. If it worked for days and then stopped, it is site-side expiry. Re-establish with a human and re-export the state.
  8. Re-run the verification after every recovery, so the next unattended run starts from a confirmed session.

FAQ

How long does a persistent browser session last?

As long as the site lets it. Your mechanism sets the ceiling and the site sets the floor: the profile directory will happily hold a cookie forever, while the server can invalidate it in minutes or after a fixed number of days. That is why verification belongs in the run rather than in a one-time setup check.

Is storageState enough, or do I need a full profile?

An exported state file is enough when the site authenticates on cookies and localStorage and does not otherwise care which browser is visiting. A full profile is the safer choice when the site correlates device, timezone, locale, or cache signals across visits, because the directory keeps those consistent while a state file does not.

Can two agents use the same persistent session at once?

Not against the same user data directory. Official documentation states that browsers do not allow multiple instances on one user data directory, and that a locked profile stops the server from starting. If two tasks genuinely need to run concurrently, give them separate profiles or separate accounts. Sharing one profile serially is fine; sharing it concurrently is not.

Can an agent re-authenticate on its own?

Only where the site offers a non-interactive path, such as a token endpoint or an API login, and only if you decide an agent should hold those credentials. For a normal password plus second factor, no: automating it either drags credentials into the agent's context or defeats the control the site put there on purpose. Treat re-authentication as a handoff.

Does storageState include session storage and session cookies?

Playwright does not provide an API to persist session storage, and its guide notes that session storage is scoped to a domain and does not survive page loads. The documented workaround is to serialize it yourself in page.evaluate and replay it with an init script, restricted to the intended hostname. This is a real distinction: page-level session storage is transient browser state, closer to the DOM than to your cookies, and it is not what keeps you signed in.

Is it safe to share a session state file with a team?

Treat it as a live credential for the account it belongs to, because that is what it is. Keep it out of version control, out of CI logs, and out of anything an agent reads back into its context, and delete it when it expires. If a teammate needs access, they should establish their own session rather than receive yours.

If the remaining question is which route should create the session in the first place, the connection-route comparison covers DevTools auto-connect, extension injection, CDP takeover, and state copies side by side: connecting an agent to your existing browser.