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

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:
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:
| Condition | Action | Rationale |
|---|---|---|
is_complete > 0.82 | Terminate with success | Zero-latency exit when the goal is fulfilled |
is_destructive > 0.75 | Halt with safety warning | Prevents purchases, deletions, or credential changes |
confidence >= 0.55 | Fast-path stagehand.act(candidate) | Executes in milliseconds without re-prompting an LLM |
confidence < 0.55 | System 2 stagehand.act(instruction) | Gemini Flash or Claude Sonnet deep reasoning |
| Stagnant page fingerprint (3 steps) | Early exit | Breaks infinite loops using URL + visible-text hash |
Consecutive anti_bot categories | Terminate anti_bot_blocked | A captcha won't clear by clicking at it |
HTTP status >= 400 on navigation | Fail-fast before step 1 | No 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:
| Metric | Typical Duration | What It Measures |
|---|---|---|
| Jev reflex | ~70ms – 150ms | Parallel typed decision (choice + completion + safety) |
| observe() LLM | ~1.5s – 3.5s | Stagehand DOM candidate enumeration round-trip |
| Total/step | ~2.0s – 4.5s | End-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 + Enterto launch a run; pressEscto 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_destructivenoul fires before the click is dispatched. - Honest telemetry: Always report
Jev reflex,observe() LLM, andTotal/stepseparately. 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