Imported from cavi-ai/bobby-browser (
skill/SKILL.md). Install upstream withnpx skills add cavi-ai/bobby-browser --skill skill. Copyright stays with the author.
bobby-browser is a browser automation runtime, not an agent. You drive it through MCP tools; the runtime verifies every effect and returns evidence. Never claim an action worked without its evidence.
Start here
On MCP initialize, the server's instructions names the canonical first
calls. Rules that govern every call:
- Always read
structuredContentbefore the next mutating call. Astatusthat is notcompletedis a failure even when prose looks optimistic (isError: truemarks it too). Repair fromerror.repair({action, doc}); the doc points intobobby://failure-taxonomy. - Pull references on demand, not upfront. All match the build:
bobby://intents(the ten intent tools and what each verifies),bobby://primitives,bobby://failure-taxonomy,bobby://capabilities,bobby://job-handlers. - Batch deferred-tool discovery in one search. If your host defers MCP
tool schemas behind a tool search, issue ONE search selecting every tool
the task will need (
select:accepts a comma-separated list) — each extra round trip is a full model turn. The explore toolset already advertises the standard loop (observe, navigate, click, type, upload, dialogs, downloads,intent_follow,intent_complete_form,intent_submit_and_verify,intent_detect_challenge); search only for what is genuinely missing.
Core loop
workflow_start(optionally withurl) creates session + page + workflow and returns aworkflowHandle. Use it over manualsession_create+page_open. Preferworkflow_observefor context: it answers from retained page memory first and only pays for a live snapshot when nothing is remembered.- Read before write. On a site this runtime has seen before,
context_askfirst — a remembered answer (markedpersisted) beats a snapshot. Otherwisea11y_snapshot, and pass its targets straight into intent or primitive calls — never guess selectors. - A snapshot node's
target{role, accessibleName, ordinal}goes verbatim into anyintent_*hintsor a primitive'starget. Keepordinal; it is what separates duplicate role/name pairs. - Scope big pages: pass
target: {role: "main"}toworkflow_observeto skip repeated site chrome, andtargetona11y_snapshotto scope to one form or dialog. - Trim observation payloads when it counts.
workflow_observe,intent_complete_form,intent_submit_and_verify, andintent_followdefaultevidenceDetail: "compact"on success; pass"full"only when debugging. - Pass the returned
workflowHandleon later calls.sessionId/pageId/workflowIdare the repair path if the handle stops resolving (handles expire with the server generation; explicit ids survive).
Choosing the tool
- Link or control with a result to verify: Use one
intent_followwithexpectedState; do not split it intoclickpluswait_foror another observation. - Form with multiple fields: one
intent_complete_form(fields resolve just-in-time; include conditional fields after their revealer even if initially absent) — never aintent_fillper field unless fields must resolve in reaction to each other. - Submit:
intent_submit_and_verifywith anexpectedStatethat only holds after the submit (a confirmation id, status change, or new element). - Data out:
intent_extract(named fields, per-field errors) orextract_structured(schema-shaped JSON via vision — needsvision:assist). - A popup/overlay blocks the page:
intent_dismiss_obstruction. - A captcha or verification widget blocks the page:
intent_detect_challenge(also advertised in explore) classifies it read-only;intent_solve_challengeruns the vision solve loop. Both needvision:assistplus the session'sexecutionPolicy.visionAssist— the capability alone is not enough. The runtime never bypasses a challenge; when the solve loop cannot clear it, surface the page to the operator.
Rules that bite
- Boundary submits are checkpoint-gated.
intent_submit_and_verifyandintent_followwithboundary: truerefuse to run without a matching checkpoint.autoCheckpointdefaults totrueand mints it inside the same call. Hand-author one (autoCheckpoint: false) only to attachinvariants/replayableInputs; put commands you already ran inevidenceRefs— never hand-authored evidence. - Pass the
workflowHandle. Later calls take it;sessionId/pageId/workflowIdare repair if the handle dies. Lost it?recovery_statuswithsessionIdlists that session's recoverable workflows, newest first. Pass an echoedworkflowIdonly intocheckpoint_save/workflow_recover. - Fail-closed by design.
verificationFailedmeans the action ran but the expected state was not proven — re-read (inspect,form_snapshot) instead of retrying blindly.needsReconciliationmeans the side effect may have landed — callrecovery_status, never replay the command. - A browser hiccup is not your problem to fix.
targetDetached/ "browser page is not open" after a transport reset means the runtime already reattached (page state, including typed values, is preserved —cdpReattachevidence) or relaunched (state wiped, page reloaded to its last URL). Re-observe, then continue; do not restart the whole flow, and do not re-run anything aneedsReconciliationalready reported as possibly-landed. - Artifacts are evidence. Screenshots, PDFs, HAR captures, and downloads
come back as digest-verified artifacts (
artifact://<id>). When a download must land as a file, passsaveAstodownload_url— it rejects escapes or overwrites before fetching, andsavedTo+sha256mean no shell verification is needed.
Error signals
| Signal | Meaning | Repair |
|---|---|---|
missingCapability |
Token lacks a required capability | Re-issue credential with that capability, or pick a covered tool |
authenticationFailed / tokenExpired |
Credential bad or expired | Operator re-runs bobby init --force; reconnect |
targetNotFound / targetAmbiguous |
Stale or guessed target | Fresh a11y_snapshot; pass the new target verbatim |
verificationFailed |
Action ran; expected state not proven | Re-inspect; adjust expectation or fill; do not blind-retry |
boundaryAlreadyExecuted |
A prior submit for this workflow + control completed | Inspect the named prior outcome; pass reSubmit: true only for a genuinely intended second effect on the same control |
needsReconciliation |
Side effect may already have landed | Call recovery_status; never retry the Boundary command |
targetDetached / page-level notFound |
Transport reset or stale page | Reattach/relaunch already handled; re-observe, continue with current ids |
deadlineExceeded |
Deadline elapsed | Longer deadline; retry only if Replayable |
idempotencyConflict |
Same key, different body | Mint a fresh idempotency key |
When unsure, open bobby://failure-taxonomy — tool descriptions give the
precise repair for their failure modes and win over this table.
Anti-patterns
- Claiming success from a chat summary without
status: completedevidence. - Blind-retrying after
verificationFailedor anyneedsReconciliation. - Inventing CSS/XPath selectors instead of snapshot targets.
- One
intent_fillper field whereintent_complete_formwould do. - Re-logging into sites every session instead of using the operator's paired Firefox profile (disposable Chromium profiles keep no cookies — see below).
- Hand-authoring
evidenceRefsor forging artifact digests. - Retrying the same tool with the same token after
missingCapability.
Engine and persistence (skim once)
The gateway resolves the browser engine at startup: an explicit
AUTOMATION_RUNTIME_BROWSER_SELECTION, else the operator's paired enrollment
(browser-selection.json), else fail-closed. Two engines result:
- Firefox companion: real headed Firefox, persistent profile — cookies and logins survive sessions. Sign in once in that window.
- Managed Chromium: disposable per-session profile. Nothing persists. If logins vanish between sessions, ask the operator to Pair the Firefox companion instead of re-authenticating every run.
Setup, pairing, and credential minting are operator tasks (bobby install,
bobby doctor); if the runtime is unreachable, say bobby doctor names the
broken piece. Scheduler jobs (job_submit/job_status/job_cancel) are
scheduler probes, not browser intents — see bobby://job-handlers.