Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,56 @@ Python 3.10 or newer, anywhere that can reach the server. Same CLI as the npm pa

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

## Optional Jev web decisions (Python)

The Python client has an opt-in `aether_browser.jev` layer for **Jev → your selected model**.
Jev reads a caller-selected text excerpt and chooses between up to three caller-approved URLs,
handoff, or human takeover. The browser navigates only to a URL the caller supplied, and its
normal destination checks still apply. On handoff, **your callback** receives the current page
evidence and Jev's request IDs, token counts, and costs; your application invokes its chosen
reasoning model to write, analyze, or continue browsing. Jev returns decisions, not prose or
visual judgments, so screenshots and complex page interactions belong to that selected model or
the human watching the live browser.

```python
import os

from aether_browser import AgentBrowser, session
from aether_browser.jev import HumanTakeover, JevWebAgent, NavigationOption, OpenRouterJev

browser = AgentBrowser(controller_token=os.environ["AGENT_BROWSER_CONTROLLER_TOKEN"])
with session(browser) as live:
first_page = live.navigate("https://example.com")
result = JevWebAgent(OpenRouterJev(os.environ["OPENROUTER_API_KEY"])).run(
live,
goal="Read the site's documentation",
initial_page=first_page,
options_for=lambda page: (
[NavigationOption("https://example.com/docs", "Official documentation")]
if page["final_url"] == "https://example.com/"
else []
),
excerpt_for=lambda page: page["readable_text"][:6000],
selected_model=lambda handoff: your_model(handoff), # Supply your own model callback.
)
if isinstance(result, HumanTakeover):
print("Take control at", result.view_url)
input("Press Enter when the human is done to close this session: ")
else:
print(result)
```

The example's `your_model` is an application-defined function, not part of this package. The
`excerpt_for` callback explicitly chooses text sent to OpenRouter, and `options_for` explicitly
chooses candidate URLs. Never return private page content or signed URLs unless your application
intends to send them to that provider. The OpenRouter key stays in the calling process; the
browser server does not store it. No Jev request occurs unless you call this optional layer.
If Jev fails or returns an invalid decision, the selected-model callback receives a handoff
with `reason="jev_unavailable"` and the browser takes no additional action. Recognized authentication
or payment pages prompt human takeover before Jev receives their text. The session remains owned
by your code; keep it open while the human takes over. See [`docs/JEV.md`](docs/JEV.md) for
the complete contract and limitations.

## What makes it different

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

- No hosted cloud service, cloud control plane, or production remote-hosting claim.
- No bundled LLM, account system, dashboard, credential vault, or credential injection.
- No bundled LLM or default model calls, account system, dashboard, credential vault, or credential injection.
- No CAPTCHA bypass, anti-detection guarantee, stealth claim, or proxy rotation.
- No arbitrary JavaScript, shell, filesystem, upload, clipboard, download, or raw CDP API.
- No multi-session pool, ATS integration, trading integration, or brokerage behavior.
Expand Down
12 changes: 12 additions & 0 deletions clients/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ Connection settings fall back to `AGENT_BROWSER_URL`, `AGENT_BROWSER_CONTROLLER_
`AGENT_BROWSER_OBSERVER_TOKEN`, so `AgentBrowser()` works with no arguments in a configured
environment.

## Optional Jev decision layer

`aether_browser.jev` can make bounded navigation choices before handing page evidence to the
model chosen by your application. It uses a separate OpenRouter key, runs in the calling process,
and sends only the text you select in `excerpt_for` plus the goal, page URL/title, and up to three
URLs supplied by `options_for`. It never sends screenshots to Jev. Jev cannot generate answers or
invent browser actions. Your `selected_model` callback does the reasoning and generation after
Jev chooses `handoff` or is unavailable. Human takeover leaves the browser session open.

See the [root README example](../../README.md#optional-jev-web-decisions-python) and
[contract and limitations](../../docs/JEV.md).

## Two roles, kept separate

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