feat(evals): add fx bench harness (integrations/fx-sdk) - #2811
feat(evals): add fx bench harness (integrations/fx-sdk)#2811miguelg719 wants to merge 7 commits into
Conversation
|
There was a problem hiding this comment.
5 issues found across 20 files
Confidence score: 2/5
- In
packages/evals/framework/fxRunner.ts, failure states (error/nonzero exit/step-limit) can still be marked successful when structured output is present, which risks downstream consumers treating failed runs as valid results — gateresultText/_successon completed sessions only. - In
packages/integrations/fx-sdk/src/session.ts, clipping stderr beforesanitizeErrorMessage()can let credentials slip through near the truncation boundary, creating a concrete secret-leak path in user-visible errors — sanitize first, then clip (or clip in a redaction-safe way). - In
packages/integrations/fx-sdk/src/session.ts,maxAgentStepscan accept fractional input and floor toFX_MAX_AGENT_STEPS=0, which may cause immediate fx failure or skip intended step-limit behavior — enforce integer validation and reject non-integer values before env assignment. - In
packages/integrations/fx-sdk/src/session.tsandpackages/integrations/fx-sdk/tests/session.test.ts, genericnew Error()for user-facing integration failures and timer-racy polling tests both weaken reliability/diagnostics under failure conditions — switch to the typed integration error and make the polling test synchronization deterministic.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/integrations/fx-sdk/tests/session.test.ts">
<violation number="1" location="packages/integrations/fx-sdk/tests/session.test.ts:295">
P2: These live-observation tests race the polling loop against a 5 ms timer, so CI scheduling can make them flaky. Replace the short wall-clock delay with deterministic synchronization, or use a substantially longer process delay plus an explicit poll barrier before allowing the process to exit.</violation>
</file>
<file name="packages/evals/framework/fxRunner.ts">
<violation number="1" location="packages/evals/framework/fxRunner.ts:104">
P1: When fx returns an output alongside an error, nonzero exit, or step-limit status, this line lets the structured output set `_success` to true. Only expose `finalMessage` as `resultText` for a completed session so failed runs cannot count as successful.</violation>
</file>
<file name="packages/integrations/fx-sdk/src/session.ts">
<violation number="1" location="packages/integrations/fx-sdk/src/session.ts:325">
P2: Fractional `maxAgentSteps` values produce an invalid zero-step budget. `positiveInteger` accepts `0.5`, then `Math.floor` writes `FX_MAX_AGENT_STEPS=0`, which can make fx fail or bypass intended step-limit handling. Clamp after flooring to at least 1 before writing the env value.</violation>
<violation number="2" location="packages/integrations/fx-sdk/src/session.ts:479">
P2: Custom agent: **Exception and error message sanitization**
Use a dedicated typed integration error here instead of `new Error()`. Rule 2 forbids generic `Error` for user-visible failures, even when the message is sanitized.</violation>
<violation number="3" location="packages/integrations/fx-sdk/src/session.ts:616">
P1: Custom agent: **Exception and error message sanitization**
When stderr contains a credential near the 500-character boundary, `clip()` removes part of it before `sanitizeErrorMessage()` runs, so the redactor can miss the token and return its sensitive prefix in `stopReason`. Sanitize the complete stderr before clipping, and apply the same ordering to assistant and stderr log summaries.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Runner as Evals Framework
participant Adapter as FxToolAdapter
participant SDK as Fx-SDK (runFxSession)
participant CLI as fx CLI (Process)
participant FS as File System (~/.fx)
participant MCP as MCP Server (Stagehand)
Note over Runner,MCP: NEW: fx Benchmarking Flow
Runner->>Adapter: prepareToolAdapter()
Adapter->>FS: NEW: Create per-run HOME & workspace
Adapter->>FS: NEW: Write mcp.json (tool config)
Adapter->>FS: NEW: Write settings.json (Wildcard deny built-ins)
Adapter-->>Runner: Prepared adapter (paths, env)
Runner->>SDK: runFxAgent()
SDK->>CLI: NEW: spawn "fx ask --json --auto"
Note right of CLI: Uses EVAL_FX_PATH bin<br/>Passes AI_GATEWAY_API_KEY
activate CLI
loop Live Observation
CLI->>FS: NEW: Append events to sessions/<id>/events.jsonl
SDK->>FS: NEW: Tail events.jsonl
SDK-->>Runner: recordObservation (NormalizedToolCall)
end
CLI->>MCP: Call tool (e.g., mcp_stagehand_run)
MCP-->>CLI: Tool Result
deactivate CLI
CLI-->>SDK: Exit with final JSON output
SDK->>FS: NEW: Read usage-v2.json
FS-->>SDK: Token & cost data
alt Abort / Process Exit
SDK->>CLI: NEW: SIGTERM to process group
opt Grace period expires
SDK->>CLI: NEW: SIGKILL
end
end
SDK-->>Runner: FxSessionResult (Events, Usage, Status)
Runner->>Runner: CHANGED: Map fx trajectory to NormalizedToolCall[]
Runner-->>Runner: Log metrics (fx_total_tokens, etc.)
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const usage = normalizeFxUsage(sessionResult.tokenUsage); | ||
| return { | ||
| raw: sessionResult, | ||
| resultText: sessionResult.finalMessage, |
There was a problem hiding this comment.
P1: When fx returns an output alongside an error, nonzero exit, or step-limit status, this line lets the structured output set _success to true. Only expose finalMessage as resultText for a completed session so failed runs cannot count as successful.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/framework/fxRunner.ts, line 104:
<comment>When fx returns an output alongside an error, nonzero exit, or step-limit status, this line lets the structured output set `_success` to true. Only expose `finalMessage` as `resultText` for a completed session so failed runs cannot count as successful.</comment>
<file context>
@@ -0,0 +1,170 @@
+ const usage = normalizeFxUsage(sessionResult.tokenUsage);
+ return {
+ raw: sessionResult,
+ resultText: sessionResult.finalMessage,
+ transcriptText: buildFxTranscript(sessionResult.events),
+ iterationError: sessionResult.iterationError,
</file context>
| resultText: sessionResult.finalMessage, | |
| resultText: sessionResult.status === "completed" ? sessionResult.finalMessage : "", |
| const stderr = input.stderr?.trim(); | ||
| return { | ||
| status: "sdk_error", | ||
| stopReason: `fx produced no JSON output${stderr ? `: ${clip(stderr, 500)}` : ""}`, |
There was a problem hiding this comment.
P1: Custom agent: Exception and error message sanitization
When stderr contains a credential near the 500-character boundary, clip() removes part of it before sanitizeErrorMessage() runs, so the redactor can miss the token and return its sensitive prefix in stopReason. Sanitize the complete stderr before clipping, and apply the same ordering to assistant and stderr log summaries.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/fx-sdk/src/session.ts, line 616:
<comment>When stderr contains a credential near the 500-character boundary, `clip()` removes part of it before `sanitizeErrorMessage()` runs, so the redactor can miss the token and return its sensitive prefix in `stopReason`. Sanitize the complete stderr before clipping, and apply the same ordering to assistant and stderr log summaries.</comment>
<file context>
@@ -0,0 +1,787 @@
+ const stderr = input.stderr?.trim();
+ return {
+ status: "sdk_error",
+ stopReason: `fx produced no JSON output${stderr ? `: ${clip(stderr, 500)}` : ""}`,
+ };
+ }
</file context>
| pollIntervalMs: 1, | ||
| onToolStep, | ||
| runProcess: async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 5)); |
There was a problem hiding this comment.
P2: These live-observation tests race the polling loop against a 5 ms timer, so CI scheduling can make them flaky. Replace the short wall-clock delay with deterministic synchronization, or use a substantially longer process delay plus an explicit poll barrier before allowing the process to exit.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/fx-sdk/tests/session.test.ts, line 295:
<comment>These live-observation tests race the polling loop against a 5 ms timer, so CI scheduling can make them flaky. Replace the short wall-clock delay with deterministic synchronization, or use a substantially longer process delay plus an explicit poll barrier before allowing the process to exit.</comment>
<file context>
@@ -0,0 +1,474 @@
+ pollIntervalMs: 1,
+ onToolStep,
+ runProcess: async () => {
+ await new Promise((resolve) => setTimeout(resolve, 5));
+ processExited = true;
+ return { stdout: JSON.stringify({ output: "done" }), stderr: "", exitCode: 0 };
</file context>
| HOME: input.home, | ||
| ...(model && { FX_MODEL: model }), | ||
| ...(positiveInteger(input.maxAgentSteps) && { | ||
| FX_MAX_AGENT_STEPS: String(Math.floor(input.maxAgentSteps!)), |
There was a problem hiding this comment.
P2: Fractional maxAgentSteps values produce an invalid zero-step budget. positiveInteger accepts 0.5, then Math.floor writes FX_MAX_AGENT_STEPS=0, which can make fx fail or bypass intended step-limit handling. Clamp after flooring to at least 1 before writing the env value.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/fx-sdk/src/session.ts, line 325:
<comment>Fractional `maxAgentSteps` values produce an invalid zero-step budget. `positiveInteger` accepts `0.5`, then `Math.floor` writes `FX_MAX_AGENT_STEPS=0`, which can make fx fail or bypass intended step-limit handling. Clamp after flooring to at least 1 before writing the env value.</comment>
<file context>
@@ -0,0 +1,787 @@
+ HOME: input.home,
+ ...(model && { FX_MODEL: model }),
+ ...(positiveInteger(input.maxAgentSteps) && {
+ FX_MAX_AGENT_STEPS: String(Math.floor(input.maxAgentSteps!)),
+ }),
+ FX_PERMISSION_MODE: permissionMode,
</file context>
| FX_MAX_AGENT_STEPS: String(Math.floor(input.maxAgentSteps!)), | |
| FX_MAX_AGENT_STEPS: String(Math.max(1, Math.floor(input.maxAgentSteps!))), |
| : undefined; | ||
| let iterationError: unknown; | ||
| if (resolution.status !== "completed") { | ||
| iterationError = new Error(stopReason ?? "fx stopped before a normal result"); |
There was a problem hiding this comment.
P2: Custom agent: Exception and error message sanitization
Use a dedicated typed integration error here instead of new Error(). Rule 2 forbids generic Error for user-visible failures, even when the message is sanitized.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/fx-sdk/src/session.ts, line 479:
<comment>Use a dedicated typed integration error here instead of `new Error()`. Rule 2 forbids generic `Error` for user-visible failures, even when the message is sanitized.</comment>
<file context>
@@ -0,0 +1,787 @@
+ : undefined;
+ let iterationError: unknown;
+ if (resolution.status !== "completed") {
+ iterationError = new Error(stopReason ?? "fx stopped before a normal result");
+ input.logger.warn({
+ category: "fx",
</file context>
b699679 to
6ae0c5b
Compare
7357ebf to
30a7fb8
Compare
6ae0c5b to
368813e
Compare
30a7fb8 to
9162b2f
Compare
…nd runner (phase 1) - New workspace package @browserbasehq/stagehand-integrations-fx-sdk: runFxSession spawns `fx ask --json` with an isolated HOME, tails the session events.jsonl for per-tool-call evidence, and normalizes status/stopReason/tokenUsage. - evals: fxToolAdapter (MCP-only mounts -> ~/.fx/mcp.json + settings.json + workspace .fx.json/AGENTS.md), fxRunner (mirrors codexRunner), harnesses/fxAdapter (tool_step events -> NormalizedToolCall), unit tests. - turbo/ci/vitest wiring for the new package. Registry/planner wiring lands in phase 2.
Define fxHarness with defineExternalHarness and add it to the bench harness registry so --harness fx plans, dry-runs, and executes like claude_code and codex. fxRunner now runs through runExternalHarnessTask (shared prompt, result parsing, normalized harness_* metrics and harnessStatus), and fxToolAdapter resolves surfaces/startup profiles through the shared registry helpers via FX_TOOL_SURFACES. EVAL_FX_MODELS overrides the default model list. Registry-derived guidance tests now include fx.
…ssions, and status - fx-sdk session: emit tool-step observations only from the live events.jsonl tail (no post-exit replay against the final browser state) and return the observed call keys so the trajectory adapter pairs evidence by key - fx-sdk session: rewrite resolveFxStatus precedence so empty stdout, ask.exit_code != 0, and failed/cancelled turn kinds are sdk_error even on OS exit 0; sanitize event summaries/transcripts; signal the fx process group on abort - fxToolAdapter: deny every non-MCP fx built-in (web_search/web_fetch/file tools included), pre-allow every discovered mcp_<server>_<tool> so --auto never adjudicates, pass the runner's HOME plus pnpm/XDG/proxy cache vars to MCP children, set startup_timeout_ms (EVAL_FX_MCP_STARTUP_TIMEOUT_MS), and log instead of swallowing cleanup timeouts while always removing the temp root
…s-group cleanup, sanitized cleanup logs
368813e to
bd1e627
Compare
9162b2f to
09b9b07
Compare
What
Adds
--harness fxto evals, backed by a new private@browserbasehq/stagehand-integrations-fx-sdkpackage. fx (Vercel's terminal agent, v0.0.3) is CLI-only with no SDK.packages/integrations/fx-sdk—runFxSession: spawnsfx ask --json --autowith the prompt on stdin and a per-run$HOME(generated~/.fx/mcp.json+settings.json), tails~/.fx/sessions/<id>/events.jsonllive for per-tool-call steps (fx only emits one JSON at exit), readsusage-v2.jsonfor tokens; step-limit detection via fx's notice text / configuredFX_MAX_AGENT_STEPS; tracked child process groups with SIGTERM→SIGKILL on abort and on process exit/SIGINT/SIGTERM; redaction.packages/evals/framework/fxToolAdapter.ts—via:"mcp"mounts; permissions are a wildcard deny plusmcp_<server>_*allows (real fx builtin tool names —glob_files,grep_files,open_file,file_info,semantic_search,vision, … all denied), MCP child env passes the runner's HOME/pnpm/XDG caches withEVAL_FX_MCP_STARTUP_TIMEOUT_MS(default 120 s); sanitized cleanup logs.packages/evals/framework/harnesses/fxAdapter.ts— events →NormalizedToolCall[].defineExternalHarness. Binary path viaEVAL_FX_PATH.Testing
AI_GATEWAY_API_KEY/FX_AI_GATEWAY_API_KEY) in the runner env. Command when available:EVAL_FX_PATH=<fx> evals run b:webvoyager --harness fx --tool stagehand_facade -l 1 -t 1 -e browserbase.Stacked on the deepagents PR. Implemented with Codex (gpt-5.6) under supervision; two review rounds (Claude +
codex exec review) fixed in-branch.Summary by cubic
Adds an fx eval harness that runs Vercel’s CLI-only fx via a new
@browserbasehq/stagehand-integrations-fx-sdk, enabling apples-to-apples benchmarks against Stagehand tasks. Previously fx was unsupported; now--harness fxplans, dry-runs, and executes with MCP-only tools and normalized trajectories, usage, and status.@browserbasehq/stagehand-integrations-fx-sdk: spawnsfx ask --json --autowith an isolated HOME; tails~/.fx/sessions/<id>/events.jsonllive to emit tool-step observations and observed call keys; readsusage-v2.jsonand normalizes models/usage; resolves status with stricter precedence (empty stdout, non-zero ask exit_code, or failed/cancelled turns → sdk_error); redacts summaries/transcripts; signals and kills the fx process group on abort/exit.fxRunner,fxToolAdapter, andharnesses/fxAdapterregistered viadefineExternalHarness("fx"); supported tool surfacesstagehand_facade(default),playwright_mcp,chrome_devtools_mcp; runs through the shared external runner for prompts, normalizedharness_*metrics, and harness status; trajectory adapter convertstool_stepevents to normalized calls and pairs evidence by call key; model overrides viaEVAL_FX_MODELS(defaultopenai/gpt-5.4-mini).mcp_<server>_<tool>so--autonever adjudicates; MCP children inherit HOME and pnpm/XDG/proxy caches; startup timeout viaEVAL_FX_MCP_STARTUP_TIMEOUT_MS; step-limit detection via fx notices orFX_MAX_AGENT_STEPS; sanitized cleanup logs with enforced temp-root removal.fx; unit tests added for the session layer, runner, tool adapter, and trajectory adapter.fx≥ 0.0.3 and setEVAL_FX_PATH; provideAI_GATEWAY_API_KEYorFX_AI_GATEWAY_API_KEY; Node ≥ 22.18.Written for commit 09b9b07. Summary will update on new commits.