Skip to content

Commit 9a92675

Browse files
authored
Merge pull request #311 from kernel/hypeship/kernel-runtime-upstream
feat(browser-runtime): add native Kernel support
2 parents 95c43ed + 19e9605 commit 9a92675

12 files changed

Lines changed: 725 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
88

99
## [0.9.2] - 2026-08-18
1010
### Added
11+
- Added Kernel as a managed remote browser runtime with live view and downloaded replay recordings.
1112
- Added support for the [WebBrain](https://github.com/webbrain-one/webbrain) harness. Thanks to @alectimison-maker.
1213

1314
### Fixed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ Once the container starts, the script prints a **noVNC URL** (e.g. `http://local
296296
clawbench-batch --models your-model --cases-suite v2 --all-cases
297297
```
298298

299-
`your-model` is a key you configured in step 1; `--cases-suite v2` runs the full V2 corpus (swap in `v1-lite` for the 20-task subset). Add `--max-concurrent N` to run tasks in parallel (default 2 locally, 1 with Browserbase) and `--harness <name>` to pick an agent. Each task is intercepted and scored by the `deepseek-v4-pro` judge from step 1 — pass `--no-judge` to skip scoring. A `batch-summary.json` plus per-run recordings land under `./test-output/`.
299+
`your-model` is a key you configured in step 1; `--cases-suite v2` runs the full V2 corpus (swap in `v1-lite` for the 20-task subset). Add `--max-concurrent N` to run tasks in parallel (default 2 locally, 1 with Kernel or Browserbase) and `--harness <name>` to pick an agent. Each task is intercepted and scored by the `deepseek-v4-pro` judge from step 1 — pass `--no-judge` to skip scoring. A `batch-summary.json` plus per-run recordings land under `./test-output/`.
300300

301301
**By hand, to produce a human reference run:**
302302

@@ -330,7 +330,7 @@ Full registry: [`src/clawbench/runtime/harnesses/harnesses.yaml`](src/clawbench/
330330

331331
| I want to… | Where |
332332
| --- | --- |
333-
| Use a managed remote browser instead of a local container | [`docs/browser-runtimes.md`](docs/browser-runtimes.md) — Browserbase setup, options, recording URLs |
333+
| Use a managed remote browser instead of a local container | [`docs/browser-runtimes.md`](docs/browser-runtimes.md) — Kernel and Browserbase setup, options, and recordings |
334334
| Run V2 through the Harbor framework (and run it fast) | [`docs/harbor.md`](docs/harbor.md) — conversion, judge wiring, concurrency, troubleshooting |
335335
| See every CLI command and flag | [`docs/cli.md`](docs/cli.md) |
336336

@@ -754,7 +754,7 @@ Each session records five layers of synchronized data under `/data/`:
754754

755755
| Layer | File | Description |
756756
| ------------------ | ---------------------- | --------------------------------------------------------------- |
757-
| Session replay | `recording.mp4` or `run-meta.json` recording URL | Local H.264 video or Browserbase Session Inspector replay |
757+
| Session replay | `recording.mp4` or `run-meta.json` recording URL | Local/Kernel H.264 video or Browserbase Session Inspector replay |
758758
| Action screenshots | `screenshots/*.png` | Throttled timestamped PNGs captured after browser actions |
759759
| Browser actions | `actions.jsonl` | Every DOM event (click, keydown, input, pageLoad, scroll, etc.) |
760760
| HTTP traffic | `requests.jsonl` | Every HTTP request with headers, body, and query params |

docs/README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ uv run clawbench-run test-cases/v1/001-daily-life-food-uber-eats claude-sonnet-4
243243
clawbench-batch --models your-model --cases-suite v2 --all-cases
244244
```
245245

246-
`your-model` 是你在第 1 步里配置的 key;`--cases-suite v2` 跑完整 V2 语料(换成 `v1-lite` 则是 20 题子集)。`--max-concurrent N` 控制并发(本地默认 2,Browserbase 默认 1),`--harness <name>` 选择智能体。每个任务都会被拦截并由第 1 步配置的 `deepseek-v4-pro` judge 打分 —— 加 `--no-judge` 可跳过评分。`batch-summary.json` 和各次运行的录制都会写到 `./test-output/`。
246+
`your-model` 是你在第 1 步里配置的 key;`--cases-suite v2` 跑完整 V2 语料(换成 `v1-lite` 则是 20 题子集)。`--max-concurrent N` 控制并发(本地默认 2,Kernel 或 Browserbase 默认 1),`--harness <name>` 选择智能体。每个任务都会被拦截并由第 1 步配置的 `deepseek-v4-pro` judge 打分 —— 加 `--no-judge` 可跳过评分。`batch-summary.json` 和各次运行的录制都会写到 `./test-output/`。
247247

248248
**自己上手操作,产出人工参考轨迹:**
249249

@@ -277,7 +277,7 @@ harness 是驱动浏览器的智能体框架,和模型是两个独立维度。
277277

278278
| 我想…… | 去哪看 |
279279
| --- | --- |
280-
| 用托管的远程浏览器代替本地容器 | [`docs/browser-runtimes.md`](browser-runtimes.md) —— Browserbase 配置、参数、录制地址 |
280+
| 用托管的远程浏览器代替本地容器 | [`docs/browser-runtimes.md`](browser-runtimes.md) —— Kernel 和 Browserbase 配置、参数与录制 |
281281
| 用 Harbor 框架跑 V2(并且跑得快) | [`docs/harbor.md`](harbor.md) —— 转换、judge 配置、并发、排错 |
282282
| 查所有 CLI 命令和参数 | [`docs/cli.md`](cli.md) |
283283

@@ -649,7 +649,7 @@ ClawBench 的定位:**真实消费级网站、日常任务、端到端录制**
649649

650650
|| 文件 | 描述 |
651651
|-------|------|-------------|
652-
| 会话回放 | `recording.mp4``run-meta.json` 中的录制 URL | 本地 H.264 视频,或 Browserbase Session Inspector 回放 |
652+
| 会话回放 | `recording.mp4``run-meta.json` 中的录制 URL | 本地/Kernel H.264 视频,或 Browserbase Session Inspector 回放 |
653653
| 动作截图 | `screenshots/*.png` | 浏览器动作后经限流捕获的带时间戳 PNG |
654654
| 浏览器动作 | `actions.jsonl` | 每个 DOM 事件 (click, keydown, input, pageLoad, scroll 等) |
655655
| HTTP 流量 | `requests.jsonl` | 每个 HTTP 请求,包含 headers、body 和查询参数 |

docs/browser-runtimes.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,42 @@
22

33
By default ClawBench launches Chromium inside its own container. You can point it at a managed remote browser instead — useful when the host cannot run containers comfortably, or when you want the provider to handle scaling and session replay.
44

5-
`--browser-runtime` accepts `local` (default), `browserbase`, `remote-cdp`, and `steel`. **`steel` is reserved and not implemented yet** — selecting it raises an error.
5+
`--browser-runtime` accepts `local` (default), `kernel`, `browserbase`, `remote-cdp`, and `steel`. **`steel` is reserved and not implemented yet** — selecting it raises an error.
66

77
## Local container (default)
88

99
Nothing to configure. Chromium, Xvfb, ffmpeg, noVNC, and the recorder/interceptor all run in the task container; the session video lands at `recording.mp4`.
1010

11+
## Kernel
12+
13+
Put the key in `.env.local`:
14+
15+
```dotenv
16+
KERNEL_API_KEY=...
17+
```
18+
19+
Then select the runtime on a single or batch run:
20+
21+
```bash
22+
uv run clawbench-run test-cases/v1/<case> your-model \
23+
--browser-runtime kernel
24+
25+
uv run clawbench-batch --models your-model --all-cases \
26+
--browser-runtime kernel
27+
```
28+
29+
Kernel runs use ClawBench's existing CDP action capture, screenshots, HTTP logging, and request interception. ClawBench starts a Kernel replay with each browser and downloads the completed video to `data/recording.mp4` before deleting the session.
30+
31+
Provider options are passed as JSON. Supported fields are `stealth`, `region`, `proxy`, and `tags`:
32+
33+
```bash
34+
uv run clawbench-batch --models your-model --all-cases \
35+
--browser-runtime kernel \
36+
--browser-runtime-options '{"stealth":true,"region":"us-east"}'
37+
```
38+
39+
Set `KERNEL_BASE_URL` to override the default `https://api.onkernel.com` API endpoint. Batch concurrency defaults to **1**; raise `--max-concurrent` only as far as your Kernel account limit allows.
40+
1141
## Browserbase
1242

1343
Put the key in `.env.local`:

docs/cli.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ clawbench-run <case-dir> --human # human reference run
4242
| `--output-dir <path>` | `<project>/test-output` | Where run directories are written |
4343
| `--no-build` | off | Skip building the container image (assumes it exists) |
4444
| `--no-upload` | off | Skip HuggingFace upload even if `HF_TOKEN` is configured |
45-
| `--browser-runtime <name>` | `local` | `local`, `browserbase`, `remote-cdp` — see [`browser-runtimes.md`](browser-runtimes.md) |
45+
| `--browser-runtime <name>` | `local` | `local`, `kernel`, `browserbase`, `remote-cdp` — see [`browser-runtimes.md`](browser-runtimes.md) |
4646
| `--browser-cdp-url <url>` || CDP endpoint for `--browser-runtime remote-cdp` |
4747
| `--browser-runtime-options <json>` || Provider-specific options, e.g. `'{"region":"us-west-2"}'` |
4848

@@ -71,7 +71,7 @@ Execution:
7171

7272
| Flag | Default | What it does |
7373
| --- | --- | --- |
74-
| `--max-concurrent <n>` | 2 local, 1 Browserbase | Parallel jobs |
74+
| `--max-concurrent <n>` | 2 local, 1 Kernel/Browserbase | Parallel jobs |
7575
| `--stagger-delay <s>` | 15 | Minimum seconds between consecutive container starts (rolling start) |
7676
| `--resume <dir>` || Reuse a previous batch's output directory and skip finished runs |
7777
| `--dry-run` | off | Print the job matrix without running anything |
@@ -125,6 +125,8 @@ See [Reproduce the leaderboard](../README.md#reproduce-the-leaderboard) for the
125125
| `CONTAINER_ENGINE` | Force `docker` or `podman` |
126126
| `HF_TOKEN` | Optional upload of runs to HuggingFace |
127127
| `BROWSERBASE_API_KEY` | Browserbase runtime (from `.env.local`) |
128+
| `KERNEL_API_KEY` | Kernel runtime (from `.env.local`) |
129+
| `KERNEL_BASE_URL` | Optional Kernel API base URL override |
128130
| `CLAWBENCH_JUDGE_*` | Judge credentials for Harbor's verifier — see [`harbor.md`](harbor.md) |
129131

130132
PurelyMail credentials for disposable run emails come from the committed `.env`.

src/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,22 +196,22 @@ A stagger delay is applied between job starts since during container startup it
196196
| `--cases-dir PATH` | Custom case directory | none |
197197
| `--all-cases` | Use all task directories in the selected suite/dir | false |
198198
| `--case-range START-END` | Filter by numeric case ID prefix | none |
199-
| `--max-concurrent N` | Max parallel jobs; Browserbase defaults to 1 | 2 locally, 1 with Browserbase |
199+
| `--max-concurrent N` | Max parallel jobs; managed runtimes default to 1 | 2 locally, 1 with Kernel/Browserbase |
200200
| `--output-dir PATH` | Base output directory | `test-output` |
201201
| `--stagger-delay SECONDS` | Minimum gap between consecutive container starts | 15 |
202202
| `--dry-run` | Print job matrix without running | false |
203203
| `--no-upload` | Skip HuggingFace upload for all runs | false |
204204
| `--harness NAME` | Harness image to use | `openclaw` |
205-
| `--browser-runtime NAME` | Browser runtime (`local`, `remote-cdp`, or `browserbase`) | `local` |
205+
| `--browser-runtime NAME` | Browser runtime (`local`, `remote-cdp`, `kernel`, or `browserbase`) | `local` |
206206
| `--browser-cdp-url URL` | CDP endpoint used with `--browser-runtime remote-cdp` | none |
207207
| `--browser-runtime-options JSON` | Provider options such as Browserbase region or proxies | none |
208208

209-
Browserbase reads `BROWSERBASE_API_KEY` from `.env.local` or the process
210-
environment. Its provider-hosted Session Inspector recording URL is stored in
211-
`browser_runtime.recording_url` in `run-meta.json`; local MP4 recording is
212-
thus omitted to save space and bandwidth. The signed provider CDP URL is mounted into the runtime
213-
container through a temporary read-only secret file and redacted from saved
214-
metadata.
209+
Kernel reads `KERNEL_API_KEY`; Browserbase reads `BROWSERBASE_API_KEY`. Both
210+
can be set in `.env.local` or the process environment. Kernel replay video is
211+
downloaded to `data/recording.mp4`; Browserbase's provider-hosted Session
212+
Inspector URL is stored in `browser_runtime.recording_url` in `run-meta.json`.
213+
Signed provider CDP URLs are mounted into the runtime container through a
214+
temporary read-only secret file and redacted from saved metadata.
215215

216216
Signal handling:
217217

src/clawbench/runner/batch.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def detect_engine() -> str:
4949
"claw-eval": "test-cases/claw-eval",
5050
}
5151
DEFAULT_CASES_SUITE = "v2"
52+
MANAGED_BROWSER_RUNTIMES = frozenset({"browserbase", "kernel"})
5253

5354

5455
def load_models_yaml() -> dict:
@@ -289,6 +290,8 @@ async def run_job(
289290
cmd_parts += ["--harness", harness]
290291
if browser_runtime:
291292
cmd_parts += ["--browser-runtime", browser_runtime]
293+
if browser_runtime in MANAGED_BROWSER_RUNTIMES:
294+
cmd_parts.append("--hide-browser-viewer")
292295
if browser_cdp_url:
293296
cmd_parts += ["--browser-cdp-url", browser_cdp_url]
294297
if browser_runtime_options:
@@ -590,13 +593,13 @@ async def async_main(args: argparse.Namespace) -> int:
590593
running_procs.clear()
591594
browser_runtime = getattr(args, "browser_runtime", None) or "local"
592595
if getattr(args, "max_concurrent", None) is None:
593-
args.max_concurrent = 1 if browser_runtime == "browserbase" else 2
596+
args.max_concurrent = 1 if browser_runtime in MANAGED_BROWSER_RUNTIMES else 2
594597
if (
595-
browser_runtime == "browserbase"
598+
browser_runtime in MANAGED_BROWSER_RUNTIMES
596599
and args.harness == "claude-code-chrome-extension"
597600
):
598601
print(
599-
"ERROR: browserbase runtime does not support the "
602+
f"ERROR: {browser_runtime} runtime does not support the "
600603
"claude-code-chrome-extension harness"
601604
)
602605
return 1
@@ -821,7 +824,7 @@ def main() -> None:
821824
"--max-concurrent",
822825
type=int,
823826
default=None,
824-
help="Max parallel jobs (default: 1 for browserbase, otherwise 2)",
827+
help="Max parallel jobs (default: 1 for managed runtimes, otherwise 2)",
825828
)
826829
p.add_argument("--output-dir", default="test-output", help="Base output directory")
827830
p.add_argument(
@@ -863,7 +866,7 @@ def main() -> None:
863866
choices=BROWSER_RUNTIME_CHOICES,
864867
default=None,
865868
help=(
866-
"Browser runtime provider: local, remote-cdp, steel, or browserbase "
869+
"Browser runtime provider: local, remote-cdp, steel, browserbase, or kernel "
867870
"(default: local)"
868871
),
869872
)

src/clawbench/runner/run.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def main():
127127
choices=BROWSER_RUNTIME_CHOICES,
128128
default=None,
129129
help=(
130-
"Browser runtime provider: local, remote-cdp, steel, or browserbase "
130+
"Browser runtime provider: local, remote-cdp, steel, browserbase, or kernel "
131131
"(default: CLAWBENCH_BROWSER_RUNTIME or local)"
132132
),
133133
)
@@ -141,6 +141,11 @@ def main():
141141
default=None,
142142
help="JSON object with provider-specific browser runtime options",
143143
)
144+
parser.add_argument(
145+
"--hide-browser-viewer",
146+
action="store_true",
147+
help=argparse.SUPPRESS,
148+
)
144149
parser.add_argument(
145150
"--judge",
146151
default="deepseek-v4-pro",
@@ -184,11 +189,11 @@ def main():
184189
if args.human and browser_runtime_provider.name != "local":
185190
parser.error("human mode currently supports only --browser-runtime local")
186191
if (
187-
browser_runtime_provider.name == "browserbase"
192+
browser_runtime_provider.name in {"browserbase", "kernel"}
188193
and args.harness == "claude-code-chrome-extension"
189194
):
190195
parser.error(
191-
"browserbase runtime does not support the "
196+
f"{browser_runtime_provider.name} runtime does not support the "
192197
"claude-code-chrome-extension harness"
193198
)
194199

@@ -230,19 +235,18 @@ def main():
230235
judge_cfg: dict | None = None
231236
personal_info_metadata: dict[str, Any] | None = None
232237
browser_session: BrowserSession | None = None
238+
browser_runtime_finalized = False
233239
browser_runtime_cleaned = False
234240
browser_cdp_secret_dir: Path | None = None
235241

236242
def _browser_runtime_meta() -> dict[str, Any]:
237243
if browser_session is not None:
238244
return browser_session.to_metadata()
239245
mode = "local" if browser_runtime_provider.name == "local" else "remote"
240-
recording_mode = (
241-
"x11"
242-
if mode == "local"
243-
else "provider"
244-
if browser_runtime_provider.name == "browserbase"
245-
else "disabled"
246+
recording_mode = getattr(
247+
browser_runtime_provider,
248+
"default_recording_mode",
249+
"x11" if mode == "local" else "disabled",
246250
)
247251
return {
248252
"provider": browser_runtime_provider.name,
@@ -260,7 +264,10 @@ def _browser_runtime_meta() -> dict[str, Any]:
260264
}
261265

262266
def _recording_required() -> bool:
263-
return browser_session is None or browser_session.recording_mode == "x11"
267+
return browser_session is None or browser_session.recording_mode in {
268+
"x11",
269+
"provider-download",
270+
}
264271

265272
def _write_browser_cdp_secret(session: BrowserSession) -> Path | None:
266273
nonlocal browser_cdp_secret_dir
@@ -293,6 +300,13 @@ def _cleanup_browser_cdp_secret() -> None:
293300
except OSError:
294301
pass
295302

303+
def _finalize_browser_runtime() -> None:
304+
nonlocal browser_runtime_finalized
305+
if browser_session is None or browser_runtime_finalized:
306+
return
307+
browser_runtime_provider.finalize(browser_session, output_dir)
308+
browser_runtime_finalized = True
309+
296310
def _cleanup_browser_runtime() -> None:
297311
nonlocal browser_runtime_cleaned
298312
if browser_session is None or browser_runtime_cleaned:
@@ -527,10 +541,16 @@ def handle_sigint(sig, frame):
527541
)
528542
console.print(" Open the URL above to watch the agent in real-time.\n")
529543
elif browser_session.viewer_url:
530-
viewer_url = browser_session.to_metadata()["viewer_url"]
531-
console.print(
532-
f"\n Browser viewer: [link={viewer_url}]{viewer_url}[/link]\n"
533-
)
544+
if args.hide_browser_viewer:
545+
console.print(
546+
f"\n Remote browser: {browser_session.provider} "
547+
"(viewer URL hidden in batch mode)\n"
548+
)
549+
else:
550+
viewer_url = browser_session.viewer_url
551+
console.print(
552+
f"\n Browser viewer: [link={viewer_url}]{viewer_url}[/link]\n"
553+
)
534554
else:
535555
console.print(
536556
f"\n Remote browser: {browser_session.provider} "
@@ -565,6 +585,12 @@ def handle_sigint(sig, frame):
565585
model_cfg=None if args.human else model_cfg,
566586
)
567587

588+
phase = "finalizing_browser_runtime"
589+
_finalize_browser_runtime()
590+
phase = "cleaning_browser_runtime"
591+
_cleanup_browser_runtime()
592+
_cleanup_browser_cdp_secret()
593+
568594
# Stage 2 — LLM judge (default on, --no-judge to skip).
569595
# Only invoked when stage 1 (intercepted) succeeded; otherwise the
570596
# task already fails at stage 1 and there's nothing to judge.
@@ -616,10 +642,6 @@ def handle_sigint(sig, frame):
616642
)
617643
print(f"Judge skipped due to error: {e}")
618644

619-
phase = "cleaning_browser_runtime"
620-
_cleanup_browser_runtime()
621-
_cleanup_browser_cdp_secret()
622-
623645
# Write run metadata
624646
phase = "writing_run_meta"
625647
duration = time.time() - start_time

0 commit comments

Comments
 (0)