Skip to main content
fastbrowse

Give your agent a browser.

A browser agent that picks from the controls a page actually has, cites the page for every claim, and keeps credentials, irreversible actions and spending under the control of code.

pypi python license status


Why fastbrowse

  • It picks, it does not invent. Each page is indexed into the controls it has, and Jev, a choice model, picks one. It cannot click something that is not on the page, and there are no selectors to maintain.
  • Answers you can check. Every claim in a complete answer cites a verbatim quote from the page, with a link that scrolls to it. Pass a schema and get validated data back.
  • Code holds the keys. Submit, pay, delete and send stop until you allow them. A secret is typed only on the origin it was declared for, and models only see its name. Step, time and dollar limits are enforced in code.
  • Any browser. A Browser Use Cloud browser by default, local Chrome, or any Chromium browser or Electron app that is already running.
  • One agent, every interface. A CLI, a Python library, a Node SDK with a native binary and no Python, an MCP server, and a skill for coding agents.

fastbrowse shopping a live store, adding a Raspberry Pi Pico 2 W and an in-stock BME280 sensor to the basket, filling in checkout without paying, and answering with every price cited

Recorded on local Chrome against thepihut.com: 37 steps, about 4 minutes and $0.29 in model calls. Playback is sped up with pauses cut. The full-resolution recording is docs/assets/demo.mp4.

Against the hosted Browser Use agent, on 46 tasks with three repeats each, fastbrowse passed 99.2% to 96.3% at $0.0296 a run to $0.3180, and took 18.9s to 15.3s. See How it compares.

Try it

You need uv, which fetches Python 3.13 or newer itself, and model keys:

export TYPESAFE_API_KEY=...     # Jev, the model that picks each action
export OPENROUTER_API_KEY=...   # the LLM that plans, reads and writes
export BROWSER_USE_API_KEY=...  # the cloud browser; or pass --local to use Chrome
uvx fastbrowse "What is the title of the top story right now?" --start https://news.ycombinator.com/

The cloud browser is the default because it passes bot checks a fresh local Chrome fails; it also prints a URL to watch the run live. AI_GATEWAY_API_KEY (Vercel AI Gateway) serves the LLM when OPENROUTER_API_KEY is unset. Jev can also run through OpenRouter (text only), the gateway as a last resort, or another decision host with FASTBROWSE_DECISION_SOURCE. Jev routing covers the order and other hosts.

Keys can live in a .env file in the working directory (.env.example lists every setting), but values named by --secret must be in the process environment. uv tool install fastbrowse keeps the command on your PATH and uv add fastbrowse adds it to a project. On Arch Linux, the AUR package installs fastbrowse and fastbrowse-mcp: paru -S fastbrowse, or makepkg -si in packaging/aur.

To work on fastbrowse itself:

git clone https://github.com/agent-labs-dev/fastbrowse.git && cd fastbrowse
uv sync --all-extras   # builds the Rust core, so it needs cargo (rustup installs the pinned toolchain)
cp .env.example .env   # then fill in the keys, or pass --local
uv run fastbrowse "What is the title of the top story right now?" --start https://news.ycombinator.com/

Steps go to stderr; the status, cost, step count, why a run stopped short, and the answer go to stdout. --json prints the full result: every step, the quotes behind the answer, and cost by component.

Flag Effect
--start URL the first page; worked out from the task when omitted
--context TEXT background the run may use but never treats as the task
--local local Chrome instead of a cloud browser
--cloud force the cloud browser even when FASTBROWSE_PROFILE or FASTBROWSE_HEADED is set
--headed show the local Chrome window (implies --local)
--profile DIR keep the local Chrome profile in DIR, so sign-ins persist (implies --local)
--cloud-profile ID run on a signed-in Browser Use Cloud profile
--fetch read pages over HTTP with no browser, for a task that only reads
--fetch-first read over HTTP, and start a browser only if the task needs one
--cdp-url URL, --cdp-port PORT drive a browser already running, at a ws:// URL or on 127.0.0.1:PORT
--attach with --cdp-url or --cdp-port, drive a window already open instead of opening a tab
--target-match TEXT attach to the first window whose title or URL contains TEXT (implies --attach)
--foreground bring the run's tab to the front instead of working behind the current one
--proxy-country CC browse from that country (uk, de, ...; default us)
--authorize allow submit, pay, delete and send; otherwise the run stops at needs_confirmation
--secret NAME=ENV_VAR[@ORIGIN] let the agent type $ENV_VAR on ORIGIN (https://*.site.com covers its hosts), or on the --start origin; models see NAME only
--bitwarden ITEM use a vault login on the origin its saved URIs match, including its one-time code
--max-steps N, --max-dollars N bound steps and model spend; unlimited by default
--downloads DIR keep downloaded files
--json print the full result
--trace FILE append each model call, read and check to FILE as JSON lines (- for stderr); FASTBROWSE_TRACE does the same for serve and MCP
--record FILE save a captioned MP4 of the run (needs ffmpeg); it shows what the pages showed, so watch it before sharing
--cursor draw the agent's cursor over a visible Chrome with Cua Driver (needs cua-driver, and X11 on Linux)
export SAUCE_PASSWORD=secret_sauce
uv run fastbrowse "Log in as standard_user with the saved password and add the backpack to the cart." \
  --start https://www.saucedemo.com/ --secret password=SAUCE_PASSWORD --authorize

Signed-in sites

Sign in once by hand in a profile of its own, then point runs at it. --headed is only needed for the first run:

google-chrome --user-data-dir="$HOME/.fastbrowse/amazon" https://www.amazon.com/   # sign in, then close Chrome
uv run fastbrowse "Add a UGREEN USB-A to USB-C cable, 2m, to my cart." \
  --start https://www.amazon.com/ --profile ~/.fastbrowse/amazon --headed

On a cloud browser, --cloud-profile prof_1234 runs as a Browser Use Cloud profile someone signed in once. The run inherits its cookies and no model sees a credential.

With the Bitwarden CLI unlocked, --bitwarden ITEM types the vault login. The item's saved URIs must match --start. Models see only username, password and, when the item holds an authenticator key, one_time_code, computed as it is typed:

export BW_SESSION="$(bw unlock --raw)"
uv run fastbrowse "Add a UGREEN USB-A to USB-C cable, 2m, to my cart." \
  --start https://www.amazon.com/ --bitwarden Amazon --profile ~/.fastbrowse/amazon

To change a password, add the replacement as an origin-scoped secret named new_password, refer to it in the task, and pass --authorize. fastbrowse never generates a password or reuses the old one.

Open windows and Electron apps

Any Chromium app that exposes a DevTools port can be driven. Restart the app with --remote-debugging-port (a running app ignores the flag), then attach to its window by title or URL:

open -a "Visual Studio Code" --args --remote-debugging-port=9222 ~/code/my-project   # elsewhere, pass the flag to the binary
uv run fastbrowse "Open the Extensions view and report how many extensions are installed." \
  --cdp-port 9222 --target-match my-project

--attach takes over the first window, skipping DevTools, and --target-match picks one by title or URL. Without --start the run begins on whatever the window shows, and the window stays open afterwards. Popups the window opens join the run.

For a signed-in browser that stays open, start a Chrome with a data directory of its own (Chrome ignores the port on its default one), sign in, and leave it running. Each run opens and closes a background tab in it:

google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.fastbrowse/chrome"
uv run fastbrowse "Report my open orders." --start https://www.amazon.com/ --cdp-port 9222

A run in a visible window works in a tab behind the current one and never raises the window itself. Chrome still brings a window forward when it starts headed, when a page opens a popup, and, with --attach, on a click in the window's front tab.

A DevTools port gives any program on the machine full control of the app and its sessions. Close the app, or restart it without the flag, when you are done.

Models

The LLM defaults to google/gemini-3.8-flash at low reasoning effort, with google/gemini-3.5-flash-lite for proposing a direct address and typing field text.

Setting Effect
FASTBROWSE_LLM_MODEL the model for every purpose
FASTBROWSE_LLM_MODEL_<PURPOSE> the model for one purpose: PLAN, READ, FIELD_TEXT, SHORTCUT, RECOVER, COMPOSE or VERIFY
FASTBROWSE_LLM_REASONING, FASTBROWSE_LLM_REASONING_<PURPOSE> none, low, medium or high; none turns reasoning off for a model such as DeepSeek V4.1 Flash that otherwise reasons at every effort
FASTBROWSE_LLM_PROVIDER openrouter, gateway, openai, anthropic, gemini, groq, together, mistral, deepseek, xai, fireworks, azure, ollama, lmstudio, vllm or custom; each reads its own key, such as OPENAI_API_KEY, and sends it to that provider alone
FASTBROWSE_LLM_BASE_URL, FASTBROWSE_LLM_API_KEY any OpenAI-compatible endpoint, such as a local vLLM at http://127.0.0.1:8000/v1; it needs FASTBROWSE_LLM_MODEL, and never receives the OpenRouter or gateway key
FASTBROWSE_LLM_STRICT=0, FASTBROWSE_LLM_EXTRA_BODY for that endpoint: send schemas in non-strict form, and add a JSON object to every request

LLM providers lists what each preset sends and its default models. Jev still needs its own key. An endpoint that reports no price leaves those calls' cost unknown, so --max-dollars stops the run after the first one.

Results

The exit code is the status. Only complete exits 0; configuration refusals exit 1 and bad syntax exits 2.

Status Exit Meaning
complete 0 every information requirement is backed by a quote, and every action that changes the site is confirmed on the page
unverified 10 it believes it finished but could not back every claim
needs_confirmation 3 stopped before an irreversible action; re-run with --authorize
needs_login 4 a sign-in wall that no --secret covers
blocked 6 a bot check (a CAPTCHA) that did not clear; not a sign-in, so no secret passes it
needs_input 5 a required value or file is missing, or an upload exceeds the configured size limit
stuck 9 recovery ran out without reaching a page state the run had not seen
budget_exceeded 7 a resource limit was reached, or a missing cost prevents enforcing the dollar cap
observation_limit 11 the page or required evidence cannot fit the configured prompt budget
unavailable 8 a model or browser provider stayed unavailable through every retry; the same run later may pass
error 1 a model or browser failure

The JSON result says why a run stopped where a caller can act on it:

  • budget names the limit a budget_exceeded run hit (steps, seconds, dollars, jev_calls or llm_calls) and its value.
  • wall describes a needs_login or blocked page: its kind (password, one_time_code, sign_in or bot_check), origin, the fields it asked for and seen_at_ms. A caller that can read the user's email or texts can answer a one_time_code wall with a secret scoped to that origin and run again.
  • A stuck or unverified run that read evidence still returns a cited answer, starting Answer (unverified).

A cost a provider omits is looked up from its receipt, or bounded from OpenRouter's listed prices. A cost that cannot be bounded is unknown, and a run with a dollar cap then stops with budget_exceeded. A browser command unanswered for 120 seconds returns unavailable. Step and dollar limits do not bound elapsed time: Limits(max_seconds=...) does.

Troubleshooting covers each status: what it means, common causes and the next step.

How it works

The task is planned and the start page opened in parallel; each step indexes the page, Jev picks an operation and target, code gates it and acts; reads keep verbatim quotes, and the answer cites every claim.

The LLM plans, reads and writes. Jev picks each action. Code owns the gates: irreversible actions stop without --authorize, secrets reach models by name only, and supported cookie banners are refused through autoconsent. More in docs/design.md.

The core is Rust, in crates/, and every interface hands its work to it; the wheel carries it as fastbrowse._native (ADR 0004). Against the Python core it replaced, it reached the same result on all 36 fixture tasks with 18x less agent CPU, 54x faster startup and 5.4x less peak memory (measurements).

How it compares

Browser Use agent (hosted) Browser Use Ultrafast (jev-ultrafast) fastbrowse
Choosing an action hosted model and tools Jev picks from indexed controls Jev picks from indexed controls
Returns an answer DONE or BLOCKED an answer with quotes, or why it stopped
Cited answers not graded for citations here no cited-answer result API answers backed by captured quotes
Signing in yes password fields excluded --secret or a Bitwarden vault item; models see names only
Irreversible actions explicit task instructions in our fixture tests no authorization parameter in Agent classifier plus a code-enforced authorization gate
Browser cloud Chrome through Browser Harness; cloud in these evals cloud by default, or local Chrome with --local

The Ultrafast column describes commit 1231850a, the version the eval runner pins.

Benchmarking makes three comparisons:

  • C1, agent against agent: fastbrowse against the hosted Browser Use agent, the figures above, on Browser Use cloud browsers under identical limits with the hosted agent at its defaults. On its own 53-task catalog, fastbrowse passed 99.3% at 17.0s and $0.0268 a run.
  • C2, browser against browser: the same fastbrowse build on its own browser pool and on a Browser Use Cloud browser. In an earlier record (release-0.5.25), not on the published page, the pool passed fewer tasks.
  • C3, fastbrowse on its pool against jev-ultrafast, over 6 tasks: not yet measured.

The benchmark page publishes campaign release-0.7.1 (agent commit 7b8b0e8f, data sha256 7d6ac26f23fa6d354128fa7e4bcc8190125c441f4f136dc39d0e3c35cec5d4e2). Benchmark results gives the sources, cost basis and limitations, and eval results and workflow covers the diagnostic suites.

Embed it

uv add fastbrowse first, then:

import asyncio

from pydantic import BaseModel

from fastbrowse import run_task
from fastbrowse.models import Limits


class Release(BaseModel):
    package: str
    version: str


async def main() -> None:
    result = await run_task(
        "Find the httpx package and report its name and latest released version.",
        start="https://pypi.org/",
        output_schema=Release,
        limits=Limits(max_dollars=0.10),
    )
    print(result.status, result.data, f"${result.cost.known_dollars:.4f}")
    for evidence in result.evidence:
        print(f'  "{evidence.quote}" from {evidence.url}')


asyncio.run(main())

Choosing the browser. With no browser argument, run_task runs local Chrome. browser_api_key= starts a cloud browser, and cloud_extensions=[...] loads up to three of your account's Browser Use Cloud extensions into it. cdp_url= or cdp_port= drives a browser already running: the run opens and closes its own tab and leaves the rest, though cookies the task sets can persist. attach=True, target_match= and foreground=True work as the matching flags do. Passing a cloud key and a CDP address together is an error.

Your own script on the page. connect_cdp() hands you the attached page without the agent, and resolve_cdp_port(port) returns the ws:// URL behind a DevTools port:

from fastbrowse import connect_cdp


async def main() -> None:
    async with connect_cdp(9222, target_match="my-project") as page:
        print(await page.observe())

Access controls that only the Python API offers:

  • allowed_origins=["https://app.example.com"] limits the documents a run may read and control to those exact origins. Navigations, redirects, frames and popups elsewhere are refused before they are sent, so a sign-in that redirects through another origin needs it listed too. It scopes documents, not the network, and a scoped run sends no live frames or screenshots and cannot record.
  • check_access= is an async callback awaited before the browser starts and before every read, navigation and action. If it raises, the run ends with status error (connect_cdp raises BrowserError), naming only the exception's type.

Citations. result.citations holds a Citation per numbered link in result.answer: id, text, requirement_id, url, quote and deep_link. Counts, totals and superlatives cite every record they were derived from. A deep_link is a text fragment URL that scrolls to the quote; it may not resolve on a page that changes or needs a session.

Watching a run.

  • on_event= gets a BrowserEvent with a cloud browser's live-view URL, then a StepEvent per step, carrying the facts that step added. Config(step_frames=True) adds a PNG of each step's page.
  • on_frame= gets JPEG frames of the active tab as they render. The handler runs on your event loop, so it must honour cancellation: one that swallows CancelledError keeps asyncio.run waiting at shutdown.
  • with traced() as events: collects the run's trace in-process, as --trace writes it.

No frame or screenshot is sent while a resolved secret may show on the page.

run_task(jev=...) and run_task(llm=...) take your own decision model or LLM client; see Jev routing and fastbrowse.llm.

Use it from JavaScript

npm install fastbrowse gives a Node.js 20+ program the same agent with no Python on the machine. The SDK has no dependencies; npm also installs a native binary for your platform (@fastbrowse/darwin-arm64, darwin-x64, linux-arm64, linux-x64 or win32-x64). No install script runs and nothing downloads on first use. It reads the same keys from your environment, and finds local Chrome, starts a cloud browser or drives one already running, as the CLI does.

npm install fastbrowse zod
export TYPESAFE_API_KEY=...
export OPENROUTER_API_KEY=...
import { Fastbrowse } from 'fastbrowse';
import { z } from 'zod';

const Release = z.object({ package: z.string(), version: z.string() });

const fb = await Fastbrowse.start({ local: true });
try {
  const result = await fb.run('Find the httpx package and report its name and latest released version.', {
    start: 'https://pypi.org/',
    output: Release,
    limits: { maxDollars: 0.1 },
    onEvent: event => {
      if (event.type === 'step') console.error(event.step.operation, event.step.target);
    },
    signal: AbortSignal.timeout(120_000),
  });
  console.log(result.status, result.answer);
  if (result.status === 'complete') console.log(result.output.package, result.output.version);
  for (const evidence of result.evidence) console.log(`  "${evidence.quote}" from ${evidence.url}`);
} finally {
  await fb.close();
}
  • One instance runs one task at a time; start more instances to run side by side. The process exits with yours.
  • run resolves with the result whatever the status. It rejects when no run finished (RpcError for a refused request, AbortError when signal stopped it, ProcessExitedError when the process is gone), and with OutputValidationError when the data fails your schema.
  • The result is Python's RunResult, with Python field names. output is data validated by your Zod (4.2+), ArkType or Valibot schema, or a plain JSON Schema; data the schema refuses rejects with OutputValidationError. A run fills a flat object of required string, number, integer and boolean fields.

Secrets and the final check stay in your code:

await fb.run('Log in as standard_user with the saved password and add the backpack to the cart.', {
  start: 'https://www.saucedemo.com/',
  authorization: { irreversibleActions: true },
  secrets: {
    refs: [{ name: 'password', origins: ['https://www.saucedemo.com'] }],
    resolve: name => vault.get(name),
  },
  until: url => url.endsWith('/cart.html'),
});

resolve is called each time a value is typed, only on an origin its ref covers. until gets the final address, and anything but true keeps the run from complete. The other options are run_task's in camelCase; the SDK README lists them with the error model.

There is no binary for musl systems such as Alpine; point binaryPath or FASTBROWSE_BINARY at your own. The macOS binaries are ad-hoc signed, not notarized, and the Windows binary is unsigned. Bun and Deno are untested.

Use it from Rust

The fastbrowse crate is run_task in your own process, with the result's types:

[dependencies]
fastbrowse = "0.9"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use fastbrowse::{Limits, RunTask, run_task};

let result = run_task(
    "What is the top story's title?",
    RunTask {
        start: Some("https://news.ycombinator.com/".into()),
        limits: Some(Limits { max_dollars: Some(0.25), ..Limits::default() }),
        ..RunTask::default()
    },
)
.await?;

The keys come from the environment, as the command reads them. cargo install fastbrowse-cli installs the fastbrowse command itself. The other crates (fastbrowse-core, -run and the rest) are published so the fastbrowse crate can be, and make no promise of their own; what is meant to be used is re-exported from it.

Use it from Go

The Go module starts the fastbrowse binary and runs tasks over the same protocol the JavaScript SDK uses:

go get github.com/agent-labs-dev/fastbrowse/go
client, err := fastbrowse.Start(ctx, fastbrowse.Options{})
if err != nil {
	return err
}
defer client.Close(context.Background())
result, err := client.Run(ctx, "What is the top story's title?", fastbrowse.RunOptions{
	Start: "https://news.ycombinator.com/",
})

The first Start downloads the release's binary for the platform into the user cache directory, checked against the SHA-256 the module pins; Options.BinaryPath or FASTBROWSE_BINARY names another. Cancelling ctx stops a run once its browser has closed. RunOptions takes the same options as the SDK: OutputSchema, Secrets, Until, OnEvent and OnFrame. One client runs one task at a time.

Use it from Java or Kotlin

The Java client is dev.agentlabs:fastbrowse on Maven Central, with the binary for each platform in its natives-<platform> jar. Java 17 or newer:

implementation("dev.agentlabs:fastbrowse:0.9.0")
runtimeOnly("dev.agentlabs:fastbrowse:0.9.0:natives-linux-x64")
try (Fastbrowse fastbrowse = Fastbrowse.start()) {
    RunResult result = fastbrowse.run("What is the top story's title?", RunOptions.builder()
            .start("https://news.ycombinator.com/")
            .build()).join();
}

run returns a CompletableFuture, so Kotlin awaits it with kotlinx-coroutines-jdk8, and cancelling the future stops the run. RunOptions takes the same options as the SDK: outputSchema, secrets, until, onEvent and onFrame. One client runs one task at a time.

Use it from Rust

The fastbrowse crate is run_task in your own process, with the result's types:

[dependencies]
fastbrowse = "0.9"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use fastbrowse::{Limits, RunTask, run_task};

let result = run_task(
    "What is the top story's title?",
    RunTask {
        start: Some("https://news.ycombinator.com/".into()),
        limits: Some(Limits { max_dollars: Some(0.25), ..Limits::default() }),
        ..RunTask::default()
    },
)
.await?;

The keys come from the environment, as the command reads them. cargo install fastbrowse-cli installs the fastbrowse command itself. The other crates (fastbrowse-core, -run and the rest) are published so the fastbrowse crate can be, and make no promise of their own; what is meant to be used is re-exported from it.

Use it from Go

The Go module starts the fastbrowse binary and runs tasks over the same protocol the JavaScript SDK uses:

go get github.com/agent-labs-dev/fastbrowse/go
client, err := fastbrowse.Start(ctx, fastbrowse.Options{})
if err != nil {
	return err
}
defer client.Close(context.Background())
result, err := client.Run(ctx, "What is the top story's title?", fastbrowse.RunOptions{
	Start: "https://news.ycombinator.com/",
})

The first Start downloads the release's binary for the platform into the user cache directory, checked against the SHA-256 the module pins; Options.BinaryPath or FASTBROWSE_BINARY names another. Cancelling ctx stops a run once its browser has closed. RunOptions takes the same options as the SDK: OutputSchema, Secrets, Until, OnEvent and OnFrame. One client runs one task at a time.

Use it from Java or Kotlin

The Java client is dev.agentlabs:fastbrowse on Maven Central, with the binary for each platform in its natives-<platform> jar. Java 17 or newer:

implementation("dev.agentlabs:fastbrowse:0.9.0")
runtimeOnly("dev.agentlabs:fastbrowse:0.9.0:natives-linux-x64")
try (Fastbrowse fastbrowse = Fastbrowse.start()) {
    RunResult result = fastbrowse.run("What is the top story's title?", RunOptions.builder()
            .start("https://news.ycombinator.com/")
            .build()).join();
}

run returns a CompletableFuture, so Kotlin awaits it with kotlinx-coroutines-jdk8, and cancelling the future stops the run. RunOptions takes the same options as the SDK: outputSchema, secrets, until, onEvent and onFrame. One client runs one task at a time.

Use it from .NET

The .NET client is Fastbrowse on NuGet, carrying the binary for each platform. .NET 10 or newer:

dotnet add package Fastbrowse
await using var fastbrowse = await FastbrowseClient.StartAsync();
RunResult result = await fastbrowse.RunAsync("What is the top story's title?",
    new RunOptions { Start = "https://news.ycombinator.com/" });

Cancelling the CancellationToken stops a run, and RunAsync throws once its browser has closed. RunOptions takes the same options as the SDK: OutputSchema, Secrets with ResolveSecret, Until, OnEvent and OnFrame. One client runs one task at a time.

Use it from Rust

The fastbrowse crate is run_task in your own process, with the result's types:

[dependencies]
fastbrowse = "0.9"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use fastbrowse::{Limits, RunTask, run_task};

let result = run_task(
    "What is the top story's title?",
    RunTask {
        start: Some("https://news.ycombinator.com/".into()),
        limits: Some(Limits { max_dollars: Some(0.25), ..Limits::default() }),
        ..RunTask::default()
    },
)
.await?;

The keys come from the environment, as the command reads them. cargo install fastbrowse-cli installs the fastbrowse command itself. The other crates (fastbrowse-core, -run and the rest) are published so the fastbrowse crate can be, and make no promise of their own; what is meant to be used is re-exported from it.

Use it from Go

The Go module starts the fastbrowse binary and runs tasks over the same protocol the JavaScript SDK uses:

go get github.com/agent-labs-dev/fastbrowse/go
client, err := fastbrowse.Start(ctx, fastbrowse.Options{})
if err != nil {
	return err
}
defer client.Close(context.Background())
result, err := client.Run(ctx, "What is the top story's title?", fastbrowse.RunOptions{
	Start: "https://news.ycombinator.com/",
})

The first Start downloads the release's binary for the platform into the user cache directory, checked against the SHA-256 the module pins; Options.BinaryPath or FASTBROWSE_BINARY names another. Cancelling ctx stops a run once its browser has closed. RunOptions takes the same options as the SDK: OutputSchema, Secrets, Until, OnEvent and OnFrame. One client runs one task at a time.

Use it from Java or Kotlin

The Java client is dev.agentlabs:fastbrowse on Maven Central, with the binary for each platform in its natives-<platform> jar. Java 17 or newer:

implementation("dev.agentlabs:fastbrowse:0.9.0")
runtimeOnly("dev.agentlabs:fastbrowse:0.9.0:natives-linux-x64")
try (Fastbrowse fastbrowse = Fastbrowse.start()) {
    RunResult result = fastbrowse.run("What is the top story's title?", RunOptions.builder()
            .start("https://news.ycombinator.com/")
            .build()).join();
}

run returns a CompletableFuture, so Kotlin awaits it with kotlinx-coroutines-jdk8, and cancelling the future stops the run. RunOptions takes the same options as the SDK: outputSchema, secrets, until, onEvent and onFrame. One client runs one task at a time.

Use it from .NET

The .NET client is Fastbrowse on NuGet, carrying the binary for each platform. .NET 10 or newer:

dotnet add package Fastbrowse
await using var fastbrowse = await FastbrowseClient.StartAsync();
RunResult result = await fastbrowse.RunAsync("What is the top story's title?",
    new RunOptions { Start = "https://news.ycombinator.com/" });

Cancelling the CancellationToken stops a run, and RunAsync throws once its browser has closed. RunOptions takes the same options as the SDK: OutputSchema, Secrets with ResolveSecret, Until, OnEvent and OnFrame. One client runs one task at a time.

Use fastbrowse with your agent

Install the fastbrowse skill to delegate website tasks from any agent that supports Agent Skills, such as Codex or Claude Code. It uses the MCP server when connected, or the CLI, and keeps the task's citations, status, authorization and budget. Nebula also runs fastbrowse in its agent harness, with Bitwarden or 1Password logins scoped to authorized websites.

Use it from an MCP client

fastbrowse-mcp serves one browse tool over MCP to Claude Code, Claude Desktop, Cursor or any MCP client. It returns the answer with numbered citation links, typed fields if asked for, the quotes behind them, the status and what to do next, and reports progress every step.

claude mcp add fastbrowse -e TYPESAFE_API_KEY=... -e OPENROUTER_API_KEY=... -e BROWSER_USE_API_KEY=... \
  -- uvx --from fastbrowse fastbrowse-mcp --max-dollars 0.25

For a client configured by JSON, such as Claude Desktop:

{
  "mcpServers": {
    "fastbrowse": {
      "command": "uvx",
      "args": ["--from", "fastbrowse", "fastbrowse-mcp"],
      "env": { "TYPESAFE_API_KEY": "...", "OPENROUTER_API_KEY": "...", "BROWSER_USE_API_KEY": "..." }
    }
  }
}

The server's flags set what a calling model may do; a call can ask for less, never more.

Flag Effect
--local, --headed, --profile DIR, --foreground, --downloads DIR, --cursor as for the CLI, fixed for every call
--cloud-profile ID every call runs as that cloud profile
--allow-authorize let a call pass authorize; without it irreversible actions always stop at needs_confirmation
--secret NAME=ENV_VAR@ORIGIN typed when a call's start page is on ORIGIN; the model sees NAME only
--bitwarden ITEM a vault login a call may name in bitwarden
--max-steps N, --max-dollars N, --max-seconds N ceilings per call; unlimited by default
--max-concurrent N runs at once, default 1; more calls wait
--transport http, --host, --port streamable HTTP at /mcp instead of stdio

Over HTTP, set FASTBROWSE_MCP_TOKEN and every request but /healthz needs Authorization: Bearer <token>; the server refuses to bind beyond loopback without one. A run takes seconds to minutes, so raise the client's tool timeout if it has one (MCP_TOOL_TIMEOUT in Claude Code).

Safety model

  • Irreversible actions. Jev judges each click, Enter press and dialog acceptance. Without authorization, a confident irreversible action stops at needs_confirmation, and an uncertain one goes to recovery. This is a classifier, not a guarantee: a page can word a harmful control to look harmless.
  • Secrets. Models see names only. A value is resolved at the moment it is typed, only on its declared origin, and redacted from everything the run returns. A password field is typed only from a stored secret.
  • Page content is data. Reader and verifier prompts say so, and completion is judged against quotes and page state, not the model's say-so.

Evals and development

Eval commands cover running suites, publishing results and regenerating evidence; the compact results feed is generated from published rows. See CONTRIBUTING.md for setup and checks, docs/design.md for the browser layer, and docs/jev.md for the Jev assumptions checked against Typesafe's documentation.

Changelog

CHANGELOG.md records each release, and every GitHub release publishes its entry.

License

MIT, copyright Agent Labs. Adapted third-party code is credited in NOTICE.

Metadata

Release files for fastbrowse 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastbrowse 0.9.0
File Size Uploaded Provenance Actions
fastbrowse-0.9.0.tar.gz 3.3 MB With provenance Details for fastbrowse-0.9.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for fastbrowse 0.9.0
File Actions
fastbrowse-0.9.0-cp313-abi3-win_amd64.whl CPython 3.13 abi3 Windows x86-64 Details for fastbrowse-0.9.0-cp313-abi3-win_amd64.whl
fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_x86_64.whl CPython 3.13 abi3 Linux glibc 2.34+ x86-64 Details for fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_x86_64.whl
fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_aarch64.whl CPython 3.13 abi3 Linux glibc 2.34+ ARM64 Details for fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_aarch64.whl
fastbrowse-0.9.0-cp313-abi3-macosx_11_0_arm64.whl CPython 3.13 abi3 macOS 11.0+ ARM64 Details for fastbrowse-0.9.0-cp313-abi3-macosx_11_0_arm64.whl
fastbrowse-0.9.0-cp313-abi3-macosx_10_12_x86_64.whl CPython 3.13 abi3 macOS 10.12+ x86-64 Details for fastbrowse-0.9.0-cp313-abi3-macosx_10_12_x86_64.whl

Total release size: 41.1 MB

Release files / fastbrowse-0.9.0.tar.gz

Download URL fastbrowse-0.9.0.tar.gz
Size 3.3 MB
Tags Source
SHA-256 checksum
How to use checksums
e4ad6f7074929778bb60895fe819f6f789b684a3e0ecb94a82fb6505fd06a08a
BLAKE2b-256 checksum
How to use checksums
d4149dc50e0590a18f31cfa5b87cceda4f973a24a0edc96772032a0137563c88
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / fastbrowse-0.9.0-cp313-abi3-win_amd64.whl

Download URL fastbrowse-0.9.0-cp313-abi3-win_amd64.whl
Size 7.5 MB
Tags CPython 3.13 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
9e9da4a97ce742ed24e0e40a3ed5847a06b132eb0198b7f3f9b78ff68cc8bf87
BLAKE2b-256 checksum
How to use checksums
cb256498b1a3df0f46955c0c0ce37b3eb69cbf7906ee765ca02a7a5c805169b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_x86_64.whl

Download URL fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_x86_64.whl
Size 8.3 MB
Tags CPython 3.13 Linux glibc 2.34+ x86-64 abi3
SHA-256 checksum
How to use checksums
530593008b64dac6e291341b15e1866c45e61428cf13cb77fd794202570d7c65
BLAKE2b-256 checksum
How to use checksums
09af66b9b09e05538e0f4e3bf7be0d762625f35f6651344287f38e0774cf7e1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_aarch64.whl

Download URL fastbrowse-0.9.0-cp313-abi3-manylinux_2_34_aarch64.whl
Size 7.9 MB
Tags CPython 3.13 Linux glibc 2.34+ ARM64 abi3
SHA-256 checksum
How to use checksums
90c92d82af094d8a1f95199aaa278bc8e2bc4bef621a894f9dbeadc384e40471
BLAKE2b-256 checksum
How to use checksums
cdeca5171d7546bfe2e952668f13da237d2b60c33500f6dcd52717265cc6124a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / fastbrowse-0.9.0-cp313-abi3-macosx_11_0_arm64.whl

Download URL fastbrowse-0.9.0-cp313-abi3-macosx_11_0_arm64.whl
Size 6.8 MB
Tags CPython 3.13 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
3173a9e30ba411dc4ca34ec6a546a1d5a9812f0044fab075fccc91e1eb7e0936
BLAKE2b-256 checksum
How to use checksums
8a35bceb35a63f2aea1fd69b6a814f97409922ac87bed25ef5a6a9eb30d95e2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / fastbrowse-0.9.0-cp313-abi3-macosx_10_12_x86_64.whl

Download URL fastbrowse-0.9.0-cp313-abi3-macosx_10_12_x86_64.whl
Size 7.3 MB
Tags CPython 3.13 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
84bda1dbaaaddc88f93728777a3e9c834606bb0fcf7f0d9a88d5506289d7b949
BLAKE2b-256 checksum
How to use checksums
acdd27093e500b1757ef6aad29f867e07783b298d31edaa8d10a6d0ee87ee7b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for fastbrowse 0.9.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for fastbrowse-0.9.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.9.0 This release

6 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page