# Build Kernel TS Sdk

> Use if building browser-automation apps on the Kernel TS SDK (@onkernel/sdk) — browsers, pools.

- Skill: `yigitkonur/build-kernel-ts-sdk` (Agent Skill, multi-file: 20 files)
- Install (CLI): `npx skillmds@latest add yigitkonur/build-kernel-ts-sdk`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yigitkonur/build-kernel-ts-sdk/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yigitkonur (https://skillmd.com/u/yigitkonur)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yigitkonur/build-kernel-ts-sdk

---


# Build Kernel TS SDK

Build with the Kernel TypeScript SDK (`@onkernel/sdk`, generated from Kernel's OpenAPI spec by Stainless) and the React helper `@onkernel/managed-auth-react`. Kernel runs each browser as a unikernel-isolated VM and co-locates your code with the browser to remove CDP latency. The SDK and CLI surface the **same** API.

## When to use this skill

Use this skill if the task involves any of:

- _building or extending TypeScript code that imports `@onkernel/sdk` or constructs `new Kernel(...)`_
- _driving a Kernel browser via `kernel.browsers.create`, `cdp_ws_url`, `kernel.browsers.playwright.execute`, or `kernel.browsers.computer.*`_
- _deploying a Kernel App with `kernel deploy` and invoking it via `kernel.invocations.create` (sync or async with `invocations.follow`)_
- _wiring Playwright, Stagehand, Browser Use, Claude Agent SDK, Vibium, Notte, Magnitude, Laminar, or Val Town to a Kernel browser_
- _using profiles (`profiles.*`), browser pools (`browserPools.*`), credentials (`credentials.*`), or replays/file I/O (`browsers.fs.*`, `browsers.replays.*`)_
- _implementing Managed Auth with `auth.connections.*` and the React `<KernelManagedAuth />` component_
- _scoping `KERNEL_API_KEY` per project via the `projectID` client option_
- _debugging Kernel-specific failures: `browser.close()` not cleaning up, sync-invocation 100 s timeout, default-context confusion, 409 profile conflicts_

Do **NOT** use this skill for:

- _Terminal-driving the `agent-browser` CLI (`agent-browser -p kernel`, `@ref` snapshots, `snapshot -i --json`) — use `run-agent-browser`. This skill owns Kernel-SDK code; `run-agent-browser` owns the CLI._
- _Python Kernel SDK (`kernel-python-sdk`), or Browser Use's Python framework — no native TS package._
- _LangChain.js / LangGraph agents that may incidentally call browser tools but are not Kernel-specific (`build-langchain-ts-app`)._

## Cross-skill disambiguation

| Situation | Use |
|---|---|
| TypeScript code importing `@onkernel/sdk` or deploying a Kernel App | `build-kernel-ts-sdk` |
| `agent-browser` CLI loops, including `agent-browser -p kernel` | `run-agent-browser` |
| LangChain.js/LangGraph agent where browser tools are optional | `build-langchain-ts-app` |

## Two operating modes — decide first

| Mode | When | Code lives | Invocation |
|---|---|---|---|
| **A. Embed** | Drive Kernel from your own service (Next.js route, worker, CLI tool) | Your repo | `new Kernel()` → `browsers.create` → CDP / `playwright.execute` / `computer.*` |
| **B. Deploy** | Long-running, browser-co-located actions; want zero CDP latency or per-invocation isolation | A Kernel App (your repo, deployed via `kernel deploy`) | Register actions → `kernel deploy` → `kernel.invocations.create({ app_name, action_name, version, payload })` |

Mixing is fine — most production setups deploy long-running browser work as a Kernel App and invoke it from an embedding service. Don't try to make a single function do both.

### Deploy vs invoke glossary

- **App:** named deployed codebase containing one or more actions.
- **Action:** named function registered inside an app.
- **Deployment:** build/version event that creates or updates an app version; track `deployment.id`, app name, and version.
- **Invocation:** one execution of one action; `invocations.create` requires `app_name`, `action_name`, **and `version`** (all three are non-optional). Track `invocation.id`, sync/async mode, status, logs/events, and output handling.

## Hard rules — load-bearing

1. **`KERNEL_API_KEY` from env.** Never hardcode. Env wins over `apiKey:` option only when the option is omitted; passing both is allowed.
2. **Pin the SDK.** `@onkernel/sdk` is auto-generated by Stainless and rev's frequently. Pin a minor version range; verify method names against the installed type declarations — `node_modules/@onkernel/sdk/client.d.ts` for the top-level resource list, `node_modules/@onkernel/sdk/resources/**/*.d.ts` for method signatures and params. The npm package ships no `api.md`.
3. **Never use `browser.close()` as cleanup.** Playwright/Puppeteer `close()` only severs the local CDP connection. Always call `kernel.browsers.deleteByID(session_id)` (or rely on `timeout_seconds`).
4. **Sync invocation cap is ~100 s.** Anything longer must use `async: true` with `async_timeout_seconds` (10–3600) and `invocations.follow(id)` for SSE. Switching after the fact requires re-deploying.
5. **Default browser context only.** Kernel browsers ship with one default context and one open page. Use `browser.contexts()[0]` and `pages()[0]` — do not call `browser.newContext()` / `context.newPage()` to make a "fresh" one.
6. **Project scoping is a client option.** With an org-wide API key, scope to a project by passing `new Kernel({ projectID: '…' })` — the SDK stamps `X-Kernel-Project-Id` on every request and `withOptions()` carries it. A companion `project` option sets `X-Kernel-Project`. Both default to `null` and the SDK reads no project env var, so pass it explicitly (e.g. `projectID: process.env.KERNEL_PROJECT`). Hand-wiring `defaultHeaders` still works but is the escape hatch, not the idiom. OAuth (CLI) is always org-wide.
7. **Runtime requirements.** TypeScript ≥ 4.9. Supported runtimes: up-to-date browsers, Node 20 LTS+, Deno 1.28+, Bun 1.0+, Cloudflare Workers, Vercel Edge Runtime, Jest 28+ (`"node"` env), Nitro v2.6+. React Native is unsupported.
8. **Payload limits are doc-conflicted.** App development and CLI docs say 64 KB; app invocation docs say 4.5 MB. Verify live docs before relying on large payloads; route multi-MB artifacts through `browsers.fs.*` or object storage.

## Default stance

- `stealth: true` for any non-trivial site — bot detection is the rule, not the exception.
- `timeout_seconds: 300` as *your* floor for real automation — the 60 s default reaps too aggressively. The API range is `10`–`259200` (72 h) and inactivity is polled every 5 s, so short-lived headless scrapes may legitimately go lower.
- **Headful** when you need live view, replays, or GPU. **Headless** for fast scripted scrapes (~8× cheaper, faster boot, but more detectable).
- Prefer `kernel.browsers.playwright.execute(id, { code })` for hot paths (runs in the browser VM with no CDP roundtrip). Reserve raw CDP for long-lived interactive sessions.
- Use Kernel **profiles** for any flow that needs login state across sessions; create named profiles explicitly. Reach for **Managed Auth** when those credentials belong to your end-users.
- Only one parallel browser should write the same profile with `save_changes: true`; other parallel browsers should load it read-only.
- A browser is "active" while a CDP client, a WebDriver/BiDi client, or a live-view client is connected, or a `computer.*` request is in flight. After 5 s with none of those it enters **standby** (zero compute cost) and only THEN does its `timeout_seconds` countdown to deletion start. **GPU browsers do not support standby** — they bill for their entire lifetime at ~48× the headless rate, so keep `timeout_seconds` tight and delete explicitly.

## Quick start

For a new scratch project, use the scaffold script so package pins come from npm at generation time:

```bash
bash scripts/scaffold-kernel-app.sh --mode embed --dir ./kernel-embed-demo
cd ./kernel-embed-demo
npm install
export KERNEL_API_KEY=...   # never commit; use .env.example only as template
npm run check
npm run start
```

For an existing repo, install explicitly and keep a pinned range:

```bash
npm install @onkernel/sdk@^$(npm view @onkernel/sdk version) playwright
npm install -D tsx typescript @types/node
```

First browser creation must print the `session_id`, do the work, then call `kernel.browsers.deleteByID(session_id)` in `finally`. If a browser, pool lease, auth session, deployment, or invocation is intentionally left alive, report the ID, timeout, and reason.

## Current-doc/version check

- Existing repo: run `scripts/check-kernel-sdk-version.sh` before changing Kernel code. Read `scripts/check-kernel-sdk-version.sh.md` for output interpretation.
- New repo: run `npm view @onkernel/sdk version dist-tags --json` before pinning; prefer a minor range for scaffolds, not `latest`.
- Installed SDK: the npm package does **not** ship `api.md`. Read the shipped declarations — `node_modules/@onkernel/sdk/client.d.ts` for the resource list, `node_modules/@onkernel/sdk/resources/**/*.d.ts` for methods and params (e.g. `grep -nE '^    [a-zA-Z]+\(' node_modules/@onkernel/sdk/resources/browsers/browsers.d.ts`). Stainless regenerates frequently and method names move. The generated `api.md` index lives only in the source repo (https://github.com/kernel/kernel-node-sdk/blob/main/api.md), tracks `main` rather than your pin, and omits lib-only helpers such as `browsers.fetch` — the local `.d.ts` files win on any disagreement.
- Live docs: for pricing, billing, Managed Auth, profiles, browser pools, deployment/invocation, or payload-size claims, check `https://www.kernel.sh/docs/llms.txt` and the linked page before changing code.

## Cost-facing preflight

Before running code, name every operation that may create paid or quota-bound resources:

- `browsers.create`, especially headful, GPU, high-resolution viewport, long `timeout_seconds`, proxy, extension, or profile-backed sessions
- browser pool `create`, `acquire`, and unreleased acquired browsers
- Kernel App `deployments.create`, `kernel deploy`, and `invocations.create`
- Managed Auth connections/login sessions and credential providers
- proxies and file/replay artifacts that require a live browser to read back

For each resource, decide the cleanup path before running: `deleteByID`, pool `release`, invocation/browser cleanup by `invocation_id`, deployment terminal state, or explicit timeout with reason. Report anything left alive.

Read the real caps instead of guessing at plan tiers: `await kernel.organization.limits.retrieve()` returns `max_concurrent_sessions`, `default_project_max_concurrent_sessions`, `max_auth_connections` vs `auth_connections_used`, and `min_health_check_interval_seconds`. Per-project overrides live at `kernel.projects.limits.retrieve(idOrName)`. Discover project ids with `kernel.projects.list()` or `kernel.projects.retrieve('<id-or-name>')`.

## Workflow

1. **Classify** the operating mode (A vs B). If mixed, name which surface each piece is on.
2. **Construct the client.** `import Kernel from '@onkernel/sdk'`. Verify env (`KERNEL_API_KEY` is the only required one; `KERNEL_LOG`, `KERNEL_BASE_URL`, `KERNEL_CUSTOM_HEADERS`, `KERNEL_SUPPRESS_BUN_WARNING`, and `KERNEL_BROWSER_ROUTING_SUBRESOURCES` — comma-separated path prefixes routed direct-to-VM, default `curl,telemetry/stream`, empty string disables direct routing — are optional). For local dev hitting `https://localhost:3001/`, pass `environment: 'development', baseURL: null`. For project-scoped work with an org-wide key, pass `projectID` to the constructor. See [references/guides/client-and-config.md](references/guides/client-and-config.md).
3. **Pick the browser-control surface** — raw CDP / Playwright-inside-VM / computer-controls / browser-curl. See [references/patterns/browser-control-surfaces.md](references/patterns/browser-control-surfaces.md).
4. **Wire profiles or Managed Auth** if the agent needs persistent login. See [references/patterns/profiles-pools-credentials.md](references/patterns/profiles-pools-credentials.md) and [references/guides/managed-auth.md](references/guides/managed-auth.md).
5. **Handle lifecycle.** Always pair `browsers.create` with `browsers.deleteByID`, even on error paths. Use `try/finally`. See [references/guides/browsers-lifecycle.md](references/guides/browsers-lifecycle.md).
6. **Deploy or run.** For Mode B, `kernel deploy` and consume invocations; for Mode A, run inside your service. See [references/guides/apps-deploy-invoke.md](references/guides/apps-deploy-invoke.md) and [references/examples/deploy-and-invoke-app.md](references/examples/deploy-and-invoke-app.md).

## Do this, not that

| Do this | Not that |
|---|---|
| `await kernel.browsers.deleteByID(session.session_id)` in a `finally` | `await browser.close()` and assume the browser is gone |
| `chromium.connectOverCDP(session.cdp_ws_url)` then `browser.contexts()[0]` | `browser.newContext()` to "isolate" the test |
| `kernel.browsers.playwright.execute(id, { code: '…' })` for hot paths | round-trip every call over CDP from your service |
| `version, async: true, async_timeout_seconds: 1800` + `invocations.follow` | omit the required `version`, or depend on the sync invocation cap holding for a multi-minute scrape |
| `JSON.stringify(payload)` and `JSON.parse(invocation.output ?? 'null')` | pass non-JSON-serializable objects to `payload` |
| `try { ... } catch (e) { if (e instanceof Kernel.APIError) … }` | swallow errors or `catch (e: any)` without checking subclasses |
| `browsers.create({ profile: { name } })` after Managed Auth completes | re-prompt the user every session |
| Pin `@onkernel/sdk` to a minor range and bump deliberately | track `latest` (Stainless regenerates frequently) |
| `kernel.browsers.curl(id, { url })` for HTTP from inside the browser's TLS fingerprint | spin up a separate Playwright `request` context and lose the fingerprint |
| Pass `projectID: '…'` to the `Kernel` constructor to scope an org-wide key | hand-roll `defaultHeaders` for project scoping, or rely on key scope and get cross-project lists |

## Steering callouts

> **`browser.close()` is not cleanup.** Closing the Playwright `Browser` only disconnects CDP. The Kernel browser keeps running until `timeout_seconds` elapses or you call `kernel.browsers.deleteByID(session_id)`. Always pair `create` with `deleteByID` in a `finally` block.

> **There is already a default context and page.** Calling `browser.newContext()` makes a *second* context; cookies, storage, and profile state live on the default one. Use `browser.contexts()[0].pages()[0]`.

> **Sync invocations time out at ~100 s.** If your action does any non-trivial browser work, set `async: true` and `invocations.follow(id)` instead. Switching after the fact requires re-deploying.

> **Stagehand v4 attaches over CDP; `new Stagehand(...)` is gone.** The constructor is private — use `Stagehand.create()`, and pass a `browser` (it is required). Mirror the Stagehand extension onto the Kernel browser's filesystem first (`browsers.fs.uploadZip`), then `const browser = await localBrowser.connect({ cdpUrl: session.cdp_ws_url })` and `await Stagehand.create({ browser, model: { modelName: 'openai/gpt-4o', apiKey: process.env.MODEL_API_KEY } })`. `env: 'LOCAL'` and `localBrowserLaunchOptions` are v3-only and do not exist in v4; `modelName` must be namespaced (`openai/…`, `anthropic/…`), never bare `'gpt-4o'`. Top-level `apiKey` is the **Stagehand** key — model credentials belong in `model.apiKey`. `projectId` is not a `Stagehand.create` option at all; Browserbase credentials live on `browserbase.connect(...)`. `stagehand.page` was removed — `act`/`extract`/`observe` are on the instance.

> **`proxy_id` is deprecated on `browsers.create`.** Pass the typed `proxy` object instead — `proxy: { id }`, `proxy: { name }`, or `proxy: { mode: 'direct' | 'default' }`. `proxy` and `proxy_id` cannot be combined, and `proxy_id` is `@deprecated` on every browser response shape too.

## Bundled scripts

| Script | Use |
|---|---|
| `scripts/check-kernel-sdk-version.sh` | Preflight Node/npm, installed Kernel package versions, npm latest versions, the installed SDK type declarations, and `KERNEL_API_KEY` presence. See `scripts/check-kernel-sdk-version.sh.md`. |
| `scripts/scaffold-kernel-app.sh` | Generate a minimal embedded SDK example or deployable Kernel App in an empty directory. See `scripts/scaffold-kernel-app.sh.md`. |

## Reference routing

| Document | What it contains | Load when |
|---|---|---|
| [references/guides/client-and-config.md](references/guides/client-and-config.md) | Env vars, environments, retries, idempotency, pagination, error taxonomy, request options | Constructing the client, debugging auth/network errors, handling pagination |
| [references/guides/browsers-lifecycle.md](references/guides/browsers-lifecycle.md) | `browsers.create` params, `BrowserCreateResponse`, standby, termination, viewport, timeout semantics | Creating, configuring, or terminating browsers |
| [references/guides/apps-deploy-invoke.md](references/guides/apps-deploy-invoke.md) | `deployments.*`, `invocations.create` sync vs async, `invocations.follow` SSE, secrets, logs | Deploying a Kernel App or invoking it from another service |
| [references/guides/managed-auth.md](references/guides/managed-auth.md) | 3-piece architecture, `auth.connections.*`, `<KernelManagedAuth />` props, profile interop | Authenticating an agent on a user's behalf into a SaaS |
| [references/patterns/browser-control-surfaces.md](references/patterns/browser-control-surfaces.md) | Decision tree across CDP, `playwright.execute`, `computer.*`, `curl` | Picking the right control surface for a task |
| [references/patterns/playwright-stagehand-integration.md](references/patterns/playwright-stagehand-integration.md) | `connectOverCDP` idiom, default-context warning, Stagehand connect/launch options, `kernel create --template` | Wiring Playwright or Stagehand to a Kernel browser |
| [references/patterns/profiles-pools-credentials.md](references/patterns/profiles-pools-credentials.md) | `profiles.*`, `browserPools.*`, `credentials.*`, `credentialProviders.*` (1Password) | Persistent login state, pool warm-starts, credential providers |
| [references/patterns/integrations-matrix.md](references/patterns/integrations-matrix.md) | Hookup snippets per integration (Stagehand, Browser Use, Claude Agent SDK, Vibium, etc.) | Connecting a third-party agent framework to a Kernel browser |
| [references/examples/browser-screenshot.md](references/examples/browser-screenshot.md) | Minimal end-to-end TS example | Sanity-check first run; copy as a starting scaffold |
| [references/examples/deploy-and-invoke-app.md](references/examples/deploy-and-invoke-app.md) | `kernel deploy` + `invocations.create` + `invocations.follow` walkthrough | Building or invoking a Kernel App |
| [references/examples/managed-auth-flow.md](references/examples/managed-auth-flow.md) | Full Next.js page + backend route + browser launch | Implementing the Managed Auth handoff end-to-end |
| [references/troubleshooting/pitfalls.md](references/troubleshooting/pitfalls.md) | The 16 production gotchas in priority order | Debugging unexpected behavior or before shipping |
| [references/troubleshooting/files-and-replays.md](references/troubleshooting/files-and-replays.md) | `browsers.fs.*`, `browsers.replays.*`, download timing, multipart | File I/O or replay download is failing or slow |
| [references/troubleshooting/auth-and-profile-errors.md](references/troubleshooting/auth-and-profile-errors.md) | 409 conflict, profile not found, hosted-page handoff failures | Managed Auth or profile errors |

## Output contract

When finishing a Kernel task, report only fields that apply:

- operating mode: embed, deploy, or mixed
- package versions checked and package range pinned
- created browser `session_id` values and whether each was deleted, pool-released, intentionally timed out, or left running with reason
- live view URL or replay/artifact path when relevant
- profile name/id and whether `save_changes` was used
- Managed Auth connection id, final `flow_status`/`status`, and profile name
- deployment id, app name, version, and action name
- invocation id, sync/async mode, terminal status, and how logs/events were consumed
- payload/output size strategy for large artifacts
- verification rung actually reached: typecheck, script run, live browser run, deployment/invocation observed

## Verification

End-to-end checks for any Kernel-TS task:

1. The SDK is installed at a pinned version, and every method you call appears in the installed declarations (`node_modules/@onkernel/sdk/client.d.ts` for resources, `node_modules/@onkernel/sdk/resources/**/*.d.ts` for signatures) — `npx tsc --noEmit` passes. The package ships no `api.md`; do not gate on one.
2. `KERNEL_API_KEY` resolves at runtime; if the key is org-wide, `projectID` is passed to the constructor. Confirm with `await kernel.auth.context.retrieve()` — `authorization.credential_scope.project_id === null` means the key is org-wide, and `authorization.effective_scope.project_id` must equal the project you intended.
3. Every `browsers.create` is paired with a `deleteByID` in a `finally` (grep the diff).
4. Long-running invocations use `async: true` and consume `invocations.follow(id)` events (`log`, `invocation_state`, `error`, `sse_heartbeat`).
5. Stagehand/Playwright wiring uses `browser.contexts()[0]` not `newContext()`.
6. Stealth is on (`stealth: true`) for anything user-facing or against a real SaaS.
7. For Managed Auth: `<KernelManagedAuth />` is a client component (`"use client"`), the backend route calls `auth.connections.create` and `auth.connections.login`, and downstream `browsers.create` uses the same `profile.name`.

## Cost cleanup check

- List open browsers when SDK/CLI is available; created `session_id` values must be deleted, released back to a pool, intentionally timed out, or left running with a reason.
- For browser pools, release every acquired browser or explain why it was destroyed/rebuilt.
- For deployments/invocations, report deployment/invocation ids, app/action/version, terminal status, and how logs/events were consumed.
- For large artifacts, report whether they moved through `browsers.fs.*`, replay download, or object storage before browser cleanup.

## Final checks

- [ ] `KERNEL_API_KEY` is read from env, not hardcoded.
- [ ] `@onkernel/sdk` version is pinned.
- [ ] Every `browsers.create` is paired with `deleteByID` (or wrapped by an invocation that will reap).
- [ ] Sync vs async invocation chosen deliberately; the right `*_timeout_seconds` is set.
- [ ] Default-context-only: no `browser.newContext()` against a Kernel browser.
- [ ] Errors are caught with `instanceof Kernel.APIError` (or specific subclass).
- [ ] If Managed Auth is used: backend creates the connection, the React component is `"use client"`, the success handler launches a browser with the matching `profile.name`.
- [ ] If Mode B: `package.json` has `"type": "module"` for TS apps; `kernel deploy` succeeded; the action is registered.
- [ ] No leftover `browser.close()` as the only cleanup.
- [ ] Stealth + timeout defaults match the **Default stance** above unless the task justifies otherwise.

## Scope boundaries

This skill covers `@onkernel/sdk` and `@onkernel/managed-auth-react` in TypeScript. It does not cover:

- The Python SDK (`kernel-python-sdk`)
- Terminal-driving the `agent-browser` CLI (use `run-agent-browser`)
- The full `kernel` CLI surface beyond `deploy` and `invoke` (read `kernel --help` directly)
- Browser Use's Python framework — there is no native TS package

