Brains & tools

Browser

Give an AI employee a real, persistent web browser. Real Google Chrome runs inside the App container; the employee reads pages as ref-annotated snapshots and acts on them with a small set of browser_* tools, while you watch live and take over whenever a human is needed.

Reading the web without a browser

Most of the time an employee does not need a browser at all — it needs to find a page and read it. Three tools do that for every employee, with no Chromium, no setup, and no toggle:

search_web
Search the public web and get back titles, URLs and snippets.
fetch_web_page
Read one page as plain text. HTML, plain text, JSON and PDF pages are all extracted.
download_web_file
Download a file and keep it as a chat attachment — a blank form the employee can then fill in with the PDF tools and attach to an email.

That last one is the useful chain: an employee working an email thread can find the current version of a form online, download it, complete it from what your company already knows, and attach the finished file to a Gmail draft. See AI chat on every email.

Every request goes through the same outbound guard as the rest of the product: http(s) only, no credentials in the URL, and every redirect re-checked so a link cannot be used to reach loopback, private, or cloud-metadata addresses inside your network. Fetched content is fed to the model as untrusted data, and the employee is told plainly that a web page giving it instructions is a stranger talking, not its teammate. Operators who want this off entirely — or search off while direct fetches stay — set web.enabled and web.searchProvider in config.ts. Search uses DuckDuckGo's no-JavaScript endpoint by default, so no API key or account is involved.

Everything below is about the heavier capability: a real browser that holds a session, clicks, and fills forms.

Enabling it

Browser access is off by default. Open the employee, go to Settings → Browser, and flip on Browser access. The same card holds the two shaping controls: the allow list (which hosts the employee may open) and require approval for form submits (a human-in-the-loop gate on anything that sends data). A Routine can override the toggle per schedule — useful for an employee who may browse during a nightly research run but not in ad-hoc chat.

The browser an employee drives does not have to be the one inside the container. A Member can connect a Chrome running on their own computer and point a conversation at it, so the employee works from that machine and its signed-in sites, with the same tools and the same live view. It is a dedicated Chrome profile rather than their everyday browser, and it comes with a stricter policy of its own — see Member browsers.

The tools

When enabled, the employee's tool list grows by the browser set. Every action returns a fresh snapshot of the page, so the employee always acts on current state:

browser_open
Navigate to a URL (gated by the allow list) and snapshot the loaded page.
browser_snapshot
Re-read the current page without acting — e.g. at the start of a new turn.
browser_click
Click an element. If the click opens a new tab, the browser follows it automatically.
browser_fill
Type into an input or textarea, replacing its contents.
browser_fill_vault
Fill a username or Login password from an explicitly granted Vault item. Passwords go only into password inputs, and the App never returns plaintext to the model.
browser_select
Choose an option in a native dropdown by value or visible label.
browser_press
Press a key — Enter, Tab, Escape, arrows.
browser_hover
Hover to reveal menus or tooltips; the hover holds so a follow-up click works.
browser_scroll
Scroll by a viewport (fires real wheel events, so lazy-loaded and infinite-scroll pages load more content) or bring a specific element into view.
browser_back
Go back one page in history — the recovery move after a misclick.
browser_wait
Wait for a selector to appear (up to 15s) or pause a fixed time, instead of polling with snapshots.
browser_save_vault_login
Request owner/admin approval to capture a same-origin password input into a new restricted Vault login without reading it back through the model.
browser_screenshot
A JPEG of the viewport when layout matters. It is unavailable after the session has observed or filled a password; use the redacted snapshot then.
browser_submit
Submit a form. With approval mode on, queues an Approval instead of firing.
browser_resume
Run an approved submit or restricted Vault capture in its original Browser session and page.
browser_close
Shut the browser down (skipped while a human is watching the live view).

Snapshots and refs

A snapshot is a YAML outline of the page in which every interactive element carries a stable marker like [ref=e12] — including elements inside iframes. The employee acts on a ref directly by passing aria-ref=e12 as the selector, which resolves instantly and unambiguously; CSS and text selectors work too as fallbacks. The outline covers the whole page, not just the viewport; on very large pages it is capped with a note saying how many elements were omitted, and the employee narrows down by interacting with a section or navigating to a more specific URL.

Events the employee could not otherwise see — a JavaScript dialog that was auto-dismissed, a popup tab that was adopted, a selector that matched more than one element — are surfaced as NOTE: lines at the top of the next snapshot.

Password-input values are removed from snapshots before they reach the model, including password fields inside frames. Once a password has been observed or filled in the session,browser_screenshot refuses to create a model-visible image; the structural snapshot remains available with the password redacted.

The allow list

One host pattern per line; blank means unrestricted. Lines starting with # are comments. Matching rules:

  • mail.google.com — that exact host, and nothing else. Use this to pin a single host.
  • *.github.com — the apex github.com and every subdomain (www.github.com, gist.github.com, …).
  • app.*.example.com — a glob; each * spans a single label and never crosses a dot.

The list is enforced server-side on browser_open, on the live view's address bar during take-over, and intersected with Vault autofill and capture checks on the live top page and target frame. Edits apply immediately — no restart needed — and neither a Vault Grant nor a human holding control widens this Browser policy.

Vault autofill and capture

A granted Vault login removes the need to paste a password into Chat or type it during take-over. The employee first calls list_vault_items for safe metadata, opens the saved website, then calls browser_fill_vault for the username or password field. The App resolves the current item-level Grant and types the value directly into Chrome. The tool result only confirms that the field was filled.

Autofill is bound to the login's exact saved origin: scheme, host, and port must match on both the top page and target frame. The stored password is accepted only from a Login item and only into an input with type=password; API keys and secure-note bodies are not Browser-fill sinks. A missing or revoked Grant fails closed. Browser access and the host allow list still apply independently, so a Vault Grant cannot turn the Browser on or widen where it may navigate.

For signup and password-generation flows, browser_save_vault_login captures the current value of a same-origin password input only after a company owner or admin approves the request. This approval is mandatory even when ordinary form-submit approval is off. The result is a restricted Vault item bound to the current origin, with a Manage Grant for the employee; other Members do not see it until an owner or admin changes its access. The password is neither read back nor included in model output. An employee can instead use create_vault_login to have Genosyn generate and encrypt a company-visible password before filling it into the page.

Approval-gated submits

With require approval for form submits on, a browser_submit does not fire. It queues an Approval — visible in the company Approvals inbox with the page URL and a one-line summary of what the employee is trying to do — and the employee is told the submission is pending. Once you approve, the employee re-fires it with browser_resume, in the same turn or a later one. The approval is bound to the page it was raised on and fires exactly once. Genosyn claims the approval atomically before touching the page, so concurrent resumes cannot submit it twice. If a browser process or network connection fails after that claim, Genosyn treats the outcome as unknown and will not replay it automatically; raise a new submit for another reviewed attempt. If the browser moved to a different page before the claim (or was reclaimed while idle), the employee is asked to submit again rather than firing blindly against whatever is now loaded. Rejecting writes the decision to the employee's journal. Only owners and admins may open or decide these requests, and deciding requires recent primary and second-factor authentication in a logged-in browser session rather than an API key.

Live view and takeover

While the employee browses, the chat panel shows the page live. Click Take over to drive it yourself — your mouse and keyboard go straight to the same Chrome. Use Vault autofill for a granted stored credential; take-over remains the fallback for a credential not in the Vault and the intended flow for captchas and 2FA. The employee navigates to the right page, you complete the human-only step, and the employee carries on. The browser is never torn down while someone is watching.

Taking over also unlocks the address bar above the page, along with back, forward and reload. Type a URL and press Enter to go somewhere the employee did not — useful when a sign-in bounces you to a settings page the model never opened. Ctrl/+L focuses it, as it would in a real browser. It is the same Chrome carrying the same cookies, so the address bar answers to the same allow list browser_open does: a host the company excluded is refused here too, with the reason shown under the bar. While you are only watching, the bar shows the current URL and nothing else.

What persists

The browser outlives individual chat turns — "I'll wait while you sign in" genuinely works, and an idle browser is reclaimed after five minutes once nobody is using it. Cookies and local storage are snapshotted per employee under the company data directory, so a login survives new conversations and container restarts. Model credentials are never involved; see Configuration for where data lives on disk.

That snapshot is written whenever a session is torn down — including on SIGTERM, so stopping or updating the container flushes every live browser before it exits rather than dropping whatever the session had learned since it started. Page loads also trigger a debounced save, which bounds what an ungraceful kill can cost to the last page. Two things are deliberately not kept: IndexedDB and service-worker storage, so a site that keys its auth off those needs a fresh sign-in; and Chrome's own profile directory, which is new on every launch, so the HTTP cache always starts cold.

That per-employee session is also what browser-login Connections use. When a site challenges a stored-password sign-in with a captcha or a 2FA prompt, the fix is to take over here and sign in once — the Connection picks up the session you established and stops failing.

The browser it actually runs

The App image ships real Google Chrome — the same build Google publishes for Debian, on both x86-64 and ARM — and runs it headed against a virtual display started by the container's entrypoint. That is a deliberate anti-blocking choice, not an implementation detail.

Sites rarely detect "automation" as such. They detect contradictions: a browser claiming to be Chrome on macOS while its fonts, GPU strings and navigator.platform all say headless Chromium on Linux. Genosyn used to ship exactly that — a Chromium wearing a hand-written costume — and the costume was what got it challenged. A real Chrome needs no costume, so there is none to catch out: its user agent, client hints, font list and renderer strings agree with each other because they are all simply true.

Genosyn still never solves a captcha and never defeats a challenge. This only removes false signals from a browser doing legitimate work. If a site challenges the employee anyway, the answer is still a human: take over here, or use a Member browser.

Nothing here needs configuring. The knobs exist in config.ts under browser if you need them — a different Chrome binary, forced headless on a host that cannot run a virtual display, or a locale and timezone matching where your deployment egresses from. Leave them empty and Chrome tells the truth about itself, which is the setting you want. A source-managed install on a host with no Chrome falls back to whatever Chromium it finds, and only then does a compatibility layer start filling in the differences.