jevnav

Verdad de página y decisiones reproducibles para agentes de navegador: un modelo elige el elemento, una puerta bloquea acciones riesgosas, y cada ejecución se reproduce sin conexión en CI.

Documentación

jevnav

Verdad de página para agentes de navegador — y decisiones que se reproducen, prueban y auditan.

CI PyPI Python MCP registry Marketplace License

Un agente de codificación que trabaja en un código frontend obtiene dos cosas de jevnav:

  • Verdad de página, no píxeles. La estructura, los estilos calculados y los controles vuelven como hechos, y diff informa font-size 32px → 28px entre una maqueta y la aplicación en ejecución — la forma que un agente puede corregir. Sin capturas de pantalla en el bucle de decisión.
  • Evidencia, no confianza. Cada acción es una decisión Jev con una probabilidad calibrada, las riesgosas están bloqueadas, y toda la ejecución es un rastro que replay re-verifica sin conexión en CI: un cambio en el sitio que rompe una decisión registrada sale con código 1, sin llamada de modelo y sin clave API.

Las pruebas basadas en selectores se rompen en el momento en que cambia una etiqueta, y los agentes de navegador LLM son confiados, no auditables y ocasionalmente incorrectos. jevnav se sitúa en medio (la versión más larga de este argumento: docs/why.md):

  1. Jev elige el elemento. La lista de candidatos de la página actual se convierte en una pregunta de elección; el modelo responde con un elemento y una probabilidad calibrada. En modo bucle (jevnav go) una solicitud también responde qué hacer, si el objetivo ya se cumple y qué valor de contexto escribir.
  2. Cada decisión se registra. El rastro contiene los candidatos tal como el modelo los vio, la elección, la probabilidad y el costo — un archivo JSONL por ejecución.
  3. Las acciones riesgosas están bloqueadas. p por debajo del umbral, o una intención que parezca destructiva, va a un humano en lugar de hacer clic.
  4. replay es la prueba de regresión. Sin conexión, sin llamada de modelo: re-resuelve cada decisión registrada contra la página tal como está ahora. Un cambio en el sitio que rompe un objetivo falla en CI; todo lo demás se informa como deriva, no como ruido.

Verdad de página para tu agente

Los hechos que un agente de codificación necesita sobre una página renderizada, sin captura de pantalla: outline(selector) para la estructura de la región (etiquetas, encabezados, texto, cajas), styles(selector, props) para los valores calculados que el navegador resolvió, page_state() para los controles que jevnav puede ver.

Maqueta vs aplicación, como hechos en lugar de píxeles — jevnav diff informa diferencias de estructura y estilo y sale con código 1 en caso de deriva:

| element                 | property      | mockup | app  |
|---|---|---|---|
| h1 [Pricing]            | font-size     | 32px   | 28px |
| button#cta [Start free] | border-radius | 8px    | 4px  |

Un font-size 32px → 28px es algo que un agente puede corregir; una diferencia de píxeles roja no lo es. Una vez que la aplicación coincide, fija el resultado (goal(..., success="<selector>")) y replay --execute lo re-verifica en CI. Recorrido completo: Coincidir una maqueta con la aplicación.

Instalación

uv tool install jevnav          # or: pip install jevnav (the MCP server is included)
playwright install chromium     # one-time browser download

Publicado en PyPI, listado en el registro MCP como io.github.dtduc-git/jevnav, y la acción de reproducción está en el GitHub Marketplace.

jevnav run necesita una clave API TypeSafe (TYPESAFE_API_KEY, o ~/.config/typesafe/apikey.txt). jevnav replay no necesita ninguna — ese es el punto.

Inicio rápido — deja que Jev conduzca

jevnav go --goal "sign in with the demo account and open the pricing page" \
  --start https://app.example.com/login \
  --context email=demo@example.com --context password="${ACME_PASSWORD}" \
  --success "#pricing.visible" \
  --report goal.md
status: done — outcome verified against the page
steps: 5 — auto 4, review 0, blocked 0

Una solicitud Jev por paso, y cada paso está bloqueado y rastreado. El bucle se detiene cuando el modelo dice que el objetivo está completo, cuando ningún elemento listado puede avanzar (stuck), cuando la puerta quiere un humano (review), cuando la página deja de cambiar (no_progress), o en --max-steps. --dry-run decide sin actuar.

done es una afirmación, no evidencia. Pasa --success <selector> y la afirmación se verifica contra la página: verified, unverified (el selector no está allí — la ejecución falla), o "no verificado" cuando no pasaste ningún selector.

Inicio rápido — un flujo con script

# flows/acme-login/flow.yaml
id: acme-login
start: https://app.example.com/login
steps:
  - intent: "Sign in to the existing account"
    action: click
  - intent: "Type the password"
    action: fill
    value: "${ACME_PASSWORD}"   # read from the environment, never written to the trace
  - intent: "Submit the login form"
    action: click
    expect: "button[type=submit]"   # optional ground truth, used to score the run
jevnav run flows/acme-login/flow.yaml --report run.md
jevnav replay acme-login.trace.jsonl --report replay.md   # offline, deterministic
jevnav diff new-ui.html http://localhost:3000             # mockup vs app, exit 1 on drift

run recorre el flujo: extraer candidatos → preguntar a Jev → bloquear → actuar → registrar. replay re-verifica el rastro contra el sitio en vivo, sin modelo en el bucle (y con --execute re-ejecuta las acciones registradas y verifica el selector --success registrado, de modo que una ejecución completa del agente se convierte en una prueba CI):

steps 3  verdicts: ok 3

Cambia Sign in a Log in en el sitio y la misma reproducción informa:

[01] changed   Sign in to the existing account
      no element now has 'button|sign in' (was 'Sign in' / 'button')

Código de salida 1, con la razón — esa es la puerta de CI.

pytest — intenciones en una prueba ordinaria

El fixture jev viene con el paquete, de modo que una prueba normal de Playwright obtiene decisiones Jev sin cambiar cómo escribes las pruebas — y cada prueba escribe un rastro que se reproduce en CI:

def test_sign_in(jev):
    jev.goto("https://app.example.com/login")
    jev.fill("the email address", "demo@example.com")
    jev.fill("the password field", "${DEMO_PASSWORD}")
    jev.click("the sign-in button")
    jev.expect("#welcome")
DEMO_PASSWORD=... pytest --jev-trace-dir=traces
DEMO_PASSWORD=... jevnav replay --execute traces/test_sign_in.trace.jsonl  # offline, no key

jev.expect se registra en el rastro, de modo que la reproducción verifica el resultado además de re-ejecutar las acciones. Un veredicto review falla la prueba antes de que la acción se ejecute, los valores ${VAR} se registran solo por nombre, y jev.page es la página real de Playwright para todo lo demás. Ejemplo ejecutable, con un rastro confirmado que cualquiera puede reproducir: examples/pytest-interop/.

Juegos (la forma Doom)

Un juego no tiene lista de candidatos para extraer, de modo que play toma la otra forma: le das una sonda de estado JS y un pequeño conjunto de acciones, y Jev decide a una tasa fija mientras el juego sigue ejecutándose — las teclas de movimiento se mantienen presionadas entre decisiones, de modo que la última respuesta se aplica mientras el modelo piensa. Exactamente cómo Jev juega Doom (alimentado con estado estructurado como texto, ~10 llamadas/segundo, sin imágenes).

jevnav play \
  --goal "catch the green blocks, dodge the red ones" \
  --url "examples/game/index.html?seed=7" \
  --state-js examples/game/state.js \
  --actions "left=ArrowLeft" --actions "right=ArrowRight" \
  --rate 4 --seconds 60 --score-js "window.jevnavScore()" \
  --ready-js "() => !!window.jevnavState" \
  --report play.md

Medido en el juego incluido (examples/game/), 60 segundos, tres semillas, misma tasa de decisión para ambos lados:

semillacontrol aleatorio @3/sJev @3/s
301
716
1112
media0.673.0

La latencia de Jev fue 312ms p50 desde Vietnam, que es lo que limita el bucle a ~3 decisiones/segundo (la demo Doom de TypeSafe corrió ~10/s desde una red de EE. UU.). Ejecuta --policy random para tu propio control, y conserva el rastro: es la evidencia.

Qué funciona dónde: un juego DOM (como el incluido, o 2048) expone estado a JS, de modo que una sonda es fácil. Un juego <canvas>/WebGL — o Flash — no tiene estado en el DOM; necesita que el propio juego exponga uno (Chocolate Doom WASM lo hace, que es cómo los agentes Doom del navegador lo leen). jevnav aún no toma capturas de pantalla y no hace decisiones de píxeles, deliberadamente: eso es lo que mantiene las decisiones reproducibles.

Tu propio Chrome (inicios de sesión, cookies, extensiones)

Tres formas de obtener un navegador:

jevnav go --goal "..."                      # default: fresh headless Chromium, no cookies
jevnav go --goal "..." --user-data-dir ~/.cache/jevnav-profile --headed
jevnav go --goal "..." --cdp http://127.0.0.1:9222
  • --user-data-dir es un perfil Chromium persistente: ejecuta una vez con --headed, inicia sesión manualmente, y cada ejecución posterior (con o sin cabeza) ya está iniciada sesión. El modo con cabeza necesita el navegador completo: playwright install chromium.
  • --cdp se adjunta a un Chrome que ya tienes abierto — tu sesión, tus extensiones, la pestaña que estás mirando. Inícialo con --remote-debugging-port=9222 (o usa chrome://inspect para encontrar el puerto). jevnav elige la última página real que encuentra y nunca cierra tu navegador.

Ambas banderas funcionan en run, go, replay y mcp. Un rastro registra lo que se decidió, nunca qué perfil se usó: las cookies y las rutas de perfil nunca llegan a él.

Usa la misma libertad para la autenticación: pasa credenciales como contexto y deja que el objetivo llene un formulario de inicio de sesión cuando una cookie obsoleta sería peor que un inicio de sesión nuevo.

Puertas

# flows/acme-login/gates.yaml  (optional; sane defaults apply)
min_confidence: 0.9        # scripted flows: one question per step, well calibrated
loop_min_confidence: 0.5   # goal loop: four questions at once, p runs lower
risky:                                  # regular expressions, matched against
  - "\\b(delete|remove|purchase|pay)\\b"   # intent + chosen element name + role
intents:
  "delete the *": { min_confidence: 0.99 }
truncated: review                       # page had more than 255 candidates

Tres veredictos, sin ambigüedad:

veredictosignificado
autoconfianza en o por encima del umbral, nada riesgoso — la acción se ejecuta
reviewun humano confirma primero (bajo p, intención riesgosa, lista de candidatos truncada)
blockedno fue posible ninguna decisión (el modelo respondió none, o la llamada falló)
n/ael bucle se detuvo solo (done) — sin acción que bloquear

En modo bucle el umbral de confianza es más bajo a propósito. Medido 2026-09-21: las decisiones correctas del bucle aterrizan en p 0.41–0.99 y las incorrectas en 0.39–0.47, de modo que p no las separa. Lo que mantiene seguro el bucle es determinista: fill en un botón se rechaza antes de ejecutarse, un campo sin valor de contexto se bloquea, dos pasos que no cambian nada detienen la ejecución, los patrones riesgosos siempre van a revisión, y el resultado se verifica contra --success.

MCP — para otros LLM

jevnav ejecuta su propio navegador y lo expone como servidor MCP, de modo que un agente de codificación (Claude Code, Codex, Cursor, cualquier cosa que hable MCP sobre stdio) puede conducir una página a través de decisiones Jev en lugar de escribir selectores:

jevnav mcp          # that is the whole setup: no URL, no trace path, no flags

No se necesita URL: el agente abre páginas él mismo con goto(url), de modo que un servidor sirve a cada dominio — una sesión puede visitar varios sitios, en varias pestañas. La sesión escribe jevnav-session.trace.jsonl en el directorio de trabajo del cliente por defecto (las sesiones anteriores se archivan a su lado, --no-trace opta por no participar), de modo que cada sesión es auditable sin configurar nada.

--start <url> existe solo como conveniencia para una configuración con ámbito de proyecto que siempre comienza en una página; ponla en la configuración de ese proyecto, no en la global. Lo mismo para las elecciones por instalación: --browser, --user-data-dir (inicia sesión en cualquier número de sitios una vez, en un perfil), --cdp, --locale, --timezone.

Herramientas:

Decidir — la parte que ningún otro MCP de navegador tiene:

herramientaqué hace
browse(intent, action, value, min_confidence)un paso: Jev elige el elemento, la puerta decide, y solo auto actúa
goal(goal, context_json, max_steps, success)conduce todo el camino: "inicia sesión y abre facturación" — success verifica el resultado; devuelve done / stuck / review más la verificación
goto(url)abre una página
page_state()URL, título y la lista corta que jevnav puede ver
summary()esta sesión: pasos, auto/revisión/bloqueado, costo, latencia

Actuar — todo lo demás que un agente necesita:

herramientaqué hace
screenshot(path, full_page, selector)guarda un PNG para un humano (nunca usado por una decisión)
upload_files(paths, selector, intent)establece archivos, en un selector o una entrada que Jev elige
drag(source_selector, target_selector)arrastra un elemento sobre otro
resize(width, height)cambia la ventana gráfica
emulate(color_scheme, media, geolocation, offline, …)emula medios, ubicación y conectividad
press_key(key, selector)una tecla o combinación ("Control+A"), opcionalmente en un elemento
fill_form(fields_json)llena varios campos en una llamada: {selector|intención, valor, acción}
wait_for(text, selector, timeout_ms)espera a que algo aparezca
scroll(direction, amount)desplaza el documento
tabs(), new_page(url), select_page(i), close_page(i)trabaja con pestañas

Inspeccionar — los ojos del agente (solo observación, nunca rastreado):

herramientaqué hace
console(limit, only_errors)mensajes recientes de consola y errores de página
network(limit, only_failed)solicitudes recientes, con estados
network_detail(index, url_contains)encabezados y cuerpo de una solicitud
dialogs()alerta/confirmar/prompt, con la política o regla que los resolvió
dialog_policy(action, match)responde diálogos futuros: el predeterminado, o reglas por texto de mensaje
read_js(expression)evalúa JS en la página
outline(selector, limit)estructura de una página o región (etiquetas, encabezados, texto, cajas)
styles(selector, props, limit)estilos calculados de los elementos coincidentes
route(pattern, status, body, abort) / unroute(pattern)simula o bloquea solicitudes (pruebas)
trace_start() / trace_stop(path)un zip de rastro Playwright para playwright show-trace
perf_metrics(), heap_snapshot(path)contadores de Chromium y una instantánea de montón (mejor esfuerzo: para perfilado real usa chrome-devtools)
emulate(cpu_throttle, network_conditions, …)limitación de CPU y perfiles estilo Slow-3G (chromium, vía CDP)
lighthouse(url, categories)puntuaciones Lighthouse, a través de npx (mejor esfuerzo: necesita node)

Conéctalo a un cliente (esta forma JSON es la que usan Cursor, Claude Desktop y VS Code; Claude Code también acepta claude mcp add --scope user jevnav -- uvx jevnav mcp):

{
  "mcpServers": {
    "jevnav": {
      "command": "uvx",
      "args": ["jevnav", "mcp"],
      "env": { "TYPESAFE_API_KEY": "..." }
    }
  }
}

Por qué un agente lo haría: no necesita su propio Playwright MCP, no puede hacer clic en un Delete por accidente (review nunca se ejecuta; los patrones de acciones riesgosas están disponibles para nueve idiomas, y se extienden en gates.yaml), y toda su sesión es un rastro que jevnav replay --execute puede re-ejecutar en CI. El costo es de aproximadamente $0.00004 y 330ms por paso; page_state y goto son gratuitos. Cada herramienta declara sus anotaciones MCP — solo lectura, destructiva, idempotente, mundo abierto — para que un cliente pueda distinguir una observación de una acción antes de llamarla.

Diálogos: respondidos por regla, no estacionados

La API síncrona de Playwright debe responder a un diálogo dentro de su manejador. Estacionar uno para que un humano decida después bloquea el renderizador y la siguiente llamada nunca regresa (medido en este código base, luego eliminado). Así que jevnav responde desde una política que estableces de antemano — dialog_policy("accept", match="delete") — y registra cada diálogo con la regla que se activó, para que la ejecución siga siendo auditable.

Cuál usar

Playwright (biblioteca)chrome-devtools-mcpjevnav
quién elige el elementoun humano escribe selectoresel LLM, desde una instantáneaJev, con una probabilidad calibrada
alcancela API completa de autoría de pruebas29 herramientas, primitivas + perfilado33 herramientas, actuación a nivel de intención + observación
acciones riesgosaslo que diga la pruebalo que diga el LLMnunca ejecutadas hasta que un humano lo diga (los patrones riesgosos cubren inglés, vietnamita, alemán, francés, español, portugués, japonés, chino y coreano)
evidencia de regresiónvisor de rastros, re-ejecutar la pruebaningunarastro de decisiones + reproducción sin conexión que sale con código 1
aserción de resultadoexpect(...)ninguna--success selector, verificado o reportado como no verificado
motoreschromium, firefox, webkitchromiumchromium, firefox, webkit (--browser)
limitación de CPU / Slow-3G✅✅✅ (chromium, CDP)
cabeceras/cuerpo de solicitudes✅✅✅ network_detail
llenado de formularios de varios campos✅✅ fill_form✅ fill_form (selector o intención)
combinaciones de teclas✅✅ press_key✅ press_key
costo por paso0un turno de LLM por paso (~38k caracteres de instantánea)$0.00004

Este no es un argumento de reemplazo: los tres hacen trabajos diferentes, y ejecutar más de uno cuesta una línea de configuración (el navegador de jevnav arranca en ~20ms y es perezoso, así que un segundo servidor es casi gratis). Playwright es la biblioteca con la que escribes una suite de pruebas — jevnav está construido sobre ella. chrome-devtools es lo que usas para depurar una página: capturas de pantalla, consola, red, rendimiento, todo el detalle crudo en el contexto del modelo, que es exactamente correcto para depurar y exactamente incorrecto para conducir. jevnav es la capa de decisión + evidencia: una intención entra, una acción restringida sale, un rastro que se reproduce sin conexión. Úsalo cuando el mismo flujo tenga que seguir funcionando, y chrome-devtools cuando necesites descubrir por qué se detuvo.

Cuando la compuerta dice review

review es la herramienta negándose a adivinar — y devuelve lo que necesitas para resolverlo: los subcampeones con sus probabilidades y una pista. Medido en la página principal de Wikipedia (254 candidatos):

  1. goal("search Wikipedia for ...") → review a p=0.33 — la página tiene dos formas plausibles de enviar, así que no se hizo clic en nada.
  2. El llamador vuelve a leer la página y llama a browse("Click the Search button that submits the search form in the site header", "click", min_confidence=0.8) → auto a p=0.95, clic real.
  3. goal(...) de nuevo → done, verificado: true contra .mw-search-results.

Entonces: sé específico, y si conoces la página mejor que el modelo, establece tu propio min_confidence — la barra de confianza es decisión del llamador. Los patrones riesgosos, la validación de rol/valor y la verificación de resultado no se pueden anular.

La ruta stdio se prueba de extremo a extremo en CI: un cliente MCP real se conecta a un subproceso jevnav mcp, lista las herramientas, llama a goal y verifica que el navegador actuó (tests/test_mcp_server.py, sin red, endpoint Jev falso).

CI — la Acción

La Acción reproduce un rastro grabado y falla cuando un cambio en el sitio rompe una decisión grabada. Sin llamada de modelo, sin clave API, ~30 segundos:

- uses: dtduc-git/jevnav@v0
  with:
    trace: examples/local-demo/demo.trace.jsonl
    execute: "true"          # also re-run the recorded actions
    report: replay.md

Entradas: trace, report, execute, json, version (por defecto latest desde PyPI, o local para ejecutar un checkout). Código de salida 1 cuando un objetivo cambió, se volvió ambiguo, o un selector --success grabado ya no es visible. @v0 es una etiqueta flotante; fija @v0.1.1 si lo prefieres.

Cómo funciona

  • Los candidatos son una lista corta, no la página. Elementos interactivos visibles ordenados por la probabilidad de que un humano actúe sobre ellos — primero en el viewport, controles de formulario antes que botones antes que enlaces — limitado a 120 (--max-candidates, el límite máximo de la API es 254). Medido en Hacker News (199 elementos → 40): misma precisión, 2.8× más rápido en una decisión en frío y 3.8× menos tokens de entrada. Cada uno lleva rol, nombre accesible, tipo, href, marcador de posición y un alcance (leyenda/encabezado más cercano) para que tres campos "Email" sigan siendo distinguibles.
  • Huella digital. La identidad de un elemento es role|name (normalizado por espacios y mayúsculas). Los rastros almacenan la huella digital de cada candidato tal como se mostró al modelo, para que la reproducción nunca vuelva a derivar la identidad con código nuevo.
  • Decisiones. Una pregunta de elección por paso: el mapa de opciones es la lista de candidatos, más none. La decisión se registra con probabilidades, uso y costo.
  • Reproducción. Re-extraer la página, comparar huellas digitales. --normalize REGEX relaja la coincidencia para cambios conocidos (un contador como "Carrito (3)" → "Carrito (4)") en ambos lados, opcional, así que estricto sigue siendo el predeterminado. ok (encontrado), moved (encontrado en otro lugar de la página), changed (desaparecido), ambiguous (ahora duplicado), error. changed, ambiguous y error fallan; moved y los conteos de deriva se reportan.
  • Acciones. click, fill, select, check, hover, press, none. La reproducción re-ejecuta acciones solo con --execute, y las resuelve por huella digital — nunca por posición — para que una página desplazada no pueda hacer clic en lo incorrecto.

Coincidir un mockup con la aplicación

jevnav diff new-ui.html http://localhost:3000 --report ui-diff.md
- mockup: `new-ui.html` — 'Pricing (new UX)', 7 elements
- app:    `http://localhost:3000` — 'Pricing', 4 elements
- differences: **5** structure, **4** style

## Structure (`body`)
| kind | element | detail |
|---|---|---|
| missing | p 'Three plans for every team.' | not on the other page |
| missing | section 'Enterprise Talk to sales' | not on the other page |
| missing | button 'Talk to sales' | not on the other page |
| new | button#extra 'Book a demo' | only on the other page |
| moved | h1 'Pricing' | x+0 y+0 w+0 h-5px |

## Styles (`h1,#cta`)
| element | property | mockup | app |
|---|---|---|---|
| h1 [Pricing] | font-size | 32px | 28px |
| button#cta [Start free] | border-radius | 8px | 4px |

Código de salida 1 cuando algo difiere, 0 cuando las páginas coinciden — así que el mismo comando funciona como una verificación de CI de que la aplicación no se ha desviado del diseño. La estructura se compara por etiqueta + el texto propio del elemento (los contenedores de auto-cierre no se consideran "cambiados" cuando un hijo desaparece), las cajas se comparan con una tolerancia de 4px (--tolerance), y los valores de píxeles fraccionarios se redondean para que el ruido de diseño no se lea como un cambio.

El bucle para "aquí hay un nuevo UX/UI, actualiza el código base": el agente de codificación abre el mockup y la aplicación en ejecución con jevnav, lee los hechos en lugar de adivinar — outline("main") para la estructura, styles("#hero", ["font-size", "gap"]) for the computed values, page_state for the controls, screenshot para el humano — compara los dos, edita el código él mismo (esa parte es del agente de codificación, no de jevnav), y luego vuelve a leer la aplicación para confirmar. goal("...", success="<selector>") fija el resultado para que la corrección pueda reproducirse en CI más tarde.

jevnav informa; no edita tu repositorio, y no compara píxeles.

Arquitectura

jevnav architecture

docs/architecture.html es la versión interactiva (pan, zoom, temas, tres vistas guiadas: una decisión, evidencia y reproducción, la otra bucles); la especificación de la que se construyó es docs/architecture.archify.json. En una línea: el llamador da una intención, jevnav lee una lista corta clasificada del navegador, Jev elige con una probabilidad calibrada, la compuerta decide si eso puede ejecutarse sin supervisión, la acción regresa a través del DOM, y cada paso aterriza en un rastro que replay re-resuelve sin conexión.

Por qué el bucle es más barato: dos secuencias

chrome-devtools: every step is an LLM turn jevnav: one call, every decision made for you

Misma tarea, diferente anatomía. Con chrome-devtools-mcp el LLM es los ojos: cada paso lee una instantánea de accesibilidad de ~38k caracteres en su propio contexto (~10k tokens en un modelo de frontera), decide el elemento, hace clic, y paga por un turno completo de nuevo en el siguiente paso. Con jevnav el LLM pregunta una vez (goal), y cada paso es una pregunta de ~330ms, $0.00004 a Jev sobre una lista corta de ≤120 candidatos que nunca entra en el contexto del LLM — con una compuerta en el medio y un rastro escrito mientras avanza. Una muestra temprana de una ejecución con el mismo LLM (deepseek-v4.1-flash vía opencode) está en el historial de git; no te apoyes en ella — n=1 por servidor, y su número más llamativo vino de un 403 de política de robots, no de la arquitectura. La afirmación determinista es replay, y no necesita benchmark para defenderse. Versiones interactivas de ambas secuencias: docs/seq-chrome-devtools.html, docs/seq-jevnav.html.

Benchmarks

En un conjunto de tareas de conducción (una consola de operaciones local: inicio de sesión, un formulario dentro de una raíz shadow, una acción de fila de tabla, una factura en iframe), mismo LLM barato para ambos servidores, n=2 por tarea: jevnav 8/8 tareas, chrome-devtools-mcp 6/8 — y los dos fallos fueron inestabilidad del modelo, no capacidad (una re-ejecución manual terminó con la respuesta correcta a través de la raíz shadow). chrome-devtools fue 2.4x más rápido de extremo a extremo (19.3s vs 45.6s de media) con menos llamadas. Esa es la corrección honesta a cualquier afirmación de "más rápido": la ventaja de jevnav es costo de decisión y evidencia, no tiempo de pared en páginas pequeñas. Método completo y advertencias: research/driving-benchmark.md.

Dos números más, y solo uno de ellos es una comparación.

Determinista, y el que debes exigir a jevnav: replay es sin conexión, no necesita clave API, y sale con código 1 cuando una decisión grabada ya no se resuelve. No hay error de muestreo en eso; ejecútalo en tus propios rastros.

A nivel de herramienta, y más débil por naturaleza — benchmarks/mcp-compare.py, misma máquina, una tarea, contra chrome-devtools-mcp:

jevnavchrome-devtools-mcp
MCP listo22ms (navegador perezoso)491ms
observación que el agente debe leer4.8k caracteres38.3k caracteres
llamadas de herramienta para la tarea24
costo de decisión (real / modelado)$0.0008$0.057
resultado verificado contra la páginasí (selector --success)no existe tal noción

Una ejecución anterior con el mismo LLM (deepseek-v4.1-flash vía opencode) está registrada en el historial de git, pero no te apoyes en ella: n=1 por servidor, un modelo, dos tareas, y el número más llamativo (una búsqueda en Wikipedia donde chrome-devtools tomó 83s y alcanzó HTTP 403) es un artefacto de política de robots, no una diferencia arquitectónica. La versión honesta es la tabla anterior — lo que el llamador paga por paso y cuánto de la página aterriza en el contexto del modelo — e incluso eso no dice nada sobre cómo se comportan los dos en muchos sitios. Lo que jevnav afirma es más estrecho y demostrable en tus propias páginas: una decisión en o por encima de la compuerta es segura de ejecutar, y la ejecución se reproduce.

Medido

El bucle de objetivos, medido el 2026-09-21 (4 objetivos × 2 redacciones × Jev real, local fixture: iniciar sesión, abrir precios, iniciar sesión luego precios, un objetivo imposible): 8/8 objetivos correctos, incluido el imposible (stuck), $0.00004 por paso, p50 314ms por paso. Una ejecución real — iniciar sesión luego abrir precios — tomó 5 pasos, $0.000214, y se reprodujo sin conexión con --execute: 5/5 objetivos resueltos, resultado verificado.

En páginas reales (research/browser-element-selection.md, 30 casos etiquetados a mano en 8 sitios públicos, una decisión cada uno, modelo jev-1.13.0):

  • 41/41 casos puntuados correctos; 30 se ejecutaron en p >= 0.9 y los 30 fueron correctos.

  • 365ms p50, $0.000153 por decisión.

  • 71 casos están escritos, pero solo 41 puntuados: el arnés rechaza etiquetas cuyo selector coincide con cero o varios elementos visibles, y 30 de los míos lo hicieron. n pequeño, anotador único, páginas bien construidas: una dirección, no una prueba. Los casos, el ejecutor y el registro de casos excluidos están todos en el repositorio. El spike de decisión de elementos en tiempo de compilación (44 decisiones: fixtures locales, Hacker News, PyPI, Wikipedia):

  • 44/44 decisiones correctas; 28/28 en p ≥ 0.9 (la puerta automática).

  • La reproducción detectó 4/4 cambios DOM inyectados con 0 falsas alarmas en las páginas sin cambios.

  • Latencia p50 334ms, p95 834ms; $0.000053 por decisión.

  • Al pedir un elemento que no existe, Jev respondió none en p=1.0 y p=0.92 en lugar de inventar uno.

Muestra pequeña, verdad fundamental autoevaluada, intenciones fáciles: trátalos como dirección, no como prueba. replay es el número que importa en CI, y es determinista.

No objetivos

  • Sin planificador ni bucle de agente: tú (o tu agente) decides qué hacer; jevnav decide dónde y registra el porqué.
  • Sin capturas de pantalla en el bucle de decisión, sin generación de texto (fill toma el texto de tu flujo o de tu entorno).
  • Sin iframes, shadow DOM, canvas ni selectores de archivos en v0.1: cola larga, rastreada como problemas en lugar de soporte parcial.
  • Sin SaaS, sin ejecutor alojado, sin telemetría. Local primero: nada sale de la máquina excepto la pregunta enviada a tu endpoint de Jev configurado.

Privacidad

Los traces contienen URLs de páginas, nombres de elementos y tus acciones, nunca capturas de pantalla. El modo de bucle también envía un resumen breve del texto visible de la página (así es como el modelo juzga si el objetivo está completo) y el valor actual de los campos de formulario (contraseñas enmascaradas): eso es lo que cualquier agente de navegador tiene que observar. Los flujos con script no envían ni uno ni otro. Los value literales del flujo se registran (ya están en tu repositorio); los valores de ${ENV} se registran solo como nombre de variable. Añade *.trace.jsonl al .gitignore de tu proyecto (el propio repositorio de jevnav lo hace) y audita un trace antes de compartirlo.

Suite

jevnav es la pieza de navegador de una pila de verificación: mcplint (configuraciones MCP), harnessguard (harnesses de agentes), jevassert + jev-packs (paquetes de decisión calibrados) y jev-table.

Licencia

Apache-2.0.