diff --git a/browsers/file-io.mdx b/browsers/file-io.mdx
index 9fbcd26..cb5fbab 100644
--- a/browsers/file-io.mdx
+++ b/browsers/file-io.mdx
@@ -246,25 +246,131 @@ if __name__ == "__main__":
-### Stagehand v3
-
-When using Stagehand with Kernel browsers, you need to configure the download behavior in the `localBrowserLaunchOptions`:
-
-```typescript
-const stagehand = new Stagehand({
- env: "LOCAL",
- verbose: 1,
- localBrowserLaunchOptions: {
- cdpUrl: kernelBrowser.cdp_ws_url,
- downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
- acceptDownloads: true, // Enable downloads
- },
+### Stagehand
+
+When using Stagehand with Kernel browsers, downloads are saved to the browser's filesystem and retrieved with Kernel's File I/O APIs. The setup differs by version (see the [Stagehand integration guide](/integrations/stagehand) for the full connection setup):
+
+- **v4** connects to the running browser, and a user-initiated download — e.g. clicking a download link — is saved to the browser's default download directory, `/home/kernel/Downloads`. No download-specific configuration is required.
+- **v3** sets the download directory via `localBrowserLaunchOptions` (`downloadsPath` + `acceptDownloads`).
+
+Here's a complete example for each version:
+
+
+```typescript Stagehand v4
+import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
+import Kernel from "@onkernel/sdk";
+import fs from "fs";
+import { createReadStream } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+// Kernel browsers save user-initiated downloads here by default.
+const DOWNLOAD_DIR = "/home/kernel/Downloads";
+
+// Mirror the Stagehand extension onto the running browser so localBrowser.connect can load it.
+const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
+async function loadStagehandExtension(kernel: Kernel, sessionId: string) {
+ await kernel.browsers.fs.uploadZip(sessionId, {
+ dest_path: join(stagehandDist, "extension"),
+ zip_file: createReadStream(join(stagehandDist, "assets/stagehand-extension.zip")),
+ });
+}
+
+// Poll listFiles until a completed file appears (skip in-progress .crdownload files).
+async function waitForFile(
+ kernel: Kernel,
+ sessionId: string,
+ dir: string,
+ timeoutMs = 30_000
+) {
+ const start = Date.now();
+ while (Date.now() - start < timeoutMs) {
+ const files = await kernel.browsers.fs.listFiles(sessionId, { path: dir });
+ const done = files.find((f) => !f.name.endsWith(".crdownload"));
+ if (done) {
+ return done;
+ }
+ await new Promise((r) => setTimeout(r, 500));
+ }
+ throw new Error(`No files found in ${dir} after ${timeoutMs}ms`);
+}
+
+async function main() {
+ const kernel = new Kernel();
+
+ console.log("Creating browser via Kernel...");
+ const kernelBrowser = await kernel.browsers.create({ stealth: true });
+
+ console.log(`Kernel Browser Session Started`);
+ console.log(`Session ID: ${kernelBrowser.session_id}`);
+ console.log(`Watch live: ${kernelBrowser.browser_live_view_url}`);
+
+ let stagehand: Awaited> | undefined;
+ let browser: Awaited> | undefined;
+ try {
+ await loadStagehandExtension(kernel, kernelBrowser.session_id);
+ browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });
+ stagehand = await Stagehand.create({
+ browser,
+ model: {
+ modelName: "anthropic/claude-sonnet-4-5",
+ apiKey: process.env.MODEL_API_KEY,
+ },
+ });
+
+ const page = await browser.context.activePage();
+ if (!page) throw new Error("No active page in the Kernel browser");
+ await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
+
+ // Use Stagehand to click the download button
+ await stagehand.act("Click the download file link");
+ console.log("Download triggered");
+
+ // Wait for the file to be fully available via Kernel's File I/O APIs
+ console.log("Waiting for file to appear...");
+ const downloadedFile = await waitForFile(
+ kernel,
+ kernelBrowser.session_id,
+ DOWNLOAD_DIR
+ );
+ console.log(`File found: ${downloadedFile.name}`);
+
+ const remotePath = `${DOWNLOAD_DIR}/${downloadedFile.name}`;
+ console.log(`Reading file from: ${remotePath}`);
+
+ // Read the file from the Kernel browser's filesystem
+ const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
+ path: remotePath,
+ });
+
+ // Save to local filesystem
+ const bytes = await resp.bytes();
+ fs.mkdirSync("downloads", { recursive: true });
+ const localPath = `downloads/${downloadedFile.name}`;
+ fs.writeFileSync(localPath, bytes);
+ console.log(`Saved to ${localPath}`);
+ } finally {
+ // Nested so a rejected close() never skips deleting the Kernel browser.
+ try {
+ await stagehand?.close();
+ } finally {
+ try {
+ await browser?.close();
+ } finally {
+ await kernel.browsers.deleteByID(kernelBrowser.session_id);
+ }
+ }
+ }
+ console.log("Browser session closed");
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
});
```
-Here's a complete example:
-
-```typescript
+```typescript Stagehand v3
import { Stagehand } from "@browserbasehq/stagehand";
import Kernel from "@onkernel/sdk";
import fs from "fs";
@@ -307,8 +413,8 @@ async function main() {
verbose: 1,
localBrowserLaunchOptions: {
cdpUrl: kernelBrowser.cdp_ws_url,
- downloadsPath: DOWNLOAD_DIR,
- acceptDownloads: true,
+ downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
+ acceptDownloads: true, // Enable downloads
},
});
@@ -357,6 +463,7 @@ main().catch((err) => {
process.exit(1);
});
```
+
### Browser Use
diff --git a/integrations/stagehand.mdx b/integrations/stagehand.mdx
index d633f02..8789124 100644
--- a/integrations/stagehand.mdx
+++ b/integrations/stagehand.mdx
@@ -5,26 +5,156 @@ title: "Stagehand"
[Stagehand](https://github.com/browserbase/stagehand) is an open source AI browser automation framework. It lets developers choose what to write in code vs. natural language. By integrating with Kernel, you can run Stagehand automations with cloud-hosted browsers.
-This guide is compatible with Stagehand SDK v3. If you're using an earlier version, please refer to the [Stagehand migration guide](https://docs.stagehand.dev/v3/migrations/v2) or upgrade to v3.
+This guide covers both Stagehand SDK v4 and v3. v4 runs as a Chrome extension alongside the browser rather than driving it purely over CDP, so a remote Kernel browser needs the extension loaded into it — the version tabs below show each setup. To move between versions, see the [Stagehand migration guide](https://docs.stagehand.dev). The CLI template uses v4.
-## Adding Kernel to existing Stagehand implementations
+## Quick start with the Stagehand template
-If you already have a Stagehand (v3) implementation, you can easily switch to using Kernel's cloud browsers by updating your browser configuration.
+The fastest way to run Stagehand on Kernel is our app template, which comes pre-wired for v4:
-### 1. Install the Kernel SDK
+```bash
+kernel create --name my-stagehand-app --language typescript --template stagehand
+```
+
+This scaffolds a self-contained app with two files:
+
+- `index.ts` — the automation (searches a startup on Y Combinator and extracts its team size).
+- `stagehand-extension.ts` — a helper that loads the Stagehand extension onto the Kernel browser.
+
+Set a provider-prefixed `MODEL` and its API key in a `.env` file:
+
+```bash .env
+# MODEL is provider-prefixed, e.g. anthropic/claude-sonnet-4-5, openai/gpt-4.1, google/gemini-2.5-flash
+MODEL=anthropic/claude-sonnet-4-5
+MODEL_API_KEY=your-api-key
+```
+
+Then deploy and invoke:
+
+```bash
+kernel deploy index.ts --env-file .env
+kernel invoke ts-stagehand teamsize-task --payload '{"company": "kernel"}'
+# → {"teamSize":"6"}
+```
+
+See the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides for more.
+
+## Adding Kernel to an existing Stagehand project
+
+If you already have a Stagehand implementation, switch it to Kernel's cloud browsers by updating your browser setup. The steps differ between v4 and v3 — pick your version below.
+
+
+
+
+**1. Install the Kernel SDK**
+
+```bash
+npm install @onkernel/sdk
+```
+
+**2. Load the Stagehand extension onto the Kernel browser**
+
+Stagehand v4 runs as a Chrome extension. When `localBrowser.connect` is called without an `extensionId`, Stagehand loads the extension into the running browser over CDP (`Extensions.loadUnpacked`), reading it from a path on the **browser's** filesystem. Mirror the extension — shipped inside the `@browserbasehq/stagehand` package — onto the running Kernel browser at that exact path first:
+
+```typescript
+import { Kernel } from "@onkernel/sdk";
+import { createReadStream } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const stagehandDist = dirname(fileURLToPath(import.meta.resolve("@browserbasehq/stagehand")));
+const STAGEHAND_EXTENSION_ZIP = join(stagehandDist, "assets/stagehand-extension.zip");
+const STAGEHAND_EXTENSION_DIR = join(stagehandDist, "extension");
+
+async function loadStagehandExtension(kernel: Kernel, sessionId: string): Promise {
+ await kernel.browsers.fs.uploadZip(sessionId, {
+ dest_path: STAGEHAND_EXTENSION_DIR,
+ zip_file: createReadStream(STAGEHAND_EXTENSION_ZIP),
+ });
+}
+```
+
+**3. Create a browser and connect**
+
+Create a Kernel browser, load the extension, then connect Stagehand to its CDP URL:
+
+```typescript
+import { Stagehand, localBrowser } from "@browserbasehq/stagehand";
+import Kernel from "@onkernel/sdk";
+
+const kernel = new Kernel();
+
+const kernelBrowser = await kernel.browsers.create({ stealth: true });
+console.log("Live view url:", kernelBrowser.browser_live_view_url);
+
+await loadStagehandExtension(kernel, kernelBrowser.session_id);
+
+// With no `extensionId`, Stagehand loads the extension over CDP.
+const browser = await localBrowser.connect({ cdpUrl: kernelBrowser.cdp_ws_url });
+
+const stagehand = await Stagehand.create({
+ browser,
+ model: {
+ modelName: "anthropic/claude-sonnet-4-5",
+ apiKey: process.env.MODEL_API_KEY,
+ },
+});
+```
+
+**4. Use your Stagehand automation**
+
+Drive the page with Stagehand's primitives. Note the v4 API: page access is async (`activePage()`), and `extract` returns its result under `data`:
+
+```typescript
+import { z } from "zod";
+
+const page = await browser.context.activePage();
+if (!page) throw new Error("No active page in the Kernel browser");
+await page.goto("https://www.ycombinator.com/companies");
+
+await stagehand.act("Type in kernel into the search box");
+await stagehand.act("Click on the first search result");
+
+const { data } = await stagehand.extract(
+ "Extract the team size (number of employees) shown on this Y Combinator company page.",
+ z.object({ teamSize: z.string() }),
+);
+
+console.log("Team size:", data.teamSize);
+```
+
+**5. Clean up**
+
+Stagehand v4 only closes browsers it launched, so close the connection and delete the Kernel browser yourself. Nest the cleanup so a failed `close()` never skips deleting the browser:
+
+```typescript
+try {
+ await stagehand.close();
+} finally {
+ try {
+ await browser.close();
+ } finally {
+ await kernel.browsers.deleteByID(kernelBrowser.session_id);
+ }
+}
+```
+
+
+
+
+**1. Install the Kernel SDK**
```bash
npm install @onkernel/sdk
```
-### 2. Initialize Kernel and create a browser
+**2. Initialize Kernel and create a browser**
Import the libraries and create a cloud browser session:
```typescript
import { Stagehand } from "@browserbasehq/stagehand";
-import Kernel from '@onkernel/sdk';
+import Kernel from "@onkernel/sdk";
import { z } from "zod";
const kernel = new Kernel();
@@ -34,7 +164,7 @@ const kernelBrowser = await kernel.browsers.create({ stealth: true });
console.log("Live view url: ", kernelBrowser.browser_live_view_url);
```
-### 3. Update your browser configuration
+**3. Update your browser configuration**
Replace your existing browser setup to use Kernel's CDP URL:
@@ -53,7 +183,7 @@ const stagehand = new Stagehand({
await stagehand.init();
```
-### 4. Use your Stagehand automation
+**4. Use your Stagehand automation**
Use Stagehand's page methods with the Kernel-powered browser:
@@ -74,15 +204,8 @@ await stagehand.close();
await kernel.browsers.deleteByID(kernelBrowser.session_id);
```
-## Quick setup with our Stagehand example app
-
-Alternatively, you can use our Kernel app template that includes a pre-configured Stagehand integration:
-
-```bash
-kernel create --name my-stagehand-app --language typescript --template stagehand
-```
-
-Then follow the [deploy](/apps/deploy) and [invoke](/apps/invoke) guides to deploy and run your Stagehand automation on Kernel's infrastructure.
+
+
## Benefits of using Kernel with Stagehand
diff --git a/reference/cli/create.mdx b/reference/cli/create.mdx
index 32ffd15..bde2127 100644
--- a/reference/cli/create.mdx
+++ b/reference/cli/create.mdx
@@ -22,7 +22,7 @@ Create a new Kernel application from a template. The CLI provides an interactive
- **`openai-computer-use`** — OpenAI Computer Using Agent (CUA)
- **`gemini-computer-use`** — Google Gemini computer use agent
- **`claude-agent-sdk`** — Claude Agent SDK browser automation agent
-- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v3 SDK integration
+- **`stagehand`** — [Stagehand](https://github.com/browserbase/stagehand) v4 SDK integration
- **`magnitude`** — [Magnitude](https://github.com/magnitude-labs/magnitude) SDK integration
- **`tzafon`** — Tzafon Northstar CUA Fast computer use agent
- **`yutori`** — Yutori n1.5 computer use agent