Build and debug Python browser automation with the Kernel SDK, including browser lifecycle, server-side Playwright, CDP, profiles, proxies, and reliable cleanup.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Build and debug Python browser automation with the Kernel SDK, including browser lifecycle, server-side Playwright, CDP, profiles, proxies, and reliable cleanup.
context
fork
When to Use This Skill
Use this skill to write Python that creates and controls Kernel browser sessions. Prefer it for:
Server-side Playwright execution in the browser VM
Client-side Playwright over CDP
Persistent profiles and explicit proxy routing
Browser pools or deployed Python actions
Use the kernel-cli skill for shell commands and quick one-off sessions.
Setup
uv pip install -U kernel
# Add this only for client-side CDP:
uv pip install playwright
Set KERNEL_API_KEY in the environment; do not put it in source. Kernel() and AsyncKernel() read it automatically. Use the async client consistently—do not call synchronous SDK methods from an async action.
Choose an Automation Path
Server-side Playwright (default)
Run JavaScript/TypeScript next to Chrome. This avoids a local CDP round trip and does not require local Playwright:
from kernel import Kernel
with Kernel() as client:
browser = client.browsers.create(stealth=True, timeout_seconds=300)
:
response = client.browsers.playwright.execute(
browser.session_id,
code=,
timeout_sec=,
)
response.success:
RuntimeError(response.error response.stderr )
(response.result)
:
client.browsers.delete_by_id(browser.session_id)
Pass the session ID as the first positional argument. The code receives page, context, and browser. Use return; otherwise response.result is empty. timeout_sec limits remote code execution, while the SDK's timeout= option controls the HTTP request.
Client-side Playwright over CDP
Use CDP when local Playwright tooling or interactive debugging is required:
Reuse the existing default context so pages see the Kernel session's loaded profile state; a new incognito context does not share that state. The SDK client context closes HTTP connections, but it does not replace deleting the remote browser.
Browser Lifecycle and Cleanup
Wrap every created or acquired session in try/finally immediately after creation.
Delete ordinary sessions with client.browsers.delete_by_id(session_id); use await with AsyncKernel.
Release pool-acquired sessions through the browser-pool release API instead of deleting them.
Keep the browser inactivity timeout finite even when cleanup exists. Activity can extend that timeout, so it is not a substitute for explicit cleanup.
Delete throwaway profiles and proxies only after all sessions using them have ended. Keep intentional persistent profiles.
Profiles
Create profiles before attaching them. Set save_changes=True when cookies and local state must persist back to the profile:
Deleting the browser ends the session and allows profile changes to finalize. Do not delete the profile first. Use either {"id": ...} or {"name": ...}, not both.
The Python SDK can request either archive format and rename profiles natively:
The SDK writes an archive for both tar.zst and tar; it never extracts the contents. Use kernel profiles download <id-or-name> --to ./profile when the archive must be unpacked into a directory.
Proxies and Default Stealth Proxy
Attach an explicit proxy at creation with proxy_id:
proxy = client.proxies.create(
type="datacenter",
config={"country": "US"},
name="automation-us",
)
try:
client.proxies.check(proxy.id, url="https://example.com")
browser = client.browsers.create(stealth=True, proxy_id=proxy.id)
try:
...
finally:
client.browsers.delete_by_id(browser.session_id)
finally:
client.proxies.delete(proxy.id) # Only if this proxy was created for this run.
A target-specific proxy check validates that public HTTP/HTTPS URL. For residential and mobile proxies, it does not guarantee the later browser uses the same exit node; with a custom URL, the check also does not update general proxy health status.
Proxy routing can be changed on a running browser:
Stealth browsers may use Kernel's default stealth proxy when no explicit proxy is attached. Control it independently:
client.browsers.update(browser.session_id, disable_default_proxy=True) # Direct connection
client.browsers.update(browser.session_id, disable_default_proxy=False) # Re-enable default
disable_default_proxy is an update parameter, not a browser-create parameter. The current SDK does not expose proxy rename; use kernel proxies update <id> --name <new-name>. Recreate the proxy to change anything besides its name.