Skip to main content
The fastest path is the finished Kernel example: clone it and run two commands. The sections after it explain how the example is built so you can adapt it. New to devboxes? Start with the Quickstart.
Give a Runloop agent browser access with Kernel: the agent runs in a devbox, the browser runs on Kernel, and the devbox drives it server-side with Playwright Execute, so no Chromium ever runs in the devbox. In this guide:

What you need

  • A Runloop API key
  • A Kernel API key
  • Python 3.12+ and pip, or Node.js 18+ and npm

Environment variables

export RUNLOOP_API_KEY=...
export KERNEL_API_KEY=...
Instead of exporting KERNEL_API_KEY, store it as a Runloop account secret and map it into the devbox at runtime. See Account Secrets.

Run the finished example

Clone the repo, install dependencies, then create the blueprint and run the browser task.
git clone https://github.com/runloopai/runloop-examples.git
cd runloop-examples/browser-integrations/kernel/python
pip install -r requirements.txt
python main.py create-blueprint
python main.py run
create-blueprint bakes the Kernel SDK into a reusable blueprint, and run creates a devbox from it, uploads the agent, and drives a Kernel browser. The rest of this guide walks through each of those pieces.

How the example is built

To build it yourself, install the Runloop SDK locally. The devbox installs the Kernel SDK itself (baked into the blueprint below), so nothing else is needed on your machine.
pip install runloop_api_client

Create a blueprint with the Kernel SDK

Bake the Kernel SDK into a blueprint once so every devbox starts ready, with no install step.
from runloop_api_client import AsyncRunloopSDK

runloop = AsyncRunloopSDK()

blueprint = await runloop.blueprint.create(
    name="kernel-browser",
    system_setup_commands=["python3 -m pip install --user kernel"],
)
From the example: python main.py create-blueprint or npm run create-blueprint. It is idempotent, so later runs reuse the built blueprint.

Create a devbox and run a browser task

Create a devbox from the blueprint with KERNEL_API_KEY injected, then have it create a Kernel browser and run a Playwright snippet against it. The snippet is JavaScript; page, context, and browser are in scope, and its return value comes back as result. Here the agent returns structured JSON (title, headings, and link count) rather than a single string, which is the first useful browser primitive. The TypeScript version below orchestrates Runloop from Node, but the in-devbox agent is still Python, so the blueprint only needs the Python Kernel SDK.
import os

agent = '''
import asyncio
from kernel import AsyncKernel

async def main():
    kernel = AsyncKernel()
    browser = await kernel.browsers.create(stealth=True)
    try:
        resp = await kernel.browsers.playwright.execute(
            browser.session_id,
            code="""
                await page.goto('https://docs.runloop.ai', { waitUntil: 'domcontentloaded' });
                return await page.evaluate(() => ({
                  title: document.title,
                  headings: document.querySelectorAll('h1, h2, h3').length,
                  links: document.querySelectorAll('a[href]').length,
                }));
            """,
            timeout_sec=60,
        )
        print(resp.result)
    finally:
        await kernel.browsers.delete_by_id(browser.session_id)

asyncio.run(main())
'''

devbox = await runloop.devbox.create_from_blueprint_name(
    "kernel-browser",
    environment_variables={"KERNEL_API_KEY": os.environ["KERNEL_API_KEY"]},
    launch_parameters={"resource_size_request": "SMALL"},
)
try:
    await devbox.file.write(file_path="/home/user/agent.py", contents=agent)
    result = await devbox.cmd.exec("python3 /home/user/agent.py")
    print(await result.stdout())
finally:
    await devbox.shutdown()
The Kernel SDK is the only dependency the devbox needs. There is no Chromium or Playwright install, because the browser runs on Kernel.
From the example: python main.py run or npm run run-kernel.

Research across multiple pages

To research several pages, reuse one Kernel session: it avoids a cold start per page and preserves cookies and history. Define a reusable scan function that navigates and returns clean JSON, then call it for each URL inside one try/finally. This code goes in the same in-devbox agent shown above.
from kernel import AsyncKernel

kernel = AsyncKernel()

async def scan(session_id, url):
    response = await kernel.browsers.playwright.execute(
        session_id,
        code=f"""
            await page.goto({url!r}, {{ waitUntil: "domcontentloaded", timeout: 30000 }});
            return await page.evaluate(() => {{
              const clean = (s) => (s || "").replace(/\\s+/g, " ").trim();
              return {{
                title: clean(document.title),
                headings: Array.from(document.querySelectorAll("h1, h2, h3"))
                  .map((e) => clean(e.textContent)).filter(Boolean).slice(0, 12),
                links: document.querySelectorAll("a[href]").length,
              }};
            }});
        """,
        timeout_sec=60,
    )
    if not response.success:
        raise RuntimeError(response.error)
    return response.result
Create one browser, loop over the URLs reusing the session, and release it in finally so the session is deleted even if a navigation raises.
browser = await kernel.browsers.create(stealth=True)
session_id = browser.session_id
try:
    for url in ["https://runloop.ai", "https://docs.runloop.ai"]:
        data = await scan(session_id, url)
        print(data["title"], len(data["headings"]), "headings")
finally:
    await kernel.browsers.delete_by_id(session_id)

Computer use controls

Some agents reason over pixels rather than the DOM, such as Claude or OpenAI computer-use models. Kernel’s Computer Controls API exposes screenshot, click, type, and scroll against the managed browser. Because Computer Controls has no navigate action, load the page once with Playwright Execute, then switch to the pixel API. These calls go inside the same in-devbox agent shown above (Python by default; the TypeScript equivalent is shown for reference). Wrapping the work in try/finally deletes the Kernel session even if a call fails.
from kernel import AsyncKernel

kernel = AsyncKernel()

browser = await kernel.browsers.create(stealth=True)
session_id = browser.session_id
try:
    await kernel.browsers.playwright.execute(
        session_id,
        code='await page.goto("https://example.com", { waitUntil: "domcontentloaded" });',
        timeout_sec=60,
    )

    # capture_screenshot returns raw PNG bytes; a vision model reads the image and
    # returns the next action. The coordinates below would come from the model.
    shot = await kernel.browsers.computer.capture_screenshot(session_id)
    with open("screen.png", "wb") as f:
        f.write(await shot.read())

    await kernel.browsers.computer.click_mouse(session_id, x=400, y=300)
    await kernel.browsers.computer.type_text(session_id, text="kernel + runloop")
    await kernel.browsers.computer.scroll(session_id, x=400, y=300, delta_y=300)
finally:
    await kernel.browsers.delete_by_id(session_id)

How the integration works internally

  1. A devbox boots from a blueprint with the Kernel SDK already installed.
  2. KERNEL_API_KEY is injected into the devbox environment.
  3. The agent calls browsers.create() to get a Kernel cloud browser session.
  4. It drives the browser with browsers.playwright.execute(), which runs the Playwright code co-located with the browser on Kernel and returns structured data. No CDP connection, no local browser.
  5. Results return to the devbox; the orchestrator reads them and shuts the devbox down.

Common issues

  • resource_size_request rejected
    • The enum is upper-case (SMALL, MEDIUM, LARGE, …). Lower-case values return a 400.
  • Kernel auth errors inside the devbox
    • Confirm KERNEL_API_KEY was passed via environment_variables when creating the devbox.
  • playwright.execute code fails to parse
    • The code is JavaScript, not Python. page, context, and browser are in scope.
  • No live view URL
    • browser_live_view_url is null for headless browsers. Kernel browsers are headful by default.

Next Steps