Skip to main content

Jev Agent Browser

A browser execution layer for agents: TypeSafe's Jev chooses individual operations, Camofox executes real mouse/keyboard input, and your agent handles planning and pauses. Use the Python library, CLI, local MCP server, or native Hermes plugin. Claude and Codex can supervise it through MCP; their models are not bundled here.

Unofficial: not affiliated with or endorsed by TypeSafe, OpenRouter, Camofox (jo-inc) or Browser Use; their names are their owners' trademarks.

Adapted from browser-use/jev-ultrafast (MIT). Requires Python 3.11+, a running Camofox service, and your own OpenRouter key with access to Jev's decisions endpoint. A proxy is optional. Requests cost money at your provider's rates; the package includes no model credits. Read SECURITY.md before browsing private or authenticated pages.

Codex or Claude supplies the goal and checks the result; Jev chooses observed page actions and Camofox executes them. The standalone interfaces work without Hermes. See agent setup for routing instructions and Camofox operations for supported versions and upgrades.

The repository is named jev-agent-browser; the Python distribution and command remain jev-browser, and the import/plugin name remains jev_browser.

Install and try it

Prerequisites: Python 3.11+, Docker with Compose to run the Camofox server (unless you already have one), and an OpenRouter API key with access to Jev.

git clone https://github.com/harmanbassi/jev-agent-browser.git
cd jev-agent-browser
python3 -m venv .venv
source .venv/bin/activate
python -m pip install '.[mcp]'
cp .env.example .env
chmod 600 .env

Edit .env: supply your OpenRouter key and generate a long random browser access key (for example, python -c 'import secrets; print(secrets.token_hex(32))'). Set both browser-key entries to that same value if using Hermes. Keep .env private. Quote values containing shell characters such as spaces, # or $ before sourcing the file. If changing the published browser port, update CAMOFOX_URL to match it as well. On Windows use .venv\Scripts\Activate.ps1 and set the environment through your shell or agent settings. MCP is optional: pip install . installs the core/CLI.

If you already have Camofox, set CAMOFOX_URL and its authentication key and keep that service. Otherwise build the pinned Camofox 1.18.1 image plus its dependency security updates with Docker Compose (the first build needs npm registry access):

docker compose --env-file .env -f examples/compose.yaml up -d --build --wait
# Export the file you just edited into this shell (review it before sourcing).
set -a
. ./.env
set +a
jev-browser doctor
jev-browser doctor --probe
jev-browser run https://en.wikipedia.org/wiki/Main_Page "Find and open the article about Gödel's incompleteness theorems"

doctor checks configuration and health without a paid model call. --probe opens a throwaway example.com tab and tests the required evaluate route; it can use proxy bandwidth. The run command returns JSON and closes its isolated session. Exit codes: 0 done, 1 task/diagnostic error, 2 configuration/usage error, 3 paused, 130 interrupted. CLI tasks are one-shot; use MCP or Python to resume a session. If Jev returns 401/403, check model access and your key; 429 indicates provider limits. The alpha decisions API can change independently of this project. For browser authentication failures, cleanup warnings and missing tools, see troubleshooting. doctor --probe verifies browser control and cleanup; a successful public /health check alone cannot verify a key.

CLI reference

Command Options
jev-browser run URL GOAL --max-steps N (default 25, at most 100), --max-model-calls N (default 100, at most 500), --timeout SECONDS (default 300, at most 900), --vision {off,auto,always}, --require-visual / --no-require-visual. Prints one JSON result and closes its session.
jev-browser doctor --probe opens and closes an example.com tab to check /evaluate; --vision checks masked capture without uploading an image; --vision-model-probe makes one paid image request.
jev-browser mcp Serves the MCP tools over stdio (requires the mcp extra).

Claude Code, Codex and other MCP clients

Install the mcp extra above. Use the absolute path to the installed jev-browser executable (command -v jev-browser; Windows: where jev-browser). Export the client environment before launching your agent, or configure its secret injection. Nothing auto-loads .env from an agent's working directory.

Claude Code (official MCP guide):

claude mcp add --transport stdio jev-browser -- /absolute/path/.venv/bin/jev-browser mcp

Codex (official MCP guide): add this to ~/.codex/config.toml and launch Codex from the configured environment:

[mcp_servers.jev-browser]
command = "/absolute/path/.venv/bin/jev-browser"
args = ["mcp"]
env_vars = ["JEV_OPENROUTER_KEY", "OPENROUTER_API_KEY", "CAMOFOX_URL", "CAMOFOX_ACCESS_KEY", "CAMOFOX_API_KEY", "JEV_ALLOWED_HOSTS", "JEV_ALLOW_PRIVATE_URLS", "JEV_MODEL", "JEV_TEXT_MODEL", "JEV_URL", "JEV_TEXT_URL", "JEV_VISION_ENABLED", "JEV_VISION_MODE", "JEV_VISION_MODEL", "JEV_VISION_MAX_CALLS", "JEV_VISION_BOX"]
tool_timeout_sec = 360

A generic MCP client can launch the same command with args: ["mcp"] and an env map containing its keys and browser URL. Use a separate server process per agent/owner. Only stdio transport is supported; no public MCP listener is opened. Configure the client's tool timeout above your chosen task timeout (default 300s). Installing the package alone does not register it in an agent's tool list. Check the client's active MCP tools and credentials after starting a new session. The MCP interface opens its own isolated identities; it does not adopt a live Hermes tab.

Tool flow:

  1. browser_open(url) returns an isolated session_id.
  2. browser_task(session_id, goal) runs or continues a task.
  3. On paused, read reason, fallback, text and elements. Either give browser_task a clearer goal, or do the step yourself: browser_snapshot returns the page text and element ids, and browser_act(session_id, action, element_id?, value?) clicks, types, selects, presses a key (Enter, Tab, Escape, arrows...) or scrolls on those ids, then browser_task continues the same session. sensitive_action (for example a phone field) is never retried by Jev; enter such values with browser_act only when the user supplied them and approved the step. Once filled, the field shows [filled] and Jev continues without pausing on it again. See SECURITY.md for this path's limits.
  4. browser_inspect retrieves the last result/running state; browser_cancel requests cancellation. browser_close destroys the owned context and attempts saved-state removal (legacy authentication failures return a warning). If busy, retry close after cancellation completes.

Sessions are process-local, capped at 16, and expire after 15 idle minutes when next accessed or a new session is opened. Server shutdown attempts cleanup.

Task results

Every interface (Python, CLI, MCP, Hermes) returns these result fields, except where a row names the interfaces:

Field Meaning
status done, paused (with reason) or error
url, title, text The last page read, when it passed the URL policy
elements On paused: the observed controls Jev could act on
confidence, target_confidence, detail On low_confidence: the operation and target confidence below the floor; detail when the goal check contradicted DONE
field On missing_value: the label of the field the goal gives no value for
fallback On paused (Python, CLI, MCP): what the supervising agent should do next. It names the MCP tools; Python has the same Browser.snapshot/Browser.act. A CLI run is one-shot, so rerun with a clearer goal or use MCP/Python to act on the same session
next, success Hermes, instead of fallback: guidance for this status, and whether it was not an error
session_id Python, CLI, MCP: the session the task ran in (already closed after a CLI run)
steps Executed actions: action (the control's label), kind (click, fill, select, scroll, wait, or recover for a reopened tab), text (the value Jev typed, else null), from_url (the page they ran on), url_after (where they led, once that page passed the URL policy), their confidences and page_changed. effect marks a click the browser abandoned after sending it (taken if the page moved, else uncertain; an identical uncertain click is not repeated: action_uncertain pause); download names a file it started, renamed download_failed if that download later failed (the click may then be repeated); tab_lost marks the action during which the browser destroyed the tab
jev_calls, model_requests Decisions made; model HTTP requests including retries/helpers
models The decision models that answered, as reported by the provider
downloads Files that clicks started downloading; a click that started one is not repeated (download_started pause)
recoveries Tabs the browser destroyed during a read that were reopened on the latest page (at most 2), also listed in steps. A tab lost during an action pauses as tab_lost instead, because the action may have taken effect
usage Reported input_tokens/output_tokens (and cost where the provider reports it) for the task, totalled and by_model
vision With vision allowed: image calls, captures, uploaded_bytes, escalations, verification
completion_probability The last decision's goal check: probability the goal is already met
done_by goal_check when that check (at least 0.9), not the operation choice, finished the task
elapsed_ms Task duration in milliseconds
warnings CLI: browser cleanup warnings from closing the session
Pause reason Meaning
step_budget max_steps actions ran; continue with a fresh budget
model_budget max_model_calls model HTTP requests were used
timeout The task deadline passed
cancelled The task was cancelled (browser_cancel, Browser.cancel, or an interrupted CLI run)
no_progress The last three actions changed nothing, or were the same action
need_guidance Jev asked for guidance instead of choosing an action
blocked Jev judged the goal blocked on this page
low_confidence The chosen operation or target was below its confidence floor
sensitive_action The next step is a sign-in, payment, destructive or personal-data control Jev never acts on
missing_value The goal does not supply a value for field
action_uncertain The browser gave up on the last input after sending it and the page has not changed; it may have taken effect
download_started This click already started a download; it is not repeated
tab_lost The browser destroyed the tab during an action, which may have taken effect
vision_budget, vision_capture_budget The image request or capture budget is used up
vision_completion_unverified A require_visual task's image did not establish the goal
vision_capture_unavailable, vision_capture_failed, vision_invalid_observation, vision_url_blocked, vision_expired_observation, vision_target_ambiguous Visual evidence could not be captured or used; see visual browsing

Errors carry a readable message in reason instead. vision_disabled and vision_dependency_missing are errors returned before a task starts.

Every decision also asks Jev, in the same request, whether the goal is already met. A confident yes (at least 0.9) finishes the task where the operation head leaves room for it: the head gave DONE at least 0.15, or its chosen action is below its floor. It never overrides NEED_GUIDANCE, BLOCKED or a confident action. A confident no (at most 0.2) overrules a DONE choice; the next-best operation is scored on its own probability and must clear the usual floor, or the task pauses as low_confidence with a detail. Goals that ask for an action (open, submit, send) are not met by a page that only offers it.

Duplicate links (the same label and the same real href, such as a header and footer "Downloads") share their probability before the target confidence floor applies, and Jev acts on the copy it preferred. Script-handled links (#..., javascript:) and controls without a shared destination, such as an "Add to cart" button on every row, are never pooled.

Optional visual browsing

Jev 1.14 can use images alongside page text. Vision is off by default and experimental. Install python -m pip install '.[mcp,vision]', rebuild the browser image from this checkout, then explicitly permit masked image uploads with JEV_VISION_ENABLED=1 in the environment of your Python/CLI/MCP process or Hermes profile. Your OpenRouter key must have access to typesafe/jev-1.14. The image model is pinned independently of the text model.

For appearance, pictures, colors or layout goals, your agent must call browser_task(..., require_visual=true) (Python: require_visual=True) or use jev-browser run URL GOAL --require-visual. This starts with visual evidence and requires a separate visual completion check. A resumed MCP/Python/Hermes session preserves that requirement when omitted; explicitly pass false to clear it. vision="always" uses images from the start; auto escalates on low confidence, blocked decisions or no progress. Automatic escalation cannot infer all visual intent from a natural-language goal: use require_visual explicitly. Task options cannot enable uploads if the operator has disabled them.

jev-browser doctor --vision checks compatible masked capture on a throwaway page without uploading an image. --vision-model-probe explicitly makes a paid image request. The regular doctor remains read-only and makes no paid requests. Old Camofox servers continue to support text browsing; visual tasks pause when the masked endpoint is unavailable. See visual browsing for privacy boundaries, budgets, installation and failure reasons.

Hermes

Install the core into Hermes' Python environment, then link this checkout's plugin directory (the package stays import-light at plugin discovery):

/path/to/hermes/venv/bin/python -m pip install /absolute/path/to/jev-agent-browser
ln -s /absolute/path/to/jev-agent-browser/jev_browser ~/.hermes/plugins/jev_browser

Merge these settings into Hermes' existing config.yaml; preserve other enabled plugins and toolsets:

plugins:
  enabled: [jev_browser]
# Add browser_task beside browser in each platform_toolsets entry you use.
# Where the Hermes version supports explicit browser selection:
browser:
  cloud_provider: camofox

Set CAMOFOX_URL, OPENROUTER_API_KEY (or JEV_OPENROUTER_KEY) in Hermes' runtime secrets/environment. On Camofox with global access-key authentication, set Hermes' CAMOFOX_API_KEY to the server's CAMOFOX_ACCESS_KEY, since its native browser adapter sends that key as a Bearer token. Restart Hermes after installing/enabling its plugin. This requires a Hermes version providing ctx.register_tool and the tools.browser_camofox/tools.browser_tool APIs used by this plugin.

The tool remains browser_task(goal, url?, max_steps?, max_model_calls?, timeout?, vision?, require_visual?). It uses Hermes' existing tab, cookie identity, auth headers, URL checks and website policy. Omitting url continues the same tab; browser_* tools remain its manual fallback. Existing proxy/persistence settings stay on the existing Camofox server. On Hermes versions with profile secret scopes, model keys, endpoints and settings are resolved within the active profile; missing credentials never fall back to a different profile's environment. Older Hermes versions retain environment loading. For a personal Hermes profile that should keep its open tab across turns, merge this optional native setting as well:

browser:
  camofox:
    managed_persistence: true
    adopt_existing_tab: true

Hermes can clear its local tab reference when a turn finishes. Adoption restores an open managed tab, preferring its session label and otherwise the latest tab in that cookie identity. Use a separate identity/profile per independent owner; this setting is intended for a personal browser with coordinated use. Browser restarts and idle reaping can still remove tabs, requiring a fresh URL. Saved cookies and live page state are different forms of persistence.

The native Hermes adapter is tested with Hermes v0.21.6; other Hermes versions can also use the standalone MCP/CLI interfaces.

The plugin supplies Jev-first browsing instructions on Hermes versions supporting plugin prompt sections. This covers ordinary requests and follow-ups, manual fallbacks, and discovery when Hermes defers plugin tools. Enabled does not mean directly visible: check the model's actual tool list, not only plugin registration. See Hermes routing and acceptance tests, including an optional, version-tested host patch to keep browser_task directly visible.

Python

from jev_browser.client import Browser

with Browser() as browser:
    result = browser.start("https://example.com", "Read the page title")
    if result["status"] == "paused":
        result = browser.run(result["session_id"], "Read the visible heading")
    print(result)

browser.snapshot(session_id) and browser.act(session_id, element_id, action, value=None) are the MCP browser_snapshot/browser_act supervisor tools. The other MCP tools map the same way:

browser = Browser(max_sessions=16, idle_seconds=900)  # the defaults
sid = browser.open("https://example.com")["session_id"]  # no model call yet
result = browser.run(sid, "Read the page title")
browser.inspect(sid)   # last result, or {"status": "running"}
browser.cancel(sid)    # cooperative; a running task pauses as cancelled
browser.close(sid)     # destroys the session's identity and saved state
browser.shutdown()     # closes every session (the context manager does this)

The lower-level Camofox, Page, and run_task interfaces remain available for custom drivers. run_task defaults to the public-page URL policy. Supply an explicit URLPolicy(allow_private=True) only when you intend to send private page data to the model. Pass the same policy to Camofox.open(..., guard=policy). Low-level cf.close() closes one tab; cf.close_session() destroys the entire identity and saved state, so use it only for identities you own.

Optional proxy and configuration

Leave PROXY_* empty for direct browser connections. To use a proxy, set those variables in .env and recreate your Compose service. Proxy credentials are passed only to Camofox, never to Jev. The Python package doesn't configure or bypass browser-side proxies. Connecting to an existing proxied service preserves its routing. Browser proxies and standard HTTP(S)_PROXY/NO_PROXY variables for Python's HTTP clients are separate; ensure control requests can reach loopback.

The Compose sample sets Camofox's Node heap budget to 512 MiB through CAMOFOX_HEAP_MB / MAX_OLD_SPACE_SIZE. The image's 128 MiB default exhausted its heap during a longer interactive acceptance run and restarted the service, losing live tabs. This is the Node heap budget, not a total container memory limit: Firefox and other allocations need additional memory. Existing Camofox operators should check their service's heap budget separately; the Python package cannot change it. A browser restart can lose tabs while saved cookies survive, so supervisors must reopen the last verified URL when no page remains.

For local/staging sites, use a separate direct Camofox service and explicitly permit private pages; a globally proxied browser might not reach them. Never send local traffic through a metered proxy accidentally. Metadata endpoints remain blocked even with JEV_ALLOW_PRIVATE_URLS=1.

Setting Default / purpose
CAMOFOX_URL http://127.0.0.1:9377
CAMOFOX_ACCESS_KEY, fallback CAMOFOX_API_KEY Browser Bearer token
JEV_OPENROUTER_KEY, fallback OPENROUTER_API_KEY Model API key
JEV_MODEL ~typesafe/jev-latest
JEV_TEXT_MODEL inception/mercury-2.5
JEV_URL, JEV_TEXT_URL OpenRouter decisions/chat endpoints
JEV_ALLOWED_HOSTS Empty; otherwise exact comma-separated page hosts
JEV_ALLOW_PRIVATE_URLS 0; standalone private-page opt-in
JEV_FAST_CLICK 0; optional Camofox plugin
JEV_ALLOW_INSECURE_CAMOFOX 0; permit a remote cleartext (http://) Camofox on a trusted network
JEV_ALLOW_INSECURE_MODEL_URLS 0; permit cleartext development model endpoints
JEV_VISION_* Optional visual browsing; see the vision settings

Task defaults: 25 executed steps, 100 model HTTP requests including helper/retry calls, 300 seconds. Maximums: 100 steps, 500 requests, 900 seconds. Cancellation is cooperative. Private-page configuration in Hermes remains Hermes' own policy. Known limitations: top-level DOM interaction, no general iframe/upload/popup support, no login/payment automation, model-assessed completion. Viewport resize works with the recommended image's pinned Playwright version. Other images' resize stalls warn after five seconds and continue using the actual viewport. A resize server error is tolerated only after verifying the page remains readable.

Development

python -m pip install '.[dev]'
python -m pytest
python -m coverage run -m pytest tests
python -m coverage report --fail-under=80
# Real disposable browser + deterministic local model provider; no paid keys:
JEV_FUNCTIONAL=1 python -m pytest tests/test_functional.py -q
# Opt-in real provider smoke, costs calls and any configured proxy bandwidth:
JEV_LIVE=1 python -m pytest tests/test_live.py -s

CI tests installed artifacts, MCP stdio, public-image browser execution and both old and new contracts. See docs/testing.md. Remove the demo browser with docker compose --env-file .env -f examples/compose.yaml down -v. Release history is in CHANGELOG.md; maintainers can use the release checklist before publishing a reviewed commit.

MIT: original contributions LICENSE; adapted upstream portions retain LICENSE-jev-ultrafast.

Metadata

Release files for jev-browser 0.4.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 jev-browser 0.4.0
File Size Uploaded Provenance
jev_browser-0.4.0.tar.gz 155.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for jev-browser 0.4.0
File Interpreter ABI Platform
jev_browser-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 219.7 kB

Release files / jev_browser-0.4.0.tar.gz

Download URL jev_browser-0.4.0.tar.gz
Size 155.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d392f930c5abb8616e4a5ea44e3cb4eb0a123bb1a17cf9ee703b307cdaa0a0ea
BLAKE2b-256 checksum
How to use checksums
928b0b8eb9a803bb522eee5f28d0c028a44dd81455dfa92dc9208da2dfc6c4b0
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 23, 2026.

Transparency log

Release files / jev_browser-0.4.0-py3-none-any.whl

Download URL jev_browser-0.4.0-py3-none-any.whl
Size 64.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b311c45abe4bff632b2d7f599806064474f62f8a9fee4e275d28178991b83ad3
BLAKE2b-256 checksum
How to use checksums
bc0a0b89454258bcdb51246e2c7e119ce05aac4785a3b9d4ef3037bae88f827d
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 23, 2026.

Transparency log

Security for jev-browser 0.4.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 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for jev_browser-0.4.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 23, 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.4.0 This release

2 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