Skip to content

Commit dab96a9

Browse files
committed
Add opt-in Jev navigation handoff to Python client
1 parent 9981040 commit dab96a9

5 files changed

Lines changed: 743 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,54 @@ Python 3.10 or newer, anywhere that can reach the server. Same CLI as the npm pa
225225

226226
See [`clients/python/README.md`](clients/python/README.md).
227227

228+
## Optional Jev web decisions (Python)
229+
230+
The Python client has an opt-in `aether_browser.jev` layer for **Jev → your selected model**.
231+
Jev reads a caller-selected text excerpt and chooses between up to three caller-approved URLs,
232+
handoff, or human takeover. The browser navigates only to a URL the caller supplied, and its
233+
normal destination checks still apply. On handoff, **your callback** receives the current page
234+
evidence and Jev's request IDs, token counts, and costs; your application invokes its chosen
235+
reasoning model to write, analyze, or continue browsing. Jev returns decisions, not prose or
236+
visual judgments, so screenshots and complex page interactions belong to that selected model or
237+
the human watching the live browser.
238+
239+
```python
240+
import os
241+
242+
from aether_browser import AgentBrowser, session
243+
from aether_browser.jev import HumanTakeover, JevWebAgent, NavigationOption, OpenRouterJev
244+
245+
browser = AgentBrowser(controller_token=os.environ["AGENT_BROWSER_CONTROLLER_TOKEN"])
246+
with session(browser) as live:
247+
first_page = live.navigate("https://example.com")
248+
result = JevWebAgent(OpenRouterJev(os.environ["OPENROUTER_API_KEY"])).run(
249+
live,
250+
goal="Read the site's documentation",
251+
initial_page=first_page,
252+
options_for=lambda page: [
253+
NavigationOption("https://example.com/docs", "Official documentation")
254+
] if page["final_url"] == "https://example.com/" else [],
255+
excerpt_for=lambda page: page["readable_text"][:6000],
256+
selected_model=lambda handoff: your_model(handoff), # Supply your own model callback.
257+
)
258+
if isinstance(result, HumanTakeover):
259+
print("Take control at", result.view_url)
260+
input("Press Enter when the human is done to close this session: ")
261+
else:
262+
print(result)
263+
```
264+
265+
The example's `your_model` is an application-defined function, not part of this package. The
266+
`excerpt_for` callback explicitly chooses text sent to OpenRouter, and `options_for` explicitly
267+
chooses candidate URLs. Never return private page content or signed URLs unless your application
268+
intends to send them to that provider. The OpenRouter key stays in the calling process; the
269+
browser server does not store it. No Jev request occurs unless you call this optional layer.
270+
If Jev fails or returns an invalid decision, the selected-model callback receives a handoff
271+
with `reason="jev_unavailable"` and the browser takes no additional action. Recognized authentication
272+
or payment pages prompt human takeover before Jev receives their text. The session remains owned
273+
by your code; keep it open while the human takes over. See [`docs/JEV.md`](docs/JEV.md) for
274+
the complete contract and limitations.
275+
228276
## What makes it different
229277

230278
- **One session, two participants.** The agent acts through JSON. You watch the same display, and
@@ -302,7 +350,7 @@ and credential injection are excluded from the public core. Provenance status is
302350
## What it does not do
303351

304352
- No hosted cloud service, cloud control plane, or production remote-hosting claim.
305-
- No bundled LLM, account system, dashboard, credential vault, or credential injection.
353+
- No bundled LLM or default model calls, account system, dashboard, credential vault, or credential injection.
306354
- No CAPTCHA bypass, anti-detection guarantee, stealth claim, or proxy rotation.
307355
- No arbitrary JavaScript, shell, filesystem, upload, clipboard, download, or raw CDP API.
308356
- No multi-session pool, ATS integration, trading integration, or brokerage behavior.

‎clients/python/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ Connection settings fall back to `AGENT_BROWSER_URL`, `AGENT_BROWSER_CONTROLLER_
4848
`AGENT_BROWSER_OBSERVER_TOKEN`, so `AgentBrowser()` works with no arguments in a configured
4949
environment.
5050

51+
## Optional Jev decision layer
52+
53+
`aether_browser.jev` can make bounded navigation choices before handing page evidence to the
54+
model chosen by your application. It uses a separate OpenRouter key, runs in the calling process,
55+
and sends only the text you select in `excerpt_for` plus the goal, page URL/title, and up to three
56+
URLs supplied by `options_for`. It never sends screenshots to Jev. Jev cannot generate answers or
57+
invent browser actions. Your `selected_model` callback does the reasoning and generation after
58+
Jev chooses `handoff` or is unavailable. Human takeover leaves the browser session open.
59+
60+
See the [root README example](../../README.md#optional-jev-web-decisions-python) and
61+
[contract and limitations](../../docs/JEV.md).
62+
5163
## Two roles, kept separate
5264

5365
The server splits authority, and this client keeps that split visible in your code. The observer

0 commit comments

Comments
 (0)