Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
5c9e720
Recover bounded capture deadlines and preserve terminal session loss
miguelg719 Sep 7, 2026
2c791a2
Add bounded CDP heartbeat and sanitized disconnect diagnostics
miguelg719 Sep 7, 2026
2603149
Align label, strict locator, and URL wait semantics
miguelg719 Sep 7, 2026
6a7a227
Resolve facade locators and snapshot refs across frames
miguelg719 Sep 7, 2026
69d92c0
Bound stalled DeepAgents sessions and cleanup
miguelg719 Sep 7, 2026
de9b161
Route existing harness models through native providers
miguelg719 Sep 7, 2026
9902640
Preserve SDK usage reporting and interrupted Codex usage
miguelg719 Sep 7, 2026
846718c
Bound SDK event logs while preserving screenshot evidence
miguelg719 Sep 7, 2026
bddda59
Keep verifier outcomes, uncertainty, and execution failures auditable
miguelg719 Sep 7, 2026
0510620
Cleanup
miguelg719 Sep 7, 2026
41af704
style(core): format capture regression imports
miguelg719 Sep 7, 2026
001d362
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 7, 2026
34be62d
Merge branch 'evals/consolidation-04-cdp-diagnostics' into evals/cons…
miguelg719 Sep 7, 2026
0d88c20
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 7, 2026
f236949
fix(go): refresh embedded extension after frame snapshot fix
miguelg719 Sep 8, 2026
299478d
chore(go): include refreshed extension in release metadata
miguelg719 Sep 8, 2026
e043e1e
fix(runtime): preserve terminal diagnostics and public error boundaries
miguelg719 Sep 8, 2026
7a3e743
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
8e887b3
Merge branch 'evals/consolidation-02-session-ownership' into evals/co…
miguelg719 Sep 8, 2026
9ad36e8
Preserve sanitized CDP transport causes in drop diagnostics
miguelg719 Sep 8, 2026
03f1e02
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
6f981aa
Fix locator filter semantics and shared action deadlines
miguelg719 Sep 8, 2026
b69db32
Merge updated parent into locator contract PR
miguelg719 Sep 8, 2026
ae49e55
Advertise URL waiting with the locator implementation
miguelg719 Sep 8, 2026
4bbedb8
Merge updated parent into frame support PR
miguelg719 Sep 8, 2026
d6ca271
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
1fc96d1
Keep DeepAgents watchdog contexts in their owning task
miguelg719 Sep 8, 2026
c185669
Require xAI credentials before creating the native model
miguelg719 Sep 8, 2026
cc06424
Fix frame locator effects and isolate snapshot documents
miguelg719 Sep 8, 2026
75bc6f8
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
31af73b
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
c3fe070
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
0249269
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
d33f457
fix(evals): share offline verifier policy and preserve ungraded uncer…
miguelg719 Sep 8, 2026
dcbb62b
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
87796a0
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
c473cb6
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 8, 2026
4ed949e
Merge branch 'evals/consolidation-04-cdp-diagnostics' into evals/cons…
miguelg719 Sep 8, 2026
e8e9d6b
Merge branch 'evals/consolidation-02-session-ownership' into evals/co…
miguelg719 Sep 8, 2026
71b6ba0
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
e99b787
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
f6346e3
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
7bce929
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
2c10b4f
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
ceeb01f
fix(evals): leave uncertain verifier spans ungraded
miguelg719 Sep 8, 2026
8bea6e1
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
50ecddd
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
0c2033b
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 8, 2026
9346b06
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
738206a
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
12766b0
Give strict locator assertions a stable browser command budget
miguelg719 Sep 8, 2026
391cfc9
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
168e919
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
5364fc8
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
533dff0
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
de45fab
Retain the last valid Codex rollout usage total
miguelg719 Sep 8, 2026
4150fb2
Assert layout diagnostics exclude raw CDP error codes
miguelg719 Sep 8, 2026
a521d91
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
13b1e18
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
e9aaddf
fix(harness): retain visible failures and bound event evidence
miguelg719 Sep 8, 2026
d5e1da6
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
f915e6b
Merge branch 'evals/consolidation-02-session-ownership' into evals/co…
miguelg719 Sep 8, 2026
35321b7
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
03ab493
Merge branch 'evals/consolidation-04-cdp-diagnostics' into evals/cons…
miguelg719 Sep 8, 2026
e359564
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 8, 2026
837dc65
fix(deepagents): distinguish owned watchdog expiry from inner timeouts
miguelg719 Sep 8, 2026
e99d7b0
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
3a18643
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
5796ed5
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
edc6027
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
23fa9f8
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
395a5e5
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
8f6b5b5
fix(evals): retain ungraded verifier failures and trusted evidence
miguelg719 Sep 8, 2026
6ecc3d0
fix(evals): tighten shared runner reporting and accounting
miguelg719 Sep 8, 2026
5e739f7
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
90721ea
fix(evals): defer runtime loading for general CLI help
miguelg719 Sep 8, 2026
8054321
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
11b056a
Merge branch 'evals/consolidation-04-cdp-diagnostics' into evals/cons…
miguelg719 Sep 8, 2026
bfcfd44
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 8, 2026
62193b5
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
83b7865
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
07327b8
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
c3f4bcd
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
b025849
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
207a17d
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
e16f1bc
Merge branch 'evals/consolidation-02-session-ownership' into evals/co…
miguelg719 Sep 8, 2026
5683aad
Merge branch 'evals/consolidation-03-capture-recovery' into evals/con…
miguelg719 Sep 8, 2026
0613e13
Merge branch 'evals/consolidation-04-cdp-diagnostics' into evals/cons…
miguelg719 Sep 8, 2026
76a7b4a
Merge branch 'evals/consolidation-05-locator-wait-contract' into eval…
miguelg719 Sep 8, 2026
02b90be
Merge branch 'evals/consolidation-06-frame-support' into evals/consol…
miguelg719 Sep 8, 2026
f87b4e8
Merge branch 'evals/consolidation-07-deepagents-lifecycle' into evals…
miguelg719 Sep 8, 2026
caad20b
Merge branch 'evals/consolidation-08-native-routing' into evals/conso…
miguelg719 Sep 8, 2026
1a761f9
Merge branch 'evals/consolidation-09-event-retention' into evals/cons…
miguelg719 Sep 8, 2026
bd6fe77
Merge branch 'evals/consolidation-10-usage-provenance' into evals/con…
miguelg719 Sep 8, 2026
3f5ea51
Merge branch 'evals/consolidation-11-verifier-results' into evals/con…
miguelg719 Sep 8, 2026
5addea3
Merge commit 'ef260fa71343690b316a136ca7575a2c26af8501' into HEAD
miguelg719 Sep 10, 2026
05ca9da
Merge commit '5addea36e279a1fc2c2aa5dad8b3cb96619a954c' into HEAD
miguelg719 Sep 10, 2026
d5fc21f
Merge commit '05ca9da698f1a6b2823d1c1462e9ff08fdcccefa' into HEAD
miguelg719 Sep 10, 2026
49e1f5b
Resolve facade locators across closed shadow roots
miguelg719 Sep 10, 2026
66fee7d
Update embedded extension
miguelg719 Sep 10, 2026
e014c9b
Merge commit '66fee7d3623f0d0b626aa386872cabee77483ae7' into HEAD
miguelg719 Sep 10, 2026
bb0e509
Merge commit 'e014c9b3b3afe6cc2def135ee647ee1ebd807baf' into HEAD
miguelg719 Sep 10, 2026
5e809b1
Merge commit 'bb0e5090d302e8eb70163b488bfed0638f823009' into HEAD
miguelg719 Sep 10, 2026
15ad0b0
Merge commit '5e809b166fbee289b2d1b6bec6afb647ccbd7cd3' into HEAD
miguelg719 Sep 10, 2026
8bdd0bb
Merge commit '15ad0b0b05906866850a53a1ed9403a1cafac4a8' into HEAD
miguelg719 Sep 10, 2026
94080eb
Merge commit '8bdd0bb57190181a435b0b17d29a11ff66c41b72' into HEAD
miguelg719 Sep 10, 2026
794fa18
Merge commit '94080eb4250cf4b507df142baf10a41509706bf8' into HEAD
miguelg719 Sep 10, 2026
307df9c
Rebuild extension archive with local dependencies
miguelg719 Sep 10, 2026
28bf333
Merge commit '307df9c76e45612824587c46caa3884eb4d5d01d' into HEAD
miguelg719 Sep 10, 2026
6ee0394
Merge commit '28bf3336faec30e97d0040bff6f5b07289e53ac8' into HEAD
miguelg719 Sep 10, 2026
4912939
Merge commit '6ee03943d18375d4955addebba7b235b571c08e0' into HEAD
miguelg719 Sep 10, 2026
17cd2d3
Merge commit '4912939b6b847fff10b4b2fa22a0d417aec4d824' into HEAD
miguelg719 Sep 10, 2026
08611ac
Merge commit '17cd2d32fc8fd796c81f303c7c89f8849227a0ef' into HEAD
miguelg719 Sep 10, 2026
a08cec7
Merge commit '08611ac78125e724e3eba933ad3652998e7c2774' into HEAD
miguelg719 Sep 10, 2026
e5c1759
Merge commit 'a08cec74dcc87cd81724785c46c898a7111f3004' into HEAD
miguelg719 Sep 10, 2026
61e41a5
Harden internal shadow-root evaluation errors and cleanup
miguelg719 Sep 10, 2026
16717f6
Rebuild extension archive with local dependencies
miguelg719 Sep 10, 2026
06d5b34
Merge commit '16717f6a4d86b244ecb2b2d9ea63e38a3145c20e' into HEAD
miguelg719 Sep 10, 2026
8be6a98
Merge commit '06d5b3494e3901c50d00296b96005aaf74007f66' into HEAD
miguelg719 Sep 10, 2026
dcce7bd
Merge commit '8be6a981ea955fc2246c1b77401d3f8fc29df8d5' into HEAD
miguelg719 Sep 10, 2026
15fe01c
Merge commit 'dcce7bd0178d1c39561d657c40f63db97992fb58' into HEAD
miguelg719 Sep 10, 2026
feffa57
Merge commit '15fe01c7d80be7e69354f97a0089c0245ad97f2b' into HEAD
miguelg719 Sep 10, 2026
2136ac4
Merge commit 'feffa57127a675b950583c81c008a25dfe372bb6' into HEAD
miguelg719 Sep 10, 2026
8f03f33
Merge commit '2136ac456525120b5be5620af986747dbc6107d6' into HEAD
miguelg719 Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eval-capture-deadlines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@browserbasehq/stagehand": patch
---

Bound experimental batch and RPC deadlines so callers can stop waiting without replaying actions or accepting late capture state.
5 changes: 5 additions & 0 deletions .changeset/eval-cdp-heartbeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@browserbasehq/stagehand": patch
---

Add configurable bounded CDP heartbeats and sanitized disconnect diagnostics with cleanup on shutdown.
7 changes: 7 additions & 0 deletions .changeset/eval-frame-snapshot-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@browserbasehq/stagehand-extension": patch
"@browserbasehq/stagehand-go": patch
---

Snapshot references remain valid across same-origin and out-of-process frame captures, including when shadow-root piercing is disabled.
Refresh the Go SDK's embedded extension to include the fix.
5 changes: 5 additions & 0 deletions packages/evals/core/contracts/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ export interface BrowserSessionLoss {
cause: string;
tool?: string;
at?: string;
provider?: "local" | "browserbase";
sessionId?: string;
/** Elapsed time since the facade started browser launch, including initialization. */
sessionAgeMs?: number;
sessionTimeoutMs?: number;
}

/** MCP content returned unchanged by a runner call into its existing surface. */
Expand Down
16 changes: 16 additions & 0 deletions packages/evals/core/tools/browserSessionLoss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ export function parseSessionLossTelemetry(line: string): BrowserSessionLoss | un
cause: sanitizeErrorMessage(parsed.cause),
...(typeof parsed.tool === "string" && { tool: parsed.tool }),
...(typeof parsed.at === "string" && { at: parsed.at }),
...((parsed.provider === "local" || parsed.provider === "browserbase") && {
provider: parsed.provider,
}),
...(typeof parsed.sessionId === "string" && {
sessionId: sanitizeErrorMessage(parsed.sessionId),
}),
...(typeof parsed.sessionAgeMs === "number" &&
Number.isFinite(parsed.sessionAgeMs) &&
parsed.sessionAgeMs >= 0 && {
sessionAgeMs: parsed.sessionAgeMs,
}),
...(typeof parsed.sessionTimeoutMs === "number" &&
Number.isFinite(parsed.sessionTimeoutMs) &&
parsed.sessionTimeoutMs >= 0 && {
sessionTimeoutMs: parsed.sessionTimeoutMs,
}),
};
} catch {
return undefined;
Expand Down
29 changes: 29 additions & 0 deletions packages/evals/docs/verifier-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Verifier evidence gates

External harnesses use the existing V3 verifier with the task's precomputed rubric when available. A requested verification that errors or returns a verifier-uncertainty sentinel fails closed: `_success` is false, `verifierError` explains the failure, and `agentReportedSuccess` preserves the original self-report. Such rows are ungraded and must not be presented as verified benchmark outcomes.

For completed grades, the raw judge verdict is retained under `judge` in `scores/result.json`. The top-level result, `task_data.json`, and the run row carry the same adjusted outcome. Failed or uncertain verification instead persists `{ graded: false, verifierError, judge? }`, without top-level outcome or process scores; `judge` is present only if the verifier returned a response. `scores/gates.json` records diagnostics for completed grades when trajectory persistence is enabled.

## Outcome

A judge pass is rejected when the final answer is empty or, if a mounted-tool matcher is available, the trajectory has no browser tool calls. Grounding checks are advisory by default. `EVAL_REQUIRE_GROUNDING=1` additionally rejects an answer whose checked numeric findings all lack matching observations from a known non-search page. Captured step and terminal accessibility trees are included; unknown-page text cannot establish grounding. A terminal match is recorded as `groundedAtFinalObservation`, while untrusted matches can be recorded as `seenOnUnknownPage`. The check is a text heuristic: images, paraphrases and valid snippet sources can escape its matching, so it is not a replacement for rubric verification.

Execution state remains separate. When populated by the runner or adapter, `harnessStatus`, `harnessStopReason` and `terminationReason` describe completion, budget exhaustion, abort, SDK error and browser loss. The verifier does not synthesize missing lifecycle metadata; its absence means unavailable. A supported task completion can still pass after a disconnect; execution error alone does not erase earlier evidence. An unfinished task must fail its rubric.

## Process

`processScoreStrict` recomputes the weighted process score with explicit `evidenceInsufficient` criteria earning zero while retaining their maximum points in the denominator. `processScore` uses this score; `processScoreLenient` preserves the judge's aggregate. Not-applicable criteria are excluded. Without a criterion breakdown, the judge's aggregate is retained and `scoringIncomplete` flags a short result against the rubric.

Blocker wording is recorded as `blockerMentioned` on criterion diagnostics. It never changes points by itself: permitted fallback and stop-boundary explanations can correctly mention a blocker. The rubric and observed evidence determine whether the requirement was satisfied. This replaces the campaign's overbroad blocker substring heuristic.

## Reporting

These fields depend on the producing runner; this verifier layer forwards them but does not make every harness emit them:

- Where supplied, `facade_tool_calls` and `facade_tool_call_failures` count attempted and failed browser work. Missing counters are unknown, not measured zero. Run-level browser loss comes from runner-owned telemetry. Normalized steps do not provide trusted per-call loss attribution, so tool-output text cannot exclude failures or synthesize a count after session loss. A graded pass with an explicit zero browser-call count is shown in the batch summary; with `EVAL_MAX_UNVERIFIABLE_CRITERIA` enabled, it fails the batch gate.
- Separate agent, evidence-capture and verifier wall times are available only when recorded by the producer.
- Usage must be interpreted with the producer's presence marker and cache convention. Legacy runners may supply zero placeholders; without an explicit presence marker, zero does not establish measured usage. Historical Cursor CLI usage remains unreported.
- A producer's `cost_source` distinguishes reported dollars from a catalog estimate (`computed`). Shared runner estimates use the dated catalog in `pricing/pricing.json`; they are not invoices. This verifier layer does not compute estimates. Without provenance, cost origin is unavailable; unknown, tier-dependent or subscription costs must not be inferred as zero.
- `harnessImplementation` records adapter and SDK versions when supplied. Its absence means unknown implementation; historical labels are preserved.

Use `VERIFIER_PERSIST_TRAJECTORIES=1` for reviewable evidence. HardBench's compatibility gate rejects verifier errors, uncertainty sentinels, missing criteria and self-report fallbacks before accepting a result. Offline transport checks establish integration compatibility; live rubric accuracy still requires the separately recorded live fixtures.
11 changes: 11 additions & 0 deletions packages/evals/framework/agentToolRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { prepareCoreBrowserTarget } from "../core/targets/index.js";
import { getCoreTool } from "../core/tools/registry.js";
import { EvalsError } from "../errors.js";
import type { EvalLogger } from "../logger.js";
import { browserSessionFromMetadata, type BrowserSessionInfo } from "./browserSession.js";

export interface AgentToolRuntimeInput {
toolSurface: ToolSurface;
Expand All @@ -13,6 +14,12 @@ export interface AgentToolRuntimeInput {

export interface StartedAgentToolRuntime {
running: ToolStartResult;
/**
* Browser behind the surface, whether the runner provided it (Browserbase
* CDP target) or the tool created it (facade, stagehand_code). Known before
* the agent starts so the session URL can head the task log.
*/
browserSession: BrowserSessionInfo;
/** Closes the tool-owned runtime, then the runner-owned browser target. */
cleanup: () => Promise<void>;
}
Expand Down Expand Up @@ -49,6 +56,10 @@ export async function startAgentToolRuntime(
let cleanupPromise: Promise<void> | undefined;
return {
running,
browserSession: browserSessionFromMetadata(
Comment thread
miguelg719 marked this conversation as resolved.
{ ...running.metadata, ...target.metadata },
input.environment,
),
cleanup: async () => {
cleanupPromise ??= (async () => {
try {
Expand Down
43 changes: 38 additions & 5 deletions packages/evals/framework/benchHarness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { V3, normalizeRubric, type AvailableModel, type TaskSpec } from "stagehand-v3";
import { EvalsError } from "../errors.js";
import { sanitizeErrorMessage } from "@browserbasehq/stagehand-integrations/harness";
import type { EvalLogger } from "../logger.js";
import type { StagehandInitResult } from "../initStagehand.js";
import type { EvalInput } from "../types/evals.js";
Expand All @@ -26,7 +27,13 @@ import {
buildExternalHarnessTaskPlan,
type ExternalHarnessTaskPlan,
} from "./externalHarnessPlan.js";
import {
logBrowserSession,
withBrowserSession,
type BrowserSessionInfo,
} from "./browserSession.js";
import { withHarnessAgentSpan } from "./otel.js";
import { verifierTraceEnabled } from "./verifierTrace.js";
import type { DiscoveredTask, TaskResult } from "./types.js";
import type { BenchMatrixRow, BenchTaskKind, Harness } from "./benchTypes.js";
import { DEFAULT_BENCH_HARNESS } from "./benchTypes.js";
Expand Down Expand Up @@ -69,7 +76,7 @@ export interface BenchHarness {
supportsApi: boolean;
/**
* Tool surfaces this harness can mount for the agent, in display order; the
* first entry is the default when --tool is omitted. An empty list means the
* facade is preferred when --tool is omitted, otherwise the first entry. An empty list means the
* harness does not mount tool surfaces and the planner passes the requested
* surface/profile through unchanged as row metadata (stagehand harness).
*/
Expand Down Expand Up @@ -105,7 +112,14 @@ export interface ExternalHarnessRunInput<TAdapter> {
verifier: ExternalHarnessVerifierConfig;
}

export interface ExternalHarnessDefinition<TAdapter extends { cleanup: () => Promise<void> }> {
/** What every prepared external-harness adapter must expose to the shared lifecycle. */
export interface ExternalHarnessAdapterBase {
cleanup: () => Promise<void>;
/** Browser behind the mounted surface; logged before the agent starts. */
browserSession?: BrowserSessionInfo;
}

export interface ExternalHarnessDefinition<TAdapter extends ExternalHarnessAdapterBase> {
harness: string;
supportedToolSurfaces: ToolSurface[];
defaultModels: AvailableModel[];
Expand All @@ -119,7 +133,7 @@ export interface ExternalHarnessDefinition<TAdapter extends { cleanup: () => Pro
* Define the lifecycle common to external agent harnesses without registering
* it; registry ownership stays explicit so list order remains deterministic.
*/
export function defineExternalHarness<TAdapter extends { cleanup: () => Promise<void> }>(
export function defineExternalHarness<TAdapter extends ExternalHarnessAdapterBase>(
definition: ExternalHarnessDefinition<TAdapter>,
): BenchHarness {
const {
Expand Down Expand Up @@ -148,6 +162,9 @@ export function defineExternalHarness<TAdapter extends { cleanup: () => Promise<
// the adapter and the carrier.
const carrierV3 = buildVerifierCarrierV3(logger);
let toolAdapter: TAdapter | undefined;
let browserSession: BrowserSessionInfo = {
provider: row.config.environment === "BROWSERBASE" ? "browserbase" : "local",
};
try {
toolAdapter = await prepareToolAdapter({
toolSurface: row.config.toolSurface,
Expand All @@ -157,7 +174,9 @@ export function defineExternalHarness<TAdapter extends { cleanup: () => Promise<
logger,
});
const preparedAdapter = toolAdapter;
return await withHarnessAgentSpan(
browserSession = preparedAdapter.browserSession ?? browserSession;
logBrowserSession(logger, browserSession);
const result = await withHarnessAgentSpan(
{
harness,
model: input.modelName,
Expand All @@ -178,6 +197,18 @@ export function defineExternalHarness<TAdapter extends { cleanup: () => Promise<
},
}),
);
return withBrowserSession(result, browserSession);
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
} catch (error) {
return withBrowserSession(
{
_success: false,
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
harnessStatus: "sdk_error",
terminationReason: "sdk_error",
logs: logger.getLogs(),
},
browserSession,
);
} finally {
try {
await toolAdapter?.cleanup();
Expand Down Expand Up @@ -208,7 +239,9 @@ function buildVerifierCarrierV3(logger: EvalLogger): V3 {
disablePino: true,
disableAPI: true,
experimental: true,
verbose: 0,
// verbose 2 surfaces the judge's LLM request/response lines (level 2),
// which verifierAdapter routes to scores/verifier-trace.jsonl.
verbose: verifierTraceEnabled() ? 2 : 0,
});
}

Expand Down
9 changes: 7 additions & 2 deletions packages/evals/framework/benchPlanner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AvailableModel } from "stagehand-v3";
import { EvalsError } from "../errors.js";
import { explicitSnapshotActionsEnabled } from "@browserbasehq/stagehand-integrations/facade";
import { buildOnlineMind2WebTestcases } from "../suites/onlineMind2Web.js";
import { buildHardBenchmarkTestcases } from "../suites/hardbenchmark.js";
import { buildWebTailBenchTestcases } from "../suites/webtailbench.js";
Expand Down Expand Up @@ -366,11 +367,15 @@ function withBenchMetadata(
}

function buildToolMetadata(row: BenchMatrixRow): Partial<Testcase["metadata"]> {
const promptVariant =
row.toolSurface === "stagehand_facade" && explicitSnapshotActionsEnabled()
? { promptVariant: "explicit_snapshot_actions" }
: {};
if (
getBenchHarness(row.harness).supportedToolSurfaces.includes("browse_cli") &&
row.toolSurface === "browse_cli"
) {
return getBrowseCliToolMetadata();
return { ...getBrowseCliToolMetadata(), ...promptVariant };
}
return {};
return promptVariant;
}
89 changes: 89 additions & 0 deletions packages/evals/framework/browserSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { LogLine } from "stagehand-v3";
import type { TaskResult } from "./types.js";

export const BROWSER_SESSION_LOG_CATEGORY = "session";

/** Where the browser behind a run lives, resolved before the agent starts. */
export interface BrowserSessionInfo {
provider: "browserbase" | "local";
sessionId?: string;
sessionUrl?: string;
debugUrl?: string;
}

export function browserbaseSessionUrl(sessionId: string): string {
return `https://www.browserbase.com/sessions/${encodeURIComponent(sessionId)}`;
}

/**
* Read the session fields core tools and runner-provided targets publish on
* their `metadata` (`browserbaseSessionId` / `browserbaseSessionUrl` /
* `browserbaseDebugUrl`). Falls back to the bare provider when a Browserbase
* surface does not report its session id (browse_cli).
*/
export function browserSessionFromMetadata(
metadata: Record<string, unknown> | undefined,
environment: "LOCAL" | "BROWSERBASE",
): BrowserSessionInfo {
if (environment !== "BROWSERBASE") return { provider: "local" };
const rawUrl = readString(metadata?.browserbaseSessionUrl);
const sessionId =
readString(metadata?.browserbaseSessionId) ?? rawUrl?.match(/\/sessions\/([^/?#]+)/u)?.[1];
const sessionUrl = rawUrl ?? (sessionId ? browserbaseSessionUrl(sessionId) : undefined);
const debugUrl = readString(metadata?.browserbaseDebugUrl);
return {
provider: "browserbase",
...(sessionId && { sessionId }),
...(sessionUrl && { sessionUrl }),
...(debugUrl && { debugUrl }),
};
}

export function formatBrowserSessionMessage(info: BrowserSessionInfo): string {
if (info.provider === "local") return "Browser: local";
if (!info.sessionUrl) return "Browser: browserbase (session id not reported by this surface)";
return `Browserbase session: ${info.sessionUrl}`;
}

/** Level-0 lines so the session pointer survives every log filter. */
export function buildBrowserSessionLogLines(info: BrowserSessionInfo): LogLine[] {
const lines: LogLine[] = [
{
category: BROWSER_SESSION_LOG_CATEGORY,
level: 0,
message: formatBrowserSessionMessage(info),
auxiliary: {
provider: { value: info.provider, type: "string" },
...(info.sessionId && { sessionId: { value: info.sessionId, type: "string" } }),
...(info.sessionUrl && { sessionUrl: { value: info.sessionUrl, type: "string" } }),
},
},
];
if (info.debugUrl) {
lines.push({
category: BROWSER_SESSION_LOG_CATEGORY,
level: 0,
message: `Browserbase debugger: ${info.debugUrl}`,
});
}
return lines;
}

export function logBrowserSession(sink: { log(line: LogLine): void }, info: BrowserSessionInfo) {
for (const line of buildBrowserSessionLogLines(info)) sink.log(line);
}

/** Surface the session on the TaskResult row so Braintrust output is filterable. */
export function withBrowserSession(result: TaskResult, info: BrowserSessionInfo): TaskResult {
return {
...result,
browserProvider: info.provider,
...(info.sessionId && { browserbaseSessionId: info.sessionId }),
...(info.sessionUrl && { sessionUrl: result.sessionUrl || info.sessionUrl }),
...(info.debugUrl && { debugUrl: result.debugUrl || info.debugUrl }),
};
}

function readString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value : undefined;
}
Loading
Loading