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). The submit gate is on by default for newly created employees — the open web is exactly where hostile content meets side effects, so a human turns it off deliberately; employees created before that default flipped keep their existing setting. 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, Login password, or current authenticator code from an explicitly granted Vault item. Passwords and codes go only into matching sign-in 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_prepare_vault_totp
- Arm an AI-created Login for TOTP enrollment before asking the site to reveal its setup key or QR. Screenshots and model-visible page text are redacted immediately.
- browser_save_vault_totp
- Capture the prepared, same-origin authenticator setup key or QR into that Login without returning the setup key.
- browser_create_vault_passkey
- Run the selected site's passkey-registration action inside a temporary software authenticator and encrypt the resulting credential into the Login as one bounded ceremony.
- browser_use_vault_passkey
- Load one granted, origin-bound Vault passkey, trigger the selected sign-in action, save its counter, and remove it from Chrome before returning.
- browser_screenshot
- A JPEG of the viewport when layout matters. It is unavailable after the session has observed a password, one-time code, authenticator setup, or any QR; use the redacted snapshot then.
- browser_submit
- Submit a form. With approval mode on, queues an Approval instead of firing.
- browser_submit_with_vault_totp
- Claim the bound action, generate a fresh Vault one-time code, fill it, and submit immediately; approval mode queues this before any code is generated or filled.
- browser_resume
- Run an approved submit, restricted Vault password capture, TOTP submit, or one-shot passkey create/use action 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 and Vault-supplied authenticator values are removed from snapshots before they reach the model, including fields inside frames. Once a password or TOTP setup value has been observed or filled in the session, browser_screenshot refuses to create a model-visible image; the structural snapshot remains available with sensitive values 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 apexgithub.comand 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 passwords, authenticator codes, and passkeys
A granted Vault login removes the need to paste a password or current authenticator code 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, password, or totp field. The App resolves the current item-level Grant, generates a fresh code when needed, 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 into an input with type=password, while a current authenticator code goes only into an ordinary sign-in input; 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.
If the site offers TOTP during signup, the employee first calls browser_prepare_vault_totp on its AI-created Login, then asks the site to reveal enrollment. This immediately redacts screenshots and model-visible page text. The employee then calls browser_save_vault_totp on the same-origin setup key, authenticator QR image, or containing element. Genosyn validates and encrypts the setup server-side; neither the setup key nor a generated code appears in model output. A Member can also paste a shown Base32 key or otpauth:// URI through the Vault editor.
A Vault passkey is created inside Genosyn rather than imported from a person's device.browser_create_vault_passkey takes the site's Create passkey control and performs registration, capture, encryption, and browser cleanup as one bounded action. On a later login, browser_use_vault_passkey takes the site's sign-in control, loads only that granted RP-bound credential, completes the assertion, persists its updated signature counter, and removes it from Chrome before returning. No private key enters the MCP child, model context, transcript, audit detail, or log.
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.
For a TOTP-protected form, use browser_submit_with_vault_totp. It queues the Approval while the one-time-code field is still empty, then generates and fills a fresh code only after the approved action is claimed and submits immediately. This avoids an expired code or a changed form fingerprint while the Member is reviewing the Approval.
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 actions for a granted password, authenticator code, or software passkey; take-over remains the fallback for a credential not in the Vault, captchas, hardware-bound passkeys, and unsupported challenges. 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.
The panel keeps whatever you last did to it. Collapse it to a rail, or hide it with the × in its header, and it stays that way through a reload and through coming back to the thread later — a reload is not a request to reopen a browser you sent away. Hiding one session does not hide the next: when the employee opens a browser again, the panel comes back.
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.
Live view is ephemeral in Chat. A Routine Run that actually uses a browser is different: Genosyn automatically saves a silent visual MP4 of each Run-linked browser session and shows it beside that Run's log. Merely giving the employee Browser access does not create a recording; capture starts only when the session opens the browser. Parallel delegated browser work produces separate recordings, and none of them contain page audio.
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.
Vault passkeys do not depend on Chrome's profile directory. Their encrypted credential material lives with the Login in the database and is loaded into a temporary software authenticator only for a granted, exact-RP Browser action.
Saved Routine recordings are separate from browser state. They live under .private/browser-recordings/<company-id>/<run-id>/ in the data directory, outside the AI Employee's working tree. They remain with Run history, are included in whole-instance backups, and are removed when the owning Routine or company is deleted. Chat sessions are not recorded.
That per-employee session is also what a sign-in driven from the Vault uses. When a site challenges a sign-in with a captcha or an authenticator not attached to the Login, the fix is to take over here and sign in once — the employee 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.
App-owned Chrome opens in a larger 1600 × 1000 window. The page's usable viewport is slightly shorter because Chrome keeps its normal browser controls. A Member browser keeps the real size of its window instead. The live preview scales the whole page to fit: drag the panel's left edge wider or use Open in new tabwhen you want a larger view. Routine recordings stay capped at 1280 × 800 and preserve the page's aspect ratio, so the larger working area does not increase recording storage.
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.
A real browser is only half of it; the other half is how it is driven. A person types a password one key at a time and moves the pointer to a button before pressing it. A sign-in page watches for exactly that, and a field that fills in a single keystroke-free burst from a pointer that never moved is read as automation even when the browser itself is an honest Chrome — which is why an employee gets challenged from the same address you sign in from cleanly. So the tools type character by character with small randomized gaps and approach a control with the pointer before clicking. This changes only how the input arrives, never what: a Vault credential is typed into the same field, is never revealed to the employee, and stays redacted from snapshots and recordings.
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, a locale and timezone matching where your deployment egresses from, or humanize to switch off the character-by-character typing and pointer approach in a trusted environment that wants raw speed. Leave them at their defaults and Chrome tells the truth about itself while behaving like a person, 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.