kernel-python-sdk — Claude Code skill: Python browser automation
Teaches the agent to write Python against Kernel browser sessions: server-side Playwright, CDP, profiles, proxies and clean teardown.
npx skills add kernel/skills --skill kernel-python-sdk -a claude-codeWhat kernel-python-sdk does
This skill covers creating and driving Kernel browser sessions from Python. There are two paths. Server-side Playwright runs next to Chrome, so no local Playwright install is needed. Client-side Playwright over CDP is the one to pick when you want local tooling or interactive debugging. Remote code receives page, context and browser, and you must use return or response.result comes back empty. Note the split: timeout_sec caps remote execution, while the SDK's timeout= governs the HTTP request.
Setup is short. Put KERNEL_API_KEY in the environment rather than in source; Kernel() and AsyncKernel() pick it up automatically. Install with uv pip install -U kernel, and add uv pip install playwright only for the CDP path. Stick to one client style — calling synchronous SDK methods from an async action will break.
Lifecycle gets its own section, and it matters. Wrap every session in try/finally right after creation and delete it with client.browsers.delete_by_id(session_id); pool-acquired sessions go back through the pool release API instead. Profiles are created before they are attached, and save_changes=True is what persists cookies and local state. Delete the browser before the profile, otherwise the changes never finalize. Proxies attach at creation via proxy_id and can be swapped on a running browser. One detail worth remembering: disable_default_proxy is an update parameter, not a create parameter.
What it helps with
- run Playwright server-side without installing it locally
- attach to a Kernel browser over CDP for debugging
- persist cookies and login state across runs
- route traffic through a specific proxy and verify it
- avoid orphaned sessions when a script crashes
Example prompts for the agent
- Write a Python script that creates a Kernel browser and runs server-side Playwright to open a page and return its title
- Connect to the Kernel session over CDP and reuse the default context so pages see the profile state
- Wrap session creation in try/finally and delete it with delete_by_id at the end
How to install kernel-python-sdk
1Skills CLI (Vercel)
npx skills add kernel/skills --skill kernel-python-sdk -a claude-code2Manually: put the files in place
~/.claude/skills/kernel-python-sdk/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/kernel-python-sdk/
1Skills CLI (Vercel)
npx skills add kernel/skills --skill kernel-python-sdk -a codex2Manually: put the files in place
.agents/skills/kernel-python-sdk/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add kernel/skills --skill kernel-python-sdk -a cursor2Manually: put the files in place
.cursor/skills/kernel-python-sdk/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install kernel/skills kernel-python-sdk2Manually: put the files in place
.github/skills/kernel-python-sdk/SKILL.md1Skills CLI (Vercel)
npx skills add kernel/skills --skill kernel-python-sdk -a gemini-cli2Platform CLI
gemini skills install https://github.com/kernel/skills --path plugins/kernel-sdks/skills/kernel-python-sdkManually: put the files in place
.devin/skills/kernel-python-sdk/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add kernel/skills --skill kernel-python-sdk -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about kernel-python-sdk
How do I install kernel-python-sdk in Claude?
Run npx skills add kernel/skills --skill kernel-python-sdk -a claude-code. The skill lands at ~/.claude/skills/kernel-python-sdk/SKILL.md.
What does kernel-python-sdk do?
It helps the agent write and debug Python for Kernel browser sessions: server-side Playwright, client-side CDP, profiles, proxies and cleanup.
What do I need before using the skill?
A KERNEL_API_KEY in the environment and the kernel package via uv pip install -U kernel. Local playwright is only needed for the CDP path.
Source text
SKILL.md · MIT
---
name: kernel-python-sdk
description: 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
```bash
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:
```python
from kernel import Kernel
with Kernel() as client:
browser = client.browsers.create(stealth=True, timeout_seconds=300)
try:
response = client.browsers.playwright.execute(
browser.session_id,
code="""
await page.goto("https://example.com");
return { title: await page.title(), url: page.url() };
""",
timeout_sec=60,
)
if not response.success:
raise RuntimeError(response.error or response.stderr or "Playwright execution failed")
print(response.result)
finally:
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:
```python
import asyncio
from kernel import AsyncKernel
from playwright.async_api import async_playwright
async def main() -> None:
async with AsyncKernel() as client:
kernel_browser = await client.browsers.create(
stealth=True,
timeout_seconds=300,
)
try:
async with async_playwright() as playwright:
remote = await playwright.chromium.connect_over_cdp(
kernel_browser.cdp_ws_url
)
context = remote.contexts[0]
page = context.pages[0] if context.pages else await context.new_page()
await page.goto("https://example.com")
print(await page.title())
finally:
await client.browsers.delete_by_id(kernel_browser.session_id)
asyncio.run(main())
```
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:
```python
profile = client.profiles.create(name="checkout-login")
browser = client.browsers.create(
profile={"id": profile.id, "save_changes": True},
stealth=True,
)
try:
# Automate with server-side Playwright or CDP.
...
finally:
client.browsers.delete_by_id(browser.session_id)
```
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:
```python
archive = client.profiles.download(profile.id, format="tar")
archive.write_to_file("profile.tar")
client.profiles.update(profile.id, name="checkout-login-v2")
```
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`:
```python
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:
```python
client.browsers.update(browser.session_id, proxy_id=another_proxy_id)
client.browsers.update(browser.session_id, proxy_id="") # Remove explicit proxy.
```
Stealth browsers may use Kernel's default stealth proxy when no explicit proxy is attached. Control it independently:
```python
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.
## Deployed Action Pattern
```python
from typing import TypedDict
import kernel
app = kernel.App("app-name")
class TaskInput(TypedDict):
task: str
@app.action("action-name")
async def run(ctx: kernel.KernelContext, payload: TaskInput):
async with kernel.AsyncKernel() as client:
browser = await client.browsers.create(
invocation_id=ctx.invocation_id,
timeout_seconds=300,
)
try:
response = await client.browsers.playwright.execute(
browser.session_id,
code="return await page.title();",
)
if not response.success:
raise RuntimeError(response.error or response.stderr or "Playwright execution failed")
return response.result
finally:
await client.browsers.delete_by_id(browser.session_id)
```
## Binary Results
Server-side screenshots and PDFs can return a Node.js Buffer-shaped result:
```python
response = client.browsers.playwright.execute(
browser.session_id,
code="return await page.screenshot({ fullPage: true });",
)
if response.success and response.result:
with open("output.png", "wb") as file:
file.write(bytes(response.result["data"]))
```
## References
- [Kernel documentation](https://www.kernel.sh/docs)
- [Python SDK API reference](https://github.com/kernel/kernel-python-sdk/blob/main/api.md)
- [Kernel quickstart](https://www.kernel.sh/docs/quickstart)
- [Additional examples](./examples/examples.md)
© kernel · MIT · kernel/skills