Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 13 additions & 22 deletions browsers/pools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -205,37 +205,34 @@ As a best practice, release each browser when you're done with it — that retur

## Profiles with browser pools

A [profile](/browsers/profiles) carries login state, including cookies and local storage, into a browser. Use [Managed Auth](/auth/managed-auth) to populate that state and monitor its health. Put the profile on the browser pool when every browser should share one identity; leave it off and attach it after acquiring when each task needs its own (see [Per-user profiles with browser pools](#per-user-profiles-with-browser-pools)).
A [profile](/browsers/profiles) carries login state, including cookies and local storage, into a browser. Use [Managed Auth](/auth/managed-auth) to populate that state and monitor its health. Put the profile on the browser pool when every browser shares one identity; leave it off and bind one at acquire when each task needs its own (see [Per-user profiles with browser pools](#per-user-profiles-with-browser-pools)).

A profile attached to the pool is loaded **read-only**. Every browser in the pool shares it, so `save_changes` doesn't apply and is silently ignored if sent — this prevents concurrent writes from corrupting the profile.

### Per-user profiles with browser pools

Because that profile is shared and read-only, it can't hold per-user login state for many users at once. To serve many users from one browser pool, create it with no profile — stealth, proxies, extensions, and viewport still live on the pool — then attach each user's profile to the browser *after* you acquire it, and release with `reuse: false` so the browser is destroyed. Destroying it keeps that user's state from reaching the next acquirer.
Because that profile is shared and read-only, it can't hold per-user login state for many users at once. To serve many users from one browser pool, create it with no profile — stealth, proxies, extensions, and viewport still live on the pool — then bind each user's profile on the acquire call. The browser is destroyed when the lease ends instead of returning to the pool, which keeps that user's state from reaching the next acquirer.

The read-only rule covers the pool's own profile, not one attached after acquiring: that profile belongs to the browser, so `save_changes` applies as it does on any other browser. Pass `save_changes: true` when you attach it — it defaults to `false`, and without it the browser is destroyed on release without writing the user's session back.
Binding at acquire loads the profile before the browser is handed to you, so you connect to a browser that already has the user's state. There is no second call to make, and no Chromium restart after your CDP client has connected.

The read-only rule covers the pool's own profile, not one bound at acquire: that profile belongs to the browser, so `save_changes` applies as it does on any other browser. Pass `save_changes: true` when you bind it — it defaults to `false`, and without it the browser is destroyed on release without writing the user's session back.

<CodeGroup>
```typescript Typescript/Javascript
const browser = await kernel.browserPools.acquire("my-pool");

await kernel.browsers.update(browser.session_id, {
profile: { name: "user-8f21c3", save_changes: true }
const browser = await kernel.browserPools.acquire("my-pool", {
profile: { name: "user-8f21c3", save_changes: true },
});

// ... drive the browser as that user ...

await kernel.browserPools.release("my-pool", {
session_id: browser.session_id,
reuse: false,
});
```

```python Python
browser = kernel.browser_pools.acquire("my-pool")

kernel.browsers.update(
browser.session_id,
browser = kernel.browser_pools.acquire(
"my-pool",
profile={"name": "user-8f21c3", "save_changes": True},
)

Expand All @@ -244,37 +241,31 @@ kernel.browsers.update(
kernel.browser_pools.release(
"my-pool",
session_id=browser.session_id,
reuse=False,
)
```

```go Go
browser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{})
if err != nil {
panic(err)
}

if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{
browser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{
Profile: shared.BrowserProfileParam{
Name: kernel.String("user-8f21c3"),
SaveChanges: kernel.Bool(true),
},
}); err != nil {
})
if err != nil {
panic(err)
}

// ... drive the browser as that user ...

if err := client.BrowserPools.Release(ctx, "my-pool", kernel.BrowserPoolReleaseParams{
SessionID: browser.SessionID,
Reuse: kernel.Bool(false),
}); err != nil {
panic(err)
}
```
</CodeGroup>

A profile can only be loaded into a browser that was created without one, which is why the pool itself has to stay profile-free.
A profile can only be loaded into a browser that was created without one, which is why the pool itself has to stay profile-free. An acquire-time profile always ends the lease by destroying the browser rather than returning it to the pool, so `reuse` does not apply. If you need to bind a profile to a browser that is already running, use [`browsers.update`](/browsers/profiles/save-and-reuse#load-a-profile-after-browser-creation) — that path restarts Chromium and disconnects your CDP client, so binding at acquire is the better default.

### Refresh on profile update

Expand Down
5 changes: 2 additions & 3 deletions browsers/profiles/concurrency.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ A profile configured directly on a [browser pool](/browsers/pools#profiles-with-
For per-user durable state:

1. Create the pool without a profile.
2. Acquire a browser.
3. Attach the user's profile with `save_changes: true`.
4. Release the browser with `reuse: false` so that user's state can't reach the next acquirer.
2. Bind the user's profile on the acquire call, with `save_changes: true`.
3. Drive the browser, then release it. An acquire-time profile always destroys the browser instead of returning it to the pool, so that user's state can't reach the next acquirer.

See [Per-user profiles with browser pools](/browsers/pools#per-user-profiles-with-browser-pools) for complete examples.

Expand Down
4 changes: 4 additions & 0 deletions browsers/profiles/save-and-reuse.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ Set `start_url` when you create a browser if your automation requires a specific

You can attach a profile to a running browser that was created without one. Loading the profile restarts Chromium, so reconnect your CDP or Playwright client afterward.

<Info>
If the browser comes from a [browser pool](/browsers/pools#per-user-profiles-with-browser-pools), bind the profile on the acquire call instead. It loads before the browser is returned, so your client connects to a browser that already has the profile rather than being restarted underneath it.
</Info>

<CodeGroup>
```typescript TypeScript
const browser = await kernel.browsers.create();
Expand Down
11 changes: 10 additions & 1 deletion reference/cli/browser-pools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,24 @@ Acquire a browser from the pool.
| Flag | Description |
|------|-------------|
| `--timeout <seconds>` | Acquire timeout in seconds. |
| `--profile-id <id>` | Profile to load before the acquired browser is returned. The pool must not already have a profile. |
| `--profile-name <name>` | Profile to load before the acquired browser is returned, by name. Mutually exclusive with `--profile-id`. |
| `--save-changes` | Persist changes back to the acquire-time profile when the session ends. Defaults to `false`; without it the browser is destroyed on release without saving. |
| `--output json`, `-o json` | Output raw JSON object. |

A browser that loads an acquire-time profile is destroyed and replaced on release rather than returned to the pool, so profile state can't leak into another lease. See [Per-user profiles with browser pools](/browsers/pools#per-user-profiles-with-browser-pools).

```bash
kernel browser-pools acquire my-pool --profile-name user-8f21c3 --save-changes
```

## `kernel browser-pools release <id-or-name>`
Release a browser back to the pool.

| Flag | Description |
|------|-------------|
| `--session-id <id>` | Browser session ID to release. |
| `--reuse` | Reuse the browser instance (default: `true`). |
| `--reuse` | Reuse the browser instance (default: `true`). Ignored for a lease that loaded an acquire-time profile, which is always destroyed. |

## `kernel browser-pools delete <id-or-name>`
Delete a browser pool.
Expand Down
Loading