jev-browse
A real benchmark run on the author's machine (2026-09-25), played at 2.5x: the same agent searches Google Flights by driving browser-harness itself (left) or with one fast_run call (right). This pair, the median of 3 recorded, took 70.4 s / $1.41 against 31.1 s / $0.45 at list price; the benchmark medians (N = 3, 2026-09-24) are 72.8 s / $2.34 against 24.7 s / $0.44.
Fast, typed browser sub-tasks for coding agents. jev-browse adds a few helpers to browser-harness so that Claude Code or Codex can hand off a whole website sub-task in one call: "search Lisbon, set these two filters, open Casa Flora". Each step is decided by TypeSafe Jev, a small model that answers typed questions in a few hundred milliseconds. The agent then gets the outcome, or a typed reason why it stopped.
# inside a browser-harness script
r = fast_run("https://en.wikipedia.org/wiki/Main_Page",
"Open the Wikipedia article on Gödel's incompleteness theorems", run_id="demo-1")
print(r.status, r.reason, r.url) # claimed_done None https://en.wikipedia.org/wiki/G%C3%B6del%27s_...
jev_close(r.target_id)
Why. When a coding agent drives a browser itself, each click costs a model turn that re-reads its whole
context. A five-field form becomes fifteen turns. fast_run does the clicking and typing in one call, with one Jev
request per decision, and hands back to the agent only when it must: a value it doesn't know, a risky click, a
frame or upload it can't handle, or anything sensitive. In the author's runs that made form and navigation tasks
1.6–3.0× faster and 2.1–5.3× cheaper than the agent driving browser-harness alone, with the same pass rate
(details).
It is a port of browser-use/jev-ultrafast (MIT), turned into helpers any agent can call from a browser-harness script.
Quickstart
You need Chrome, browser-harness connected to it, Python 3.11+, uv (or pipx), and a TypeSafe API key from console.typesafe.ai/keys.
uv tool install jev-browse && jev-browse install
That installs the jev-browse command, adds the helpers to browser-harness, and links the skill for Claude Code
and Codex. Then store your key and check the install (below), or let your agent do all of it.
With your coding agent (recommended)
Paste this into Claude Code or Codex:
Install jev-browse from https://github.com/danielnc/jev-browse by following its install.md: install the package
with `uv tool install jev-browse`, run `jev-browse install`, store my TypeSafe API key in the browser-harness
agent-workspace .env (ask me for it; never print it), and run `jev-browse doctor` until it passes. Ask me which
text backend I want (default: my Claude subscription if the claude CLI is installed) and whether to add the
jev-browse pointer to my global agent instructions.
As a Claude Code plugin
In Claude Code:
/plugin marketplace add danielnc/jev-browse
/plugin install jev-browse@jev-browse
/jev-browse:setup
The plugin gives Claude Code the jev-browse skill. /jev-browse:setup installs the package, wires it into
browser-harness, and walks through the key, the text backend, and doctor: the same steps as
install.md.
By hand (about a minute)
uv tool install jev-browse # or: pipx install jev-browse
jev-browse install # adds the helpers to browser-harness and links the skill
ENV=~/.config/browser-harness/agent-workspace/.env
printf 'TYPESAFE_API_KEY=%s\n' '<your key>' >> "$ENV" && chmod 600 "$ENV"
jev-browse doctor # checks everything and prints what is active
browser-harness <<'PY'
r = fast_run("https://en.wikipedia.org/wiki/Main_Page", "Open the Wikipedia article about the Eiffel Tower",
run_id="hello-1")
print(r.status, r.url)
jev_close(r.target_id)
PY
To run from a git checkout instead, see install.md step 1.
Then add the global pointer to your agent's instructions. Without it, agents rarely think to use jev-browse on their own.
How it works
your agent (Claude Code / Codex)
│ writes one browser-harness script: r = fast_run(url, goal, values={...})
▼
browser-harness ──CDP──► Chrome: a new background tab owned by jev-browse
│ │
│ ┌───────────────────────┘
│ ▼
│ loop: snapshot the page (snapshot.js: visible text, controls, fields; sensitive values never read)
│ → one TypeSafe Jev request: next operation? which target? which value? ──► api.typesafe.ai
│ → value missing from the goal? ask the text backend once (optional) ──► Claude / Codex /
│ → safety gates: commit verbs, sensitive fields, frames, uploads, hosts Ollama / any
│ → click / type / select / scroll via CDP OpenAI-compatible
│ until DONE, BLOCKED, or a typed hand-back (confirm_required, in_frame, text_value_unavailable, …)
▼
RunResult(status, reason, url, evidence, trace) + a JEV_BROWSE_RESULT={...} line to verify in the same script
- One decision = one small Jev request (typically a few hundred milliseconds). No screenshots and no large model in the loop.
- Values come from your
values=, from the goal itself (Jev picks among candidates taken from the goal), or, only after a miss, from a text backend. A grounding gate checks the backend's answer before anything is typed. - It never guesses past its limits. Too many options, frames, shadow DOM, canvas, uploads, missing values,
sensitive fields, or a click that would send, pay, delete, or book all hand back with a
Reason. The tab stays open for the agent to finish or resume.
More: docs/architecture.md.
When to use it (and when not)
Use fast_run for |
Use browser-harness directly for |
|---|---|
| Multi-step forms and searches (fill, pick dates, filter, open a result) | Anything visual: charts, layouts, images, "does this look right" |
| Link navigation to a known destination | Iframes, shadow DOM, canvas, file uploads, drag and drop |
"Find and click the X" steps (jev_find / jev_click) |
Sensitive pages (banking, health, credentials) |
| Sub-tasks where you only need the outcome | Pages whose text you should not send to TypeSafe |
When fast_run hands back, the agent continues with the harness on the same tab, or resumes after supplying
what was missing: fast_run(None, goal, target_id=r.target_id, values={...}).
Configuration
Zero config beyond TYPESAFE_API_KEY. Everything else is an environment variable (the harness .env counts) or
an entry in ~/.config/jev-browse/config.toml. The environment wins. The settings you are most likely to change:
Setting (config.toml) |
Environment | Default |
|---|---|---|
text.backend |
JEV_BROWSE_TEXT_BACKEND |
auto: claude if its CLI is installed, else none |
text.fallback |
JEV_BROWSE_TEXT_FALLBACK |
auto: who answers when a local backend fails |
ollama.url, ollama.model |
JEV_BROWSE_OLLAMA_URL, _MODEL |
unset, qwen3:30b-a3b |
openai.base_url, openai.model, openai.api_key_env |
JEV_BROWSE_OPENAI_* |
unset |
jev.model |
JEV_BROWSE_JEV_MODEL |
jev-latest |
run.max_actions, run.timeout_s |
JEV_BROWSE_MAX_ACTIONS, JEV_BROWSE_TIMEOUT_S |
30, 90 s |
safety.allowed_hosts |
JEV_BROWSE_ALLOWED_HOSTS |
all hosts |
Privacy note on
text.fallback. With a local (ollama) or OpenAI-compatible backend, the defaulttext.fallback = "auto"means: if that backend fails its known-answer check, is unreachable, or returns invalid output twice, theclaudeCLI answers instead (when it is installed), so the goal, field labels, and page excerpt go to Anthropic. If you chose a local model to keep page text on your machine, settext.fallback = "none"(JEV_BROWSE_TEXT_FALLBACK=none): the miss then hands back to your agent.doctorprints which fallback is active.
All settings, and common setups (privacy mode, local model, OpenRouter/Groq/Cerebras/Gemini):
docs/configuration.md. jev-browse config shows what is active and where each
value came from.
Text backends
A text backend is asked only when a field's value is implied but not stated ("the capital of France" → Paris).
| Backend | Text goes to | Cost | Trade-off |
|---|---|---|---|
claude (default if installed) |
Anthropic, via your Claude subscription | subscription only; API keys are stripped | a few seconds per miss (CLI start-up, mostly hidden by a pre-started process) |
codex |
OpenAI, via your ChatGPT subscription | subscription only | a few seconds of CLI start-up per miss; not yet benchmarked |
ollama |
your own server (and the fallback's provider if it fails; see text.fallback) |
free per token | as fast as your hardware; guarded by a known-answer canary |
openai |
any OpenAI-compatible endpoint (and the fallback's provider if it fails) | provider prices | unmeasured; bring your own model |
none |
nowhere | free | misses hand back to the agent (privacy mode) |
Personal fields (name, email, phone, address) and sensitive fields are never sent to a text backend. Honest numbers and set-up notes: docs/backends.md.
Safety and privacy
- What leaves your machine. On every decision, visible page text, element labels, non-personal field values,
URL, and title go to TypeSafe (
api.typesafe.ai). After a miss, the goal, field labels, and up to 2,000 characters of page text go to your text backend, or to its fallback when a local backend fails (seetext.fallbackabove). Use it only on pages you are comfortable sending there. Usetext_backend="none"(ortext.backend = "none") for sensitive sites. - Sensitive fields (passwords, one-time codes, card numbers, CVV, IBAN, national IDs) are never typed, never read out of the page, and never sent. Personal fields are reported only as filled or empty.
- Commit gate. Clicks that send, pay, delete, book, or confirm need the goal's explicit authorisation, or your
confirm=[...]in a later script. Otherwise the run hands backconfirm_required. This is a heuristic (verbs plus structural signals such as a confirmation inside a dialog): it lowers the risk of an unwanted click in your signed-in browser. It does not remove it. - Owned tabs. jev-browse works only in background tabs it created (or that your
new_tab()created and you adopted). It never touches, focuses, or closes your other tabs. - Hosts.
safety.allowed_hostsrestricts every page jev-browse opens, adopts, or observes. - No telemetry in jev-browse. browser-harness has its own telemetry, which sends script text and helper-call
arguments to PostHog when enabled. The installer and
doctorwarn if it is on. Opt out withbrowser-harness telemetry disable. - Local data. Run files, traces, and screenshots can contain page text and typed values. They stay in the
harness tmp dir and other gitignored paths, and
make clean-tracesremoves them.
Tell your agent about it
A skill that never triggers delivers nothing. With only the skill installed, the calling agent used jev-browse in
0 of 5 unprompted tasks in the author's runs. After a short pointer in the global instructions it used it in
2 of 5, and the explicit fast_run call shape saved a turn per task. Paste this into ~/.claude/CLAUDE.md,
~/.codex/AGENTS.md, or your project's agent file:
## Browser tasks: jev-browse fast path
For a multi-step website sub-task (search, fill, filter, open a result) where you only need the outcome, call
jev-browse from a browser-harness script. You don't need to load its skill first:
r = fast_run(url, goal, values={...known field values...}, run_id="<unique>")
print(r.status, r.reason, r.detail, r.target_id)
- `claimed_done` is not proof: check the printed JEV_BROWSE_RESULT line, or `js("...", target_id=r.target_id)`,
in the same script, then `jev_close(r.target_id)`.
- On a hand-back (`r.reason`), load the jev-browse skill for what to do next. Resume on the same tab with
`fast_run(None, <same goal>, target_id=r.target_id, values={...})`; never re-run from the URL.
- Never act on `confirm_required` in the same script: decide first (ask me if my request does not clearly cover
it), then resume with `confirm=[...]`.
- Use browser-harness directly for visual judgement, frames, uploads, and sensitive pages. Page text goes to
TypeSafe; pass `text_backend="none"` on sensitive sites.
- Close only tab ids that jev-browse returned to you.
The same snippet is in docs/global-pointer.md.
Use it from any MCP client
Cursor, Claude Desktop, Codex, Windsurf, and other MCP clients can use jev-browse through its MCP server, which runs on stdio. Each tool call goes through browser-harness exactly as a script would, so ownership, config, and hand-backs are unchanged:
uv tool install "jev-browse[mcp]" # the jev-browse command plus the MCP SDK
jev-browse mcp # what your MCP client runs
Tools: fast_run, fast_run_status, jev_open, jev_find, jev_click, jev_check, jev_close, doctor. A
fast_run that outlasts the client's tool timeout returns a run_id to resume with fast_run_status. Config
snippets for Claude Desktop, Cursor, and Codex are in docs/mcp.md.
Limitations
- No iframes, shadow DOM, canvas or visual understanding, file uploads, or pop-up tabs: these hand back.
- Pages with very many options or very large state hand back (
too_many_options,state_too_large). - Decisions are text-only. A page whose meaning is visual will confuse it, and it will usually say so
(
visual_only,low_confidence). - Every decision is a metered TypeSafe request: a few tenths of a cent per task at the time of writing.
- The commit gate and the grounding gate are heuristics. Review what a run did (
r.trace) on anything that matters. - The Codex and OpenAI-compatible backends are unmeasured.
- Tested with browser-harness 0.1.13 on macOS with Chrome. Other platforms should work but have not been benchmarked.
Benchmark
jev-browse was compared with the same coding agent driving browser-harness directly, on public-site navigation,
form, and filter tasks, with every outcome verified by code. In the author's runs it was 1.6–3.0× faster and
2.1–5.3× cheaper, with the same pass rate (N = 3 each), mostly because the agent needs far fewer turns. Your
numbers will differ. Summary: docs/benchmark.md. Reproduce it, or evaluate your own text
backend, with bench/ (docs/benchmarking.md).
Documentation
- install.md: step-by-step install, written for an agent to follow
- docs/architecture.md: the decision loop, modules, and safety model
- docs/configuration.md: every setting
- docs/backends.md: text backends and their trade-offs
- docs/benchmarking.md: running the benchmark and
text_eval - docs/mcp.md: the MCP server and client configuration
- skill/SKILL.md and skill/reference.md: what the agent reads
Contributing
See CONTRIBUTING.md. Development is offline by default: uv sync && make check runs the
linter and more than 400 tests with TypeSafe, the CLIs, and Chrome mocked.
License
MIT, see LICENSE. jev-browse ports code from browser-use/jev-ultrafast (MIT, Copyright (c) 2026 Browser Use); see NOTICE. Ported files carry a header comment naming the upstream file.
Release files for jev-browse 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jev_browse-0.2.0.tar.gz | 98.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jev_browse-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 207.7 kB
Release files / jev_browse-0.2.0.tar.gz
| Download URL | jev_browse-0.2.0.tar.gz |
|---|---|
| Size | 98.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f397344fd57452b94a406704a2ddbe76e6df6a42a00b64920588e6f3b208f8e6
|
|
BLAKE2b-256 checksum How to use checksums |
18c1ea8de8664ca861ff70ccb7cea89cef82303c376c4a85aafa71a8aef04556
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 25, 2026.
Transparency logRelease files / jev_browse-0.2.0-py3-none-any.whl
| Download URL | jev_browse-0.2.0-py3-none-any.whl |
|---|---|
| Size | 109.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bdf7c4e7b75468b9e0908f2db44bf4ab75215ae3c8ffabb193642882e9a1de8d
|
|
BLAKE2b-256 checksum How to use checksums |
e698e38064d2079729466e5321a84ceb7b3b1915502f256718715b67fad37899
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 25, 2026.
Transparency log