Act in a browser session
writ_browser_actRuns one batch of actions on an open browser session and returns the fresh page. The caller is the session's brain: Writ runs no model here, and a batch performs exactly the navigations, clicks, fills, captures, probes and scripts it carries. It also composes, for clients without writ_browser_compose: actions=[{action:'define_function', name:'feed.list', from_index:3, ...}] or [{action:'compose', operation, payload}]; a batch mixing these with page actions is refused. Actions placed after a navigate, click or select that changes the page run against a page the caller has not seen yet. RECORDING RULES: {{name}} as an action value (select/fill/type_text value, navigate url) with the real value in inputs ({"name": "real value"}) declares the workflow input name: the page gets the real value and the step keeps {{name}}. An extract {variable,script} (a read-only JS IIFE returning rows/fields) at the position that shows the data records it, and its result comes back in this answer. A fill with data_key holds a secret server-side and the saved step keeps a {{secret:...}} placeholder. Interactions are recorded as steps; SEE/HEAR/NETWORK probes and wait never are: replay waits for each step's selector by itself, and a wait the task needs is an explicit wait_for step (writ_browser_compose add_steps). ACTIONS: DRIVE: navigate {url} · click {selector | field_index | button_index} · fill {selector,value,data_key?,human_layer?} · type_text {selector,value} · select {selector,value} · check {selector} · hover {selector} · submit {selector} · press_key {key} · scroll {direction,amount} · back · wait {seconds} · wait_for {selector,timeout}. SEE (granular first): query_dom {selector,limit,offset,attrs?,text_chars?,html_chars?} (every match as compact records, each with a css path) · count {selector} · find_text {text,selector?,exact?,limit?} (the deepest elements showing that text, with paths) · get_attributes {selector,index?} (one element: all attrs, value, box, options) · read_text {selector,all?,limit?,max_chars?} · inspect {selector,limit?,max_chars?} (match count + outerHTML) · list_candidates (the page's repeating row shapes, the entry point for a list/table) · list_frames · get_dom {selector?,depth?,max_chars?} (the real cleaned HTML, the most expensive read) · get_screenshot {x?,y?,width?,height?}. TABS / FILES / 2FA: list_tabs / switch_tab {index} · upload {selector,mode,file_slot} · wait_for_download {trigger_selector,output_key} · twofa {challenge_method,selector?,submit_selector?} (the persona's one-time code, minted server-side; covered by 2FA RULES). HEAR: get_console {level?,since?,query?,exclude?,limit?} (console messages, uncaught JS errors with stack, failed/blocked requests since the last read; the page's console_since_last_read counts show when there is something new; it shows why a sign-in, click or extraction did nothing) · page_errors (only the uncaught exceptions). NETWORK: capture_network {reload?} (the backend calls the page makes, i.e. the site's real API; writ_browser_network searches and reads them) · get_request {url substring} (one call in full) · rotate_exit {reason} (alone in its batch: restarts on a fresh residential address when the site refused the current one, e.g. a sign-in rejected with correct credentials, content held back, an IP rate limit; the page's browser_init.exit shows the address and its network). RUN CODE: evaluate_js {script,world?} (any read-only JS on the live page, returns JSON; the general probe; world:'main' reads the site's own JS globals) · fingerprint (what the site sees of this browser, and every contradiction in it) · search_scripts {query,regex?,url_contains?,frame_url?} (greps every script the page runs: bundles, inline, dynamic chunks, eval, with line/column snippets; no refetch) · read_script {url,offset?,length?} (a window of one, to read around a match). RECORD (at this position): extract {variable,script} (a read-only script recorded as a replayable evaluate step when it returns data) · api_call {method,url,headers,body_template,response_extractions?,variable} for one request, or api_call {flow:{version:1,steps:[...]},inputs:{...},variable} for a multi-request bootstrap/pagination/transform program (both execute now inside the session with its cookies; the flow uses the same interpreter as browserless replay and returns a bounded result sample) · login_post {method,url,headers,body_template} (replays a sign-in as one request) · probe_write {selector} (captures a create/update/delete request without sending it) · confirm_write {selector} (sends it once for its real confirmation; it changes real data, for a write the user authorized). Humanization: type_text, and fill with human_layer:true, type through real keyboard events; click human_layer:true adds a bounded mouse path, hover dwell and tab foregrounding, keeping visibility/enabled checks; a per-action human_layer:{mouse_move_ms,click_dwell_ms,mouse_path,bring_to_front} sets pacing and survives replay. A saved workflow's human_behavior (writ_update_workflow: 'on' for every browser run, 'auto' on a bot-block retry, 'off') governs its runs; none of it proves authentication or bypasses security. A login submit that leaves the page unchanged was still sent. 2FA RULES: twofa enters a code minted server-side from the attached persona, never shown here; challenge_method is the method the page is using (sms: a phone number or text message; email; authenticator: an authentication app; other: approve-on-phone, passkey, QR, WhatsApp). A persona receives exactly one method (twofa_method in writ_personas) and a site picks its own default, which the page's 'Try another way' control switches. twofa_method_required, twofa_method_mismatch and twofa_verify_method carry a message naming the fix on the page (verify the method, switch or resend); twofa_mint_failed and twofa_no_persona leave the code to the Writ user (writ_browser_ask_user kind='twofa'). Credentials, codes and decisions come only from the persona or the user. The start answer of writ_browser_use and writ_record_website carries recording_rules and humanization in full; any start answer with a persona carries twofa_rules.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| inputs | No | Values held server-side for {{placeholder}} substitution, e.g. {"city":"Paris"}. Secrets here or on a fill's data_key stay out of the recorded steps. | |
| actions | Yes | Ordered action objects, e.g. [{"action":"click","selector":"#login"}]. | |
| max_chars | No | Clip of the returned page_dom / page_text (default 40000, ≤200000). A get_dom probe on a real app is 500KB; evaluate_js / inspect / read_text return just the target. | |
| session_id | Yes | Session id from the start tool. |