ego-jev

September 18, 2026 · View on GitHub

A small, bounded browser loop that joins ego-lite browser control with TypeSafe Jev decisions.

ego-lite snapshot -> canonical state + candidate set -> Jev choice
     -> optional text model -> ego-lite action -> new snapshot

It is deliberately not a general-purpose browser agent. Jev can choose only a supported operation and a target from the current ego snapshot. It never receives or emits JavaScript, CSS selectors, browser coordinates, or an action ref. The executor maps the selected opaque candidate back to a ref that was observed in that same snapshot, then discards it after the action.

Requirements

  • Node 26.7.0 for the checked development baseline (.nvmrc). The package accepts Node >=22.15.0 <27.
  • npm 11.19.0 (packageManager field) and the committed package-lock.json.
  • An installed, connected ego-lite command: ego-browser. This project invokes it as an external runtime; it does not bundle, fork, or import its closed browser runtime.
  • TYPESAFE_API_KEY for Jev/System One.
  • TEXT_MODEL_API_KEY (or OPENAI_API_KEY) and TEXT_MODEL only when Jev selects TYPE_TEXT. This MVP calls the OpenAI Responses API with structured output; it does not use the legacy Chat Completions API.

The implementation was checked against ego-lite runtime 0.5.0.28 and ego-browser CLI output on 2026-09-18. See NOTICE for the pinned upstream commits and licenses.

Version boundaries are committed: Node 26.7.0, npm 11.19.0, TypeScript 5.8.3, and dotenv 16.6.1 are exact checked baselines; the lockfile pins their resolved dependency graph. Set TEXT_MODEL to the exact model identifier your account permits (the checked local configuration uses gpt-5.6-luna).

Install

git clone https://github.com/phd-peter/ego-jev.git
cd ego-jev
npm ci
cp .env.example .env

Set the required values in .env; it is ignored by Git.

TYPESAFE_API_KEY=...
TYPESAFE_MODEL=jev-latest
TEXT_MODEL_API_KEY=...
TEXT_MODEL_BASE_URL=https://api.openai.com/v1
TEXT_MODEL=gpt-5.6-luna

Confirm the browser runtime before the first run:

ego-browser --version
npm run check

Run

After npm link, the package exposes the requested CLI form:

ego-jev --url https://en.wikipedia.org/wiki/Main_Page \
  --goal "Find and open the Wikipedia article about Godel's incompleteness theorems."

Without a global link, use the identical interface through npm:

npm run start -- --url https://en.wikipedia.org/wiki/Main_Page \
  --goal "Find and open the Wikipedia article about Godel's incompleteness theorems."

npm run smoke:wikipedia is the same live end-to-end command with a bounded 12-action budget. It requires both configured model credentials because the example first types a search phrase. --keep-space leaves a successful TaskSpace open; otherwise the agent calls finish({ keep: [] }) after DONE. On BLOCKED, max steps, a decision error, or an execution error, the space is left open for inspection rather than being replaced with a new one.

npm run smoke:runtime:wikipedia is the credential-free, deterministic ego-lite adapter smoke. It performs the same Wikipedia field fill, bounded wait, fresh-snapshot result selection, and ref-grounded click, then closes its TaskSpace. It tests no Jev or text-model request; its query is isolated in the example and is never part of the production policy.

Options:

--max-steps <n>    1..100; default 12
--log-dir <path>   default .ego-jev-runs
--space-name <n>   default ego-jev
--keep-space       do not close a successful TaskSpace

Supported action space

Jev operationego-lite executionCandidate source
CLICKpage.click("@ref")current button, link, checkbox, radio, menuitem, option, or combobox
TYPE_TEXTpage.fill("@ref", text)current textbox or searchbox; text is requested only after the choice
SELECTpage.selectOption("@ref", { label })an observed option nested under a current combobox
SCROLL_UP, SCROLL_DOWNpage.mouse.wheel()bounded control operation
WAITpage.waitForTimeout(500)bounded control operation
DONE, BLOCKEDno browser inputbounded control operation

page.snapshot() in the installed ego-lite API is a string containing semantic lines and markers such as [ref=3]; it is not a {content, refs} value. The adapter parses only the specified roles and normalizes a marker to @3 at execution. For TYPE_TEXT only, it also derives an exact loc=role:…[name=…] fallback from that same current role/name when the snapshot ref is a wrapper rather than the editable HTML control (observed on Wikipedia's searchbox). Jev never sees or emits either form. ego-lite invalidates refs after inputs and waits, which is why this project always observes after an action and never stores refs in history.

How a Jev step is safe

  1. ego-lite returns the current semantic snapshot.
  2. ego-state derives canonical page text; action-space derives ref-grounded candidates.
  3. jev-client sends one System One request with an operation head plus speculative operation-specific target heads. Target keys are opaque (click_1, etc.); snapshot refs are not sent.
  4. The response must contain exactly the current choice keys, finite probabilities in [0,1] summing to one, and a selected maximum-probability key. The selected target is validated again by the loop.
  5. Only a validated TYPE_TEXT calls the OpenAI Responses API. The request sends no reasoning parameter, so the model's configured default applies. Structured output constrains the result to exact JSON { "text": "..." }, which is accepted only when non-empty and at most 2,000 characters.
  6. ego-lite executes one input and returns a fresh snapshot. The JSONL trace records snapshot, Jev, text-model, and action latencies without recording generated text.

The trace is written to .ego-jev-runs/<timestamp>.jsonl and is excluded from Git. It includes snapshot, action_space, jev_decision, text_model, action_executed, and terminal records.

Verification

npm run check is offline and covers:

  • semantic snapshot parsing and ref-bounded candidate construction;
  • a Jev request containing speculative target heads but no @ref values;
  • validation rejection for an invented target;
  • loop behavior: type, click, fresh snapshots, and clean completion only after DONE.
  • an OpenAI Responses request with text.format JSON Schema, no explicit reasoning parameter, and output extraction across all response items.

The live smoke:wikipedia command is intentionally separate because it uses paid, account-scoped model calls. It is the reproducible actual-browser smoke target; its success requires a current TypeSafe key and a compatible text model. A DONE decision is not an independent assertion that a site's goal was achieved, so applications needing stronger guarantees should add a task-specific verifier outside this MVP.

On 2026-09-18 the checked local configuration completed this command in five executed actions. It used live TypeSafe Jev decisions, one live OpenAI Responses text generation with gpt-5.6-luna, navigated to /wiki/G%C3%B6del%27s_incompleteness_theorems, received DONE, and closed its TaskSpace. The redacted latency trace is local-only under .ego-jev-runs/.

Confirmed interfaces and upstream references

  • ego-lite: the installed TaskSpace/Page surface is taskSpace(nameOrId), task.page("p1"), page.snapshot(), page.click("@N"), page.fill("@N", value), page.selectOption("@N", {label}), page.mouse.wheel(), and task.finish({keep: []}). The checked source package exposes an older Playwright-like helper API, so it is intentionally not imported here. The adapter launches the documented ego-browser nodejs stdin/heredoc protocol rather than nodejs -e: in the tested macOS runtime, -e could hang when spawned by a Node parent, and the embedded console transcript was written to stderr when stdout was piped. The adapter accepts its unique structured marker from either stream.
  • Jev/System One: POST https://api.typesafe.ai/v1/systemone with Authorization: Bearer …, model, state, and typed choice questions. The response supplies answers.<id>.choice, probabilities, and confidence. The live smoke received jev-1.13.0.
  • Text model: POST https://api.openai.com/v1/responses with instructions, input, max_output_tokens, and a strict text.format JSON Schema. Response parsing scans every message item for output_text; it deliberately does not assume output[0] contains text.
  • Jev Ultrafast reference: its current model.py supplies the dynamic operation-specific action-space pattern. Its browser implementation uses Browser Harness/CDP and is not an ego-lite dependency. Its current source only maps click/fill/select as dynamic element actions; this project adds the bounded scroll/wait controls at its own loop boundary.

Both checked source repositories are MIT licensed. This repository contains an independent TypeScript implementation and no copied upstream source; see NOTICE for attribution and exact checked commits.

Current limits

  • Snapshot parsing supports only the roles in the table. Frames, shadow-root edge cases, canvas, uploads, pop-ups, nested scrolling, and custom keyboard widgets are out of scope.
  • SELECT supports only visible option labels nested beneath a snapshot combobox. ARIA listboxes are treated as clickable options when they have a ref.
  • There is no site skill, planning model, retry of browser mutations, screenshot policy, benchmark suite, or final task verifier.
  • The Wikipedia smoke confirms the requested navigation, but generic DONE remains a model decision rather than a reusable semantic success verifier.