> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Kernel on Runloop

> Run browser agents from Runloop devboxes with Kernel-managed browsers, without installing Chromium or opening CDP from the devbox.

<Tip>
  The fastest path is the finished [Kernel example](https://github.com/runloopai/runloop-examples/tree/main/browser-integrations/kernel): 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](/docs/tutorials/quickstart).
</Tip>

Give a Runloop agent browser access with [Kernel](https://www.kernel.sh): 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:

* [Run the finished example](#run-the-finished-example): clone and run with two commands
* [Research across multiple pages](#research-across-multiple-pages): reuse one Kernel session to scan multiple pages
* [Computer use controls](#computer-use-controls): screenshot, click, and type for vision-model agents

## What you need

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

## Environment variables

```bash theme={null}
export RUNLOOP_API_KEY=...
export KERNEL_API_KEY=...
```

<Note>
  Instead of exporting `KERNEL_API_KEY`, store it as a Runloop account secret and map it into the devbox at runtime. See [Account Secrets](/docs/devboxes/configuration/account-secrets).
</Note>

## Run the finished example

Clone the repo, install dependencies, then create the blueprint and run the browser task.

<CodeGroup>
  ```bash Python theme={null}
  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
  ```

  ```bash TypeScript theme={null}
  git clone https://github.com/runloopai/runloop-examples.git
  cd runloop-examples/browser-integrations/kernel/typescript
  npm install
  npm run create-blueprint
  npm run run-kernel
  ```
</CodeGroup>

`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.

<CodeGroup>
  ```bash Python theme={null}
  pip install runloop_api_client
  ```

  ```bash TypeScript theme={null}
  npm install @runloop/api-client
  ```
</CodeGroup>

### Create a blueprint with the Kernel SDK

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

<CodeGroup>
  ```python Python theme={null}
  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"],
  )
  ```

  ```typescript TypeScript theme={null}
  import { RunloopSDK } from '@runloop/api-client';

  const runloop = new RunloopSDK();

  const blueprint = await runloop.blueprint.create({
    name: 'kernel-browser',
    system_setup_commands: ['python3 -m pip install --user kernel'],
  });
  ```
</CodeGroup>

<Tip>
  From the example: `python main.py create-blueprint` or `npm run create-blueprint`. It is idempotent, so later runs reuse the built blueprint.
</Tip>

### 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.

<CodeGroup>
  ```python Python theme={null}
  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()
  ```

  ```typescript TypeScript theme={null}
  const 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())
  `;

  const devbox = await runloop.devbox.createFromBlueprintName('kernel-browser', {
    environment_variables: { KERNEL_API_KEY: process.env.KERNEL_API_KEY! },
    launch_parameters: { resource_size_request: 'SMALL' },
  });

  try {
    await devbox.file.write({ file_path: '/home/user/agent.py', contents: agent });
    const result = await devbox.cmd.exec('python3 /home/user/agent.py');
    console.log(await result.stdout());
  } finally {
    await devbox.shutdown();
  }
  ```
</CodeGroup>

<Note>
  The Kernel SDK is the only dependency the devbox needs. There is no Chromium or Playwright install, because the browser runs on Kernel.
</Note>

<Tip>
  From the example: `python main.py run` or `npm run run-kernel`.
</Tip>

## 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.

<CodeGroup>
  ```python Python theme={null}
  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
  ```

  ```typescript TypeScript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  async function scan(sessionId: string, url: string) {
    const response = await kernel.browsers.playwright.execute(sessionId, {
      code: `
        await page.goto(${JSON.stringify(url)}, { 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,
          };
        });
      `,
    });
    if (!response.success) throw new Error(response.error ?? "execute failed");
    return response.result;
  }
  ```
</CodeGroup>

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.

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  const browser = await kernel.browsers.create({ stealth: true });
  const sessionId = browser.session_id;
  try {
    for (const url of ["https://runloop.ai", "https://docs.runloop.ai"]) {
      const data = await scan(sessionId, url);
      console.log(data.title, data.headings.length, "headings");
    }
  } finally {
    await kernel.browsers.deleteByID(sessionId);
  }
  ```
</CodeGroup>

## 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.

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

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

    // captureScreenshot returns a binary response; persist or forward the PNG bytes.
    const shot = await kernel.browsers.computer.captureScreenshot(sessionId);

    await kernel.browsers.computer.clickMouse(sessionId, { x: 400, y: 300 });
    await kernel.browsers.computer.typeText(sessionId, { text: 'kernel + runloop' });
    await kernel.browsers.computer.scroll(sessionId, { x: 400, y: 300, delta_y: 300 });
  } finally {
    await kernel.browsers.deleteByID(sessionId);
  }
  ```
</CodeGroup>

## 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

* Full runnable source: the [Kernel example](https://github.com/runloopai/runloop-examples/tree/main/browser-integrations/kernel)
* Review [Devbox overview](/docs/devboxes/overview) and [Blueprints](/docs/devboxes/blueprints/overview)
* Read Kernel's [Playwright Execution](https://www.kernel.sh/docs/browsers/playwright-execution) guide
