Adnan Ahmad
September 23, 20267 min read

Building System One: A Dual-Process Browser Agent

Traditional browser agents rely on autoregressive LLMs for every single DOM interaction — sending thousands of tokens of HTML to a large language model just to decide which button to click or to verify whether a page has finished loading. Each step costs 3–8 seconds and $0.05–$0.20 in token spend, and most of that reasoning is wasted: the model already "knows" the answer in the first few tokens but must complete the entire sequence before it can act.

To solve this, I built System One — a high-speed, dual-process autonomous browser agent harness that combines sub-150ms typed reflex decisions with Stagehand's self-healing browser automation and Gemini/Claude deep reasoning fallbacks. The name is inspired by Daniel Kahneman's Thinking, Fast and Slow: routine clicks and completion checks are resolved by System 1 (Jev / TypeSafe AI) in ~70–150ms, while complex reasoning is delegated to System 2 (Google Gemini Flash or Claude Sonnet) only when confidence is low.

GitHub Repository: adnanahmaddev/system-one-browser-agent


🎬 Demo Showcase

System One Operator Dashboard Demo Preview

The System One operator dashboard running a multi-step browser task with real-time CDP viewport screencasting, candidate action rankings, and live telemetry.


1. The Dual-Process Architecture

The core insight is that most browser navigation decisions are trivial: "click the Releases link", "the page has loaded", "this is a search results page." These don't need a 175B-parameter model spending 5 seconds to reason about. They need a fast, typed classifier that can answer multiple questions simultaneously in a single round-trip.

System One splits every browser step into two tiers:

Loading diagram…

System 1 (Jev / TypeSafe AI) resolves four typed questions in a single parallel request:

  • target (choice): Which candidate action should be executed next?
  • is_complete (noul): Has the user's objective been achieved?
  • is_destructive (noul): Would this action trigger a payment, deletion, or credential change?
  • page_category (choice): What type of page is this? (search, form, content, anti-bot)

System 2 (Gemini 2.5 Flash / Claude 3.5 Sonnet) is invoked only when Jev's confidence drops below 0.55, or when the task requires complex creative text synthesis (filling forms with generated content, composing messages, etc.).


2. The Decision Matrix & Safety Guardrails

Every step in the agent's execution loop passes through a strict decision matrix with pre-execution safety gates:

ConditionActionRationale
is_complete > 0.82Terminate with successZero-latency exit when the goal is fulfilled
is_destructive > 0.75Halt with safety warningPrevents purchases, deletions, or credential changes
confidence >= 0.55Fast-path stagehand.act(candidate)Executes in milliseconds without re-prompting an LLM
confidence < 0.55System 2 stagehand.act(instruction)Gemini Flash or Claude Sonnet deep reasoning
Stagnant page fingerprint (3 steps)Early exitBreaks infinite loops using URL + visible-text hash
Consecutive anti_bot categoriesTerminate anti_bot_blockedA captcha won't clear by clicking at it
HTTP status >= 400 on navigationFail-fast before step 1No point spending step budget on a 404 page

The destructive action gate is especially important: it fires before the action is dispatched to the browser. Traditional agents check for dangerous actions after the LLM has already decided to click — by then, it's too late if the model hallucinated a confirmation click on a payment form.


3. The Jev Decision Engine

The heart of the speed advantage is the JevDecisionEngine class, which wraps the @typesafe-ai/sdk client. Here's how a single parallel evaluation is structured:

const response = await client.systemOne({
  state: `Page: ${pageSummary}\n\n` +
         `Candidates:\n${candidateDescriptions}`,
  questions: {
    target: choice(
      "Which candidate action should be executed next?",
      candidateChoiceMap
    ),
    is_complete: noul(
      "Has the user's objective been fully achieved?"
    ),
    is_destructive: noul(
      "Would this action trigger a payment, deletion," +
      " or credential change?"
    ),
    page_category: choice(
      "What is the primary category of this screen?",
      { search: "Search results", form: "Input form",
        content: "Content page", anti_bot: "Captcha/challenge" }
    ),
  },
});

The key innovation is that choice and noul questions are resolved simultaneously in a single typed request — not as four separate LLM calls. This is what enables the ~70–150ms latency: Jev doesn't generate text tokens; it classifies pre-defined answer spaces.


4. Fast-Path Execution: Bypassing LLM Generation

When Jev returns high confidence, the selected candidate action object (which already contains a selector and method from stagehand.observe()) is passed directly to Stagehand:

// High confidence — fast path
if (evaluation.confidence >= 0.55 && candidate.selector) {
  await stagehand.act(candidate);
  // No LLM generation! Direct DOM action via selector.
}

This is a critical distinction: the decision to act is made by Jev in ~80ms, and the execution is a direct Playwright selector click — no natural language instruction is sent to Gemini or Claude. The entire decide-and-act cycle completes in under 100ms.


5. The "3-Metric Truth": Honest Telemetry

Many browser agent benchmarks report misleading numbers by comparing a sub-second reflex against an entire multi-step agent workflow. System One avoids this by separating and measuring three distinct phases of every step:

MetricTypical DurationWhat It Measures
Jev reflex~70ms – 150msParallel typed decision (choice + completion + safety)
observe() LLM~1.5s – 3.5sStagehand DOM candidate enumeration round-trip
Total/step~2.0s – 4.5sEnd-to-end wall-clock including DOM settlement and CDP screencast

Jev replaces the decision LLM call, not the observe() call. Every step still pays a Stagehand observe() round-trip to discover actionable elements before Jev is consulted. The UI and CLI report all three metrics separately so you can quote whichever one answers your specific question.


6. Next.js 16 Operator Dashboard

System One includes a purpose-built operator web UI built with Next.js 16 (App Router) and React 19:

npm run ui   # UI on :3000 + agent bridge on :3001

The dashboard provides:

  • Live Viewport Screencasting: Real-time frames streamed over WebSocket directly from Chromium's CDP, with configurable viewport dimensions and device pixel density.
  • Interactive Step Timeline: Step-by-step trace showing candidate rankings, confidence meters, and action statuses.
  • Step Inspector Drawer: Detailed breakdown of Jev answers, probability distributions, and raw telemetry data.
  • Live Telemetry Bar: Instant visibility into Jev reflex latency, observe time, total step duration, and safety guard status.
  • Keyboard Shortcuts: Press Cmd/Ctrl + Enter to launch a run; press Esc to dismiss inspectors.

The WebSocket bridge in src/server.ts binds to 127.0.0.1 and validates the Origin header on every handshake — without this check, any open web page could connect to the agent's port and drive its browser.


7. Programmatic Usage

You can embed the BrowserAgent directly into your own TypeScript applications:

import "dotenv/config";
import { BrowserAgent } from "./src/browserAgent.js";

const agent = new BrowserAgent({
  headless: false,
  confidenceThreshold: 0.55,
  completionThreshold: 0.82,
  destructiveThreshold: 0.75,
  stagnationLimit: 3,
  maxSteps: 10,
  verbose: true,
  fallbackProvider: "gemini",  // or "claude"
});

const result = await agent.run({
  instruction: "Find the latest release version",
  startUrl: "https://github.com/microsoft/playwright",
  extractInstruction: "Extract the release tag name",
});

console.log("Success:", result.success);
console.log("Extracted:", result.extractedData);

Summary & Key Takeaways

  • Separate what's fast from what's hard: Routine browser decisions (which element, is the goal done, is this dangerous) can be classified in ~100ms. Reserve LLM generation for genuinely ambiguous situations.
  • Pre-execution safety gates: Checking for destructive actions after the model decides is too late. The is_destructive noul fires before the click is dispatched.
  • Honest telemetry: Always report Jev reflex, observe() LLM, and Total/step separately. Comparing a reflex to an end-to-end workflow is misleading.
  • Dual-model fallback: Supporting both Gemini Flash and Claude Sonnet (--fallback claude) means the harness adapts to whatever model infrastructure you already have.

The full source code, benchmark suite, and operator UI are open source:

🌐 GitHub: adnanahmaddev/system-one-browser-agent