Skip to content
MrJev

Jot

A general-purpose agent loop where Jev picks the next move and Jot runs it. No license file at the time of writing.

View on GitHub →

Hands-on review

A local chat app where the tool, the arguments and each word of the reply are all Jev choices. One tool skips the sign-in gate.

Good for

  • Model output never becomes a selector or a script — targets are opaque ids
  • `browser_fill` can only type text the user themselves wrote
  • Budget exhaustion is an explicit stop, never a pretend completion

Watch out for

  • `browser_read` bypasses the Google sign-in gate every other browser tool honours
  • A hardcoded flight-table regex can answer a question nobody asked
  • No LICENSE and no `license` field anywhere — the repository is unlicensed

Tested Sep 20, 2026 at 55c3082 · node:22 in Docker, full workspace install, then --network none with a local stand-in

How we reviewed this: we installed the full workspace in node:22, then ran offline with a local stand-in, and reproduced the browser finding by feeding one snapshot to two tools. We made no Jev calls.

What it does

A chat app bound to 127.0.0.1 in which every decision is a Jev question: which tool to call, what each argument should be, and — this is the unusual part — each individual word of the prose reply. It drives a real Chrome through patchright for observe, navigate, search, click, fill and read, plus an exact rational calculator.

Word-by-word generation costs what you would expect: two round trips per word, and we measured a five-word reply at 13 requests, each re-sending the whole conversation. With maxSteps at 40 the ceiling is roughly 83 requests for one reply.

The discipline around the browser is genuinely good, and worth reading if you are building anything similar. packages/browser/src/snapshot.ts opens with /** Static page-owned code; model output never becomes a selector or script. */ and holds to it: targets are opaque integers in a WeakMap, re-verified in-page before every interaction with a fingerprint covering role, name, value, href and the surrounding text. browser_fill can only submit text the user themselves typed. Password, file and hidden inputs are excluded at three independent layers — including on the path that names a target directly.

Which is exactly why the next thing stands out.

One tool skips the gate

Every browser tool funnels its snapshot through a shared builder, server/browser-tools.ts:8:

function result(snapshot:BrowserSnapshot,allowSignIn=false):ToolResult{
 if(!allowSignIn&&/accounts\.google\.com/i.test(snapshot.url))return {status:'error',reason:'needs_input',error:'Google sign-in is open.',…};

observe, navigate, search, click, fill, select, open, scroll and wait all return result(...). browser_read does not — it calls result() only on the interruption branch and otherwise builds its own return (:107):

const snapshot=await browser.observe(signal);if(snapshot.interruption)return result(snapshot);
…
return {status:'ok',text:text||'No matching page content.',data:{source:'browser',url:snapshot.url,field:args.field}};

interruption is only set when a visible captcha iframe and blocking text are both present (snapshot.ts:30). A Google sign-in page has neither, so the URL check is never reached. We fed one snapshot of accounts.google.com to both tools:

--- browser_observe ---
{"status":"error","reason":"needs_input","error":"Google sign-in is open.", …}

--- browser_read field=text, same page ---
{"status":"ok","text":"Sign in\nUse your Google Account\nEmail or phone\nvictim@example.com\n…"}

Two things make it worse than a missed check. browser_read is in the replyable set (server/jev-agent.ts:8), so that text can be returned to the user verbatim through the respond_N path. And because it is the latest tool result, it is exempt from the clipping that shortens everything else (jev-agent.ts:26):

const clipped=m===latestTool||!text||text.length<=1200?text:text.slice(0,1200)+'…';

so it goes to the API in full. The browser runs on a shared persistent profile, so a logged-in account chooser is a realistic state to be in.

There is a test for this gate and it passes — because it only exercises the path that is guarded.

A regex can answer a question nobody asked

server/local-draft.ts:49:

const table=flightTable(messages.filter(m=>m.role==='tool').map(m=>m.result.text??'').join('\n'));
if(table){
 const text=`Here are the matching flight options:\n\n${table}`;

If a regex matches two or more flight-shaped rows in any tool output, this returns a formatted flight table and sets reason:'final', which ends the loop. The user’s question is never read. We reproduced it by asking about baggage policy:

local model fetch calls: 0
reason: final  provider: evidence-table
answer:
Here are the matching flight options:
| Depart | Arrive | Airline | Duration | Stops | Price |
| 7:40 AM | 8:35 AM | British Airways | 1 hr 55 min | Nonstop | $178 |

Confident, well formatted, and not an answer to anything asked. The regex matches the row format of the project’s own headline demo.

It doesn’t build

README.md:68 says npm test && npm run build. At HEAD:

# tests 103 / # pass 88 / # fail 1 / # skipped 13   EXIT=1
npm run typecheck -> 2 errors
npm run build     -> exit 2

The failing test traces to server/jev-agent.ts:54, where a missing needs_long_draft answer defaults to false through an optional chain and overturns an explicit draft_message decision. git log -L shows the clause and the broken test arrived in the same commit. There is no CI to have caught it.

Two things the README doesn’t mention

It never mentions the browser at all. No section describes that a real Chrome starts, that the profile persists under .cache/browser-profiles, that google.com loads at startup before you do anything, that Google is the hardcoded search engine, or that live page text and form field values go to the API. The package list omits packages/browser entirely.

And README.md:35 says “Chats stay in this browser”. For storage that is true. But the whole conversation plus every tool result is posted to the API roughly 2N+3 times per reply. The neighbouring claim, “Credentials stay on the server”, we checked and it holds.

Verdict

18 commits, two days old, no CI — and no licence: no LICENSE file and no license field in any of the five package.json files, so nothing here can legally be reused. Ironically, the vendored word list has exemplary licence hygiene: source commit SHA, snapshot hash, itemised modifications and a scope note.

The browser safety work is better than most production code we have read. The gaps are a missed call site and a shortcut that predates the rest. We’ve reported the browser_read bypass, the flight-table shortcut and the missing licence.

For a browser agent with a working build, see jev-browser.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 20, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Coding Agents & Developer Tools

fast-jev-compaction

★ 7.8k▲ 59

tamaratran/fast-jev-compaction

Claude Code plugin that replaces the compaction summary with Jev decisions. Every tool call and result is scored; stale ones are dropped or truncated, and everything kept stays verbatim.

TypeScriptReviewed

Foreman

★ 804▲ 17

thruwire/foreman

Puts Jev as a fast supervisor above slower coding agents such as Codex, starting from a ticket, spec, or bug report.

PythonReviewed

Jev Review

★ 695▲ 4

devagrawal09/jev-review

Staged code-review workflow for JavaScript and TypeScript with a local dashboard. Jev screens correctness, security, reliability, compatibility, and test risk, then scores severity and suggests a reviewer, with no generative model involved.

TypeScriptReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.