Use case
Jev browser agent
A browser agent is a sequence of small decisions: which element, which operation, done or not. Jev answers those instead of writing text, which is the difference between a loop that runs at interaction speed and one that runs at generation speed.
The loop, and why it is a loop
A browser agent does not need a plan. It needs a decision per step, repeated. Every observation produces an indexed element table — the interactive nodes on the page, numbered, with their roles and current values. The agent sends that table to the model together with the goal, and gets back one operation and one target.
Two properties of this loop matter more than anything else. First, the answer space is closed: click, type, select, done, blocked. Second, the target space is closed too, because the elements were already enumerated by the observation step. A decision between enumerated options is not a writing task, and systems that treat it as one pay for text they immediately throw away.
state = { url, title, text, elements[0..n] }
questions = {
operation: { type: choice, criteria: { CLICK, TYPE_TEXT, SELECT, DONE, BLOCKED } },
click_target: { type: choice, criteria: { "1": {element}, "2": {element}, ... } },
}
answer = { choice, confidence, probabilities } // no text for the decision itselfWhere the text still comes from
One operation is irreducible: typing. If the goal says “search Zürich to London” and the loop lands on an empty textbox, something has to produce those characters, and that is a generation task. The pattern that shows up across every build we collected is to keep a small language model in the loop strictly for that one operation, and to route every other operation through the decision model.
That split is what makes the numbers in the case list below interesting. The reported cost per task in the sub-cent range is not the cost of a model that thinks; it is the cost of one state payload and a handful of closed choices, plus a small amount of text on the steps that genuinely need it.
What we measured, and what it does not prove
We ran Jev against two chat models on four bounded decision tasks and published every raw response. On those tasks, from a machine in Hong Kong, Jev’s warm median latency was 313 ms against 263 ms and 259 ms for the two baselines. It was not the fastest, and its cost per thousand decisions was $0.0200 against $0.0351 for the cheap baseline — under 2x, nowhere near the 40x to 400x figures circulating.
The reason matters more than the result. A minimal-payload round trip from that location already costs about 280 ms to Jev and 700 ms or more to the other two endpoints, and Jev’s floor is essentially equal to its task latency. At that distance a single request is dominated by the network, so this setup cannot demonstrate a large speed advantage in either direction. If you are choosing an architecture, run the same scripts from the region your agent will actually run in.
What the measurement does support is narrower and still useful: the decision itself adds very little on top of the round trip, which is what you want from a component called once per step. The full method and the raw data are on the benchmark page.
A minimal loop you can actually run
The shape below is the one every build in the case list converges on. The important parts are not the API call but the two guards around it: element indices are re-derived from the current observation rather than remembered across steps, and the whole step is wrapped in a bounded retry budget.
async function step(goal, observation, history, budget = 8) {
const elements = indexInteractiveNodes(observation) // re-derived every step
const answer = await jev({
state: { goal, url: observation.url, elements, recent_actions: history.slice(-10) },
questions: {
operation: { type: 'choice', criteria: OPERATIONS },
click_target: { type: 'choice', criteria: indexChoices(elements) },
},
})
if (answer.operation.choice === 'DONE') return { done: true }
if (answer.operation.confidence < 0.6 && budget === 0) return { escalate: true }
if (answer.operation.choice === 'TYPE_TEXT') {
const text = await smallLlm(goal, elements[answer.click_target.choice]) // the only generation
return act({ kind: 'TYPE_TEXT', target: answer.click_target.choice, text })
}
return act({ kind: answer.operation.choice, target: answer.click_target.choice })
}Failure modes worth designing for up front
Stale indices are the most common failure. A page that re-renders between the observation and the action invalidates the numbering, so the click lands on something else. Re-deriving the table per step and refusing to act on an index that no longer exists removes most of this class.
Confidence is not a substitute for verification. A decision can come back with a high probability and still be wrong because the element description was ambiguous — two buttons both labelled “Continue”. The fix belongs in the observation step: label the choices with enough surrounding context that the model can tell them apart, rather than trusting the score.
Cold calls are visible to users. Our cold measurement for Jev was 443 ms against a 313 ms warm median, and a second cold pass after 90 seconds of idle landed in the same range. In a loop where each step is expected within a few hundred milliseconds, the first step after a quiet period will feel different, so warm the connection before the user is waiting on it.
Finally, budget the retries explicitly. A decision model that is wrong will usually be wrong consistently, so retrying the identical state is the expensive way to keep failing. Change the observation, narrow the options, or escalate to something that can explain itself — but cap the loop.
How this connects to the rest of the pattern
The browser case is the agent-loop pattern with an unusually clean observation step: the DOM hands you the option list for free. If your loop does not have that, start with where a decision model fits in an agent loop, then come back. If the payload per step is the thing you are worried about, the document classification page covers what happens when the input gets long.
Reported on X
Quoted metrics are as posted by the author and are not verified or normalised by us.
Browser Use rebuilt its agent loop on Jev and published a flight-booking run at 1× speed.
Reported: 7s flight search, $0.0039
@gregpr07 on XVoice control of a live browser, one Jev call per interaction.
Reported: ~300ms per decision, $0.0002 per decision
@moritzkremb on XComputer/browser use with a11y tree as state and actions as questions.
Reported: $0.001 per task
@kylejeong on XComputer-use loop benchmarked against a frontier chat model.
Reported: 155× cheaper, ~20× faster than the compared model
@awlevin on XA free browser agent shipped Jev as its decision layer.
Reported: recorded tasks ~40% less time
@b_kalisetty on X