Skip to content

[ENG-3296] Add a browser tool that shares the user's MindsHub browser - #540

Open
torrmal wants to merge 4 commits into
stagingfrom
jorge/eng-3296-browser-anton-browser-tool-skill-and-login-memory-anton
Open

torrmal wants to merge 4 commits into
stagingfrom
jorge/eng-3296-browser-anton-browser-tool-skill-and-login-memory-anton

Conversation

@torrmal

@torrmal torrmal commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

This adds the anton side of the shared browser (ENG-3188, ENG-3296).

The user's MindsHub browser instance (br-<hash>, mindshub_services#239) is a real Chromium they watch beside the chat and can take over at any time. This PR gives anton a browser tool to work in it with them.

What the model gets

The tool is browser, with these actions:

Action What it does
open Opens the browser, optionally at a URL, and lists signed-in sites. Each site shows whether cookies are still present.
run Works toward a one-sentence goal. inputs holds values to type, e.g. {"search": "MacBook Air"}.
page, act, screenshot Reads the page as [17] button "Add to Cart" lines, takes one step, or returns an annotated image.
hand_to_user The user signs in, solves a CAPTCHA or confirms something in the browser, then chooses Done.
note_login Records the site and an account hint in the browser profile and as a global profile memory. Never a credential.
close Closes the browser and keeps the profile.

The tool prompt and a new built-in browser skill cover:

  • connectors first;
  • only sites the user named;
  • never ask for or type a password;
  • hand over for sign-ins and payments;
  • confirm before buying, sending, deleting or submitting;
  • report only what the page shows.

Jev drives run

Each step is one /v1/decisions call. The new jev.decide is a generic sibling of classify, and it never raises. It asks four questions:

  • next_action: one of 9 options
  • target: which element, from a shortlist of about 40, ranked by overlap with the goal and whether it's on screen
  • needs_user
  • risky

An input_key question is added when inputs are given.

What happens with each answer:

  • Acts directly: a confident click, scroll, select or back. type and select only use a value from inputs.
  • Goes back to the model, with the page and the steps taken:
    • a sign-in wall;
    • an action or target below browser_jev_min_p (default 0.8);
    • a type with no matching input;
    • two steps that changed nothing;
    • the step limit;
    • Jev missing, which also covers every non-MindsHub provider.
  • Asks the user first: a step with risky ≥ 0.5.

browser_jev controls the rollout and defaults to shadow. In shadow, run only reads the page and records Jev's pick. The model's next act is compared against it and logged as the browser_jev_shadow event (agree on action and target, plus both probabilities). on lets Jev act, and off never calls it.

Wiring

  • ChatSessionConfig.browser is a BrowserConfig(base_url, profile="main"). When it's set, the tool is registered. Hosts set it; the CLI falls back to ANTON_BROWSER_URL.
  • Credentials are resolved on each call: the MindsHub provider's current key if there is one, otherwise minds_api_key. The worker accepts either for the instance's owner (mindshub_services#241).
  • Cloud pod:
    • TurnRequestV1.browser carries {base_url, profile}.
    • The allowlist gains browser only when that field parses.
    • The pod authenticates with its existing turn key.
    • StreamBrowserSession goes out on the wire as {"kind": "browser", ...}.
  • StreamBrowserSession(session_id, view_url, expires_at) is emitted when the tool first opens the browser. view_url is a one-hour embed URL from the worker's /_embed, so hosts can split the screen.
  • Root cause: five new sentinels: browser_bad_input, browser_stale_element, browser_unauthorized, browser_unreachable, browser_error.

Related PRs

  • mindshub_services#239 (the browser agent) and gdrive connector with google picker api #241 (bearer gate, embed tokens, sites registry)
  • mindsdb/scratchpad-controller#83 (passes browser through to the pod)
  • cowork-server and cowork follow: provisioning, the setting, response.browser_session_opened, and the split pane

Testing

  • tests/test_browser_tool.py: 26 tests, run against a fake instance served through httpx.MockTransport, with a fake Jev and a fake elicitor. They cover:
    • page formatting, the shortlist and parsing;
    • open emitting the viewer and listing sites;
    • credential fallback;
    • single steps, bad and stale steps;
    • run taking confident steps, handing back when unsure or when it needs text, stopping at sign-in walls, asking before a risky step, and stopping when steps change nothing;
    • no-Jev, shadow-agreement and off modes;
    • hand-over followed by automatic note_login and the memory write;
    • screenshots, auth errors, jev.decide, and the config and contract wiring.
  • Full suite: 4605 passed. Five test_cloud_turn_process tests fail in my local environment, and the same five fail on a clean staging checkout too.
  • ruff check passes, and uv lock --check is clean.
  • Not yet verified: a live run against a real instance. That's next, in the PR environment.

🤖 Generated with Claude Code

A `browser` tool drives the user's MindsHub browser instance (br-<hash>), a
real Chromium the user watches and takes over beside the chat. It reads pages
as numbered elements and acts on them by number.

- run: the model states a goal plus values to type; Jev (/v1/decisions)
  picks each click, scroll and select. Anything below its confidence bar, a
  sign-in wall, or a step that would commit something goes back to the model
  or the user. browser_jev=shadow (default) only records Jev's picks against
  the model's; on lets it act; off never asks.
- act/page/screenshot: single steps for the model.
- hand_to_user: the user signs in or solves a CAPTCHA in the browser, then
  Jev checks they're signed in and the login is noted.
- note_login: records the site in the browser profile and as a global
  profile memory. Never a credential.
- open emits StreamBrowserSession with a viewer embed URL, so hosts can
  split the screen.

Registered when the host passes ChatSessionConfig.browser (or the CLI sets
ANTON_BROWSER_URL). The cloud pod reads TurnRequestV1.browser and allows the
tool only then; the pod reaches the instance with its turn key. A built-in
`browser` skill covers when to act, hand over and confirm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he user

Standalone services
- BrowserConfig takes an optional `token` (a standalone service's
  BROWSER_SERVICE_TOKEN), kept out of repr. With a token the tool sends it
  instead of the MindsHub credential and needs no MindsHub connection.
- Plain http is accepted only for localhost/127.0.0.1/[::1].
- CLI: ANTON_BROWSER_URL + ANTON_BROWSER_TOKEN. TurnRequestV1.browser may
  carry the token, the way `llm.api_key` already travels.
- A refused standalone token points at the API key, not "reconnect MindsHub";
  model-facing text no longer says "MindsHub browser".

Human checks
- page.human_check spots Cloudflare / reCAPTCHA / hCaptcha verification
  pages. open, page, act and run hand such a page to the user at once
  instead of returning it to the model to click.
- Hand-overs switch the service to "your turn" (POST /sessions/:id/control)
  and back; Done in the browser viewer ends the hand-over too and retires
  the chat card. Best effort against services without the endpoint.
- A 409 human_in_control from the service becomes a "wait for the user"
  outcome (browser_human_in_control, classified in root_cause).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lucas-koontz pushed a commit to mindsdb/cowork-server that referenced this pull request Oct 18, 2026
…nton (ENG-3298)

The shared browser is a per-user MindsHub instance (br-<hash>) the agent
drives while the user watches beside the chat. This is the server's half:

- /browse/status, /browse/provision, /browse/embed replace the compat stub.
  Each calls MindsHub as the caller (hub_credential): the /instance lambda
  for status and provisioning, the instance worker's /_embed for a fresh
  one-hour viewer URL. Provisioning remembers the instance in browser_url.
- browser_enabled (per user) turns it on. Then the desktop harness passes
  anton a BrowserConfig and web turns carry params.browser, so anton
  registers its browser tool. Neither carries a credential.
- StreamBrowserSession becomes response.browser_session_opened on both
  paths (a pod's "browser" turn_step maps to the same event), so the
  renderer can split the chat and show the live viewer.

anton-agent temporarily tracks mindsdb/anton#540's branch so this PR's
environment runs the browser tool; it goes back to main before merge.

Refs: ENG-3298

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
torrmal and others added 2 commits October 19, 2026 18:46
A failed or no-op step usually means a modal, cookie banner or overlay is in
the way, which the page list doesn't make obvious. Still on request only,
never after every step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y for secrets

- act takes drag, and x/y clicks (right, double) for canvas apps; x/y are
  pixels on the latest screenshot, which now says its size.
- hand_to_user is for sign-ins, CAPTCHAs and payments only: when stuck, say so
  in the chat instead of giving the user the work.
- Skill: for pictures in canvas apps, make the image and insert it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant