Browser Routines

SkillWeb & browsing

Record, compile, and replay Browser Routines — saved, named browser workflows. (Alias for openbrowser-routines.) Supports subcommands: "list [query]" to list/search routines, "new" to record a new routine, "execute <name>" to replay a saved routine. Use when the user says "list routines", "record a routine", "replay X", "execute X", or "/ob-routines <subcommand>".

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Browser Routines skill

What this skill tells your AI

The instructions your AI receives, as published by softpudding/openbrowser in skill/claude/ob-routines/SKILL.md and read by ahel’s review.

Browser Routines are named, compiled workflows captured from real Chrome sessions. The pipeline has four stages: record → compile → name → replay.

Subcommand dispatch

When invoked with arguments, act immediately — do not ask the user what they want:

InvocationAction
/ob-routinesShow available routines and ask what to do
/ob-routines list [query]Run list_routines.py [query] and display results
/ob-routines newAsk only for the one-line goal/intention, then start recording immediately (see "Before recording" below)
/ob-routines execute <name>Run replay.py <name> immediately

Your role during compilation

You are a bridge and quality gate, not the compiler. The Compiler Agent does the reasoning; you ensure it did its job correctly before finalizing.

Bridge duties

  1. Run compile.py in a tmux pane (mandatory — see below).
  2. Watch for [compiler:question] — relay it to the user, send their answer back.
  3. Watch for [compiler:stalled] — show the agent's message, optionally prompt a follow-up.
  4. At [compiler:name_prompt] — help the user pick a short slug.

Quality gate (run before every finalize)

After the compiler reports status=review, read the compiled routine markdown and check both of the following before calling /compile/finalize:

Gate 1 — Intent clarity

Did the compiler understand why the user performed each action, not just what they clicked? Red flags:

  • Steps that say "click X" with no explanation of goal or condition
  • A position-based selection from a sorted/filtered list without asking whether to replay by position or by identity (e.g. "upvote the top 3 posts" — top 3 today vs. the same 3 posts always?)
  • A value (date, search query, ticker, ID) that will obviously change between runs, not parameterized

If any red flag is present and the compiler did NOT ask about it: relay the ambiguity to the user yourself, get their answer, then send it via POST /recordings/{id}/compile/answer so the compiler can revise.

Gate 2 — Delivery goal for read-only workflows

A workflow is read-only if it has no form submission, no purchase, no send/post/create/delete action — the user only navigated, read, filtered, or inspected. For read-only workflows, ask: does the compiled routine end with a delivery step (a file_editor write, a terminal command, or an explicit instruction to report results in chat)?

If the routine is read-only AND has no delivery step, the compiler made an error. Do not finalize. Instead:

  1. Tell the user: "This routine reads data but doesn't capture results anywhere. How do you want results delivered on replay?"
    • (a) Summary shown in chat (brief / structured table / full details?)
    • (b) Written to a local file (path + format: plain text, Markdown, CSV, JSON?)
    • (c) Both
  2. Get their answer.
  3. Send it to the compiler via POST /recordings/{id}/compile/answer — the compiler will revise the routine to include the delivery step.
  4. Wait for the next status=review, then re-run both gates.

Why this matters: A routine that just clicks through pages is useless on replay — OpenBrowser will navigate and stop with no output. The delivery step is what makes the routine meaningful.


Preconditions

First time? Complete the full setup in ~/.claude/skills/open-browser/references/setup.md before using this skill. That guide covers: loading the Chrome extension, connecting it to the server, and obtaining a valid OPENBROWSER_CHROME_UUID. Without that, recording and replay will fail immediately.

For subsequent uses, confirm:

  • OpenBrowser server at http://127.0.0.1:8765
  • Chrome extension connected
  • OPENBROWSER_CHROME_UUID set (or passed via --chrome-uuid)

Quick check:

python3 ~/.claude/skills/open-browser/scripts/check_status.py --chrome-uuid "$OPENBROWSER_CHROME_UUID"

Start the server if needed:

cd /Users/yangxiao/git/OpenBrowser && uv run local-chrome-server serve

Scripts path: ~/.claude/skills/ob-routines/scripts/.


List & search routines

python3 ~/.claude/skills/ob-routines/scripts/list_routines.py
python3 ~/.claude/skills/ob-routines/scripts/list_routines.py "login"
python3 ~/.claude/skills/ob-routines/scripts/list_routines.py --recordings

Record a routine

Before recording — DO NOT interrogate the user

The whole point of record → compile is that the browser actions are observed, and the Compiler Agent asks clarifying questions after it has seen them.

Ask the user only for a short goal/intention (one line). Do NOT ask:

  • which site or URL to start from
  • which tool/screener to use
  • how to define filter terms ("what's high-value?", "what's significant?")
  • which parameters should vary between runs

All of that is the compiler's job during Gate 1. Pre-record interrogation defeats the pipeline and wastes the user's time. If the user's goal is vague ("find good stocks"), that's fine — start recording. The compiler will ask.

Step 1 — start recording

python3 ~/.claude/skills/ob-routines/scripts/start_recording.py \
  --chrome-uuid "$OPENBROWSER_CHROME_UUID" \
  --name "xiaohongshu-messages" \
  --intent "check messages on Xiaohongshu"

Prints [recording:started] <recording_id>. Save this ID.

Tell the user: "Perform your actions in the browser window, then come back and say done." Do NOT proceed until the user confirms.

Step 2 — stop recording

python3 ~/.claude/skills/ob-routines/scripts/stop_recording.py <recording_id>

Compile to a routine — MANDATORY: tmux interactive session

compile.py uses input() for Q&A and the name prompt. It MUST run in an interactive shell. Never invoke it directly via the Bash tool — it will block and then be killed, losing the compiler session.

Pick the compiler model before launching

The Compiler Agent benefits from a large model. Before launching compile.py:

curl -s http://127.0.0.1:8765/api/config | jq '.config'
  • If default_compiler_alias is set → launch compile.py with no --model-alias flag; the server uses that alias.
  • If default_compiler_alias is null → pick the best alias from llm_configs (only entries with has_api_key: true) and pass it via --model-alias <alias>. Prefer *-plus over *-flash, and prefer the regular dashscope.aliyuncs.com/compatible-mode/v1 endpoint over coding.dashscope.aliyuncs.com (the coding endpoint has tighter quota semantics — see project memory).

Always tell the user which model the compiler is using, in one line, before launching — whether it came from the server default or your pick.

Launch in tmux

Default (compiler alias configured on server, or no override needed):

tmux new-window -n "compile" \
  "python3 ~/.claude/skills/ob-routines/scripts/compile.py <recording_id>; echo '[compile-done]'; exec zsh"

With an explicit override (when you picked a model automatically):

tmux new-window -n "compile" \
  "python3 ~/.claude/skills/ob-routines/scripts/compile.py <recording_id> --model-alias <alias>; echo '[compile-done]'; exec zsh"

Why exec zsh: Without it, the tmux window closes the instant compile.py exits — the final [compiler:saved] / [compile-done] markers can land between monitor polls and be lost, leaving you unable to capture confirmation. The trailing shell keeps the window alive so the last lines of the pane are always readable. Close it manually with tmux kill-window -t compile after confirming the routine saved.

Monitor output

tmux capture-pane -t "compile" -p

For long runs, stream markers via the Monitor tool instead of polling by hand. The script MUST detect pane-gone and emit a terminal event — otherwise, if the window exits between polls, the monitor silently runs forever on a dead pane and you never learn the routine finished. Template:

rm -f /tmp/ob_compile_seen
while true; do
  if ! tmux list-windows -F '#{window_name}' | grep -qx compile; then
    echo "[tmux:pane-gone] compile window exited"
    break
  fi
  cur=$(tmux capture-pane -t compile -p -S -400 2>/dev/null)
  echo "$cur" | grep -E --line-buffered \
    "\[compiler:question\]|\[compiler:stalled\]|\[compiler:gate_check\]|\[compiler:name_prompt\]|\[compiler:saved\]|\[compile-done\]|\[compiler:error\]|Traceback" \
    | while read -r line; do
      grep -qxF "$line" /tmp/ob_compile_seen 2>/dev/null && continue
      echo "$line" >> /tmp/ob_compile_seen
      echo "$line"
    done
  sleep 3
done

Send an answer

tmux send-keys -t "compile" "the answer" Enter

Markers to watch for

MarkerYour action
[compiler:thought] / [compiler:action]Relay as progress to user
[compiler:question] <text>Relay to user, wait for answer, send via tmux send-keys
[compiler:stalled] <text>Show message, ask user for follow-up
[compiler:complete] goal=… steps=NCompilation reached review state
[compiler:routine_draft]Full routine markdown printed for inspection
[compiler:gate_check]Run both quality gates here. Send feedback or press Enter
[compiler:name_prompt]Gates passed — help user pick slug
[compiler:saved]Run list_routines.py <name> to verify, then report name + id to user

Quality gate checkpoint

When [compiler:gate_check] appears in the pane, compile.py is explicitly paused waiting for your review of [compiler:routine_draft]. The gate check is Claude's judgment, not the compiler's — the compiler's own "Let me know if I misread your intent" message in [compiler:message:assistant] is not a substitute. You must:

  1. Read the [compiler:routine_draft] markdown in the pane capture.
  2. Write out Gate 1 and Gate 2 reasoning as user-visible text before sending any input — one or two lines per gate, stating what passed or failed and why. This makes the judgment inspectable by the user instead of hidden inside an empty Enter.
  3. Then either pass or fail:
  • Gates pass → send an empty Enter: tmux send-keys -t compile "" Enter
  • Gate fails → send corrective feedback: tmux send-keys -t compile "Please add a delivery step: summarise results in chat as a structured list of tickers with metrics." Enter

compile.py forwards non-empty input back to the compiler, streams the revision, and loops back to another [compiler:gate_check]. Only an empty Enter advances to [compiler:name_prompt].

Never send gate feedback at the [compiler:name_prompt] stage — that input goes directly to the routine name field, not the compiler.


Replay a routine

python3 ~/.claude/skills/ob-routines/scripts/replay.py "routine-name" \
  --chrome-uuid "$OPENBROWSER_CHROME_UUID"

# List without replaying
python3 ~/.claude/skills/ob-routines/scripts/replay.py --list

Name matching: exact → ID → prefix → substring.


Full example workflow

1. /ob-routines new  →  ask user what to record
2. start_recording  →  [recording:started] abc123
3. (user records in browser, says "done")
4. stop_recording abc123  →  [recording:events] 21 events
5. tmux new-window "compile.py abc123"
6. monitor pane → relay questions → send answers
7. [compiler:complete]  →  run Gate 1 + Gate 2
   Gate 2 fails: routine is read-only, no delivery step
   → ask user: chat summary, file, or both?
   → send answer via tmux send-keys
   → wait for next [compiler:complete]
8. Gates pass → [compiler:name_prompt] → user picks slug
9. [compiler:saved] name='…' id=…
10. list_routines.py <name> to verify, then report saved + verified to user
11. /ob-routines execute <name>  →  streams [action] … [complete]

Failure handling

  • Server unreachable: uv run local-chrome-server serve
  • Browser UUID invalid: reconnect Chrome extension, get fresh UUID
  • 0 events captured: browser disconnected; re-record
  • tmux not found: brew install tmux
  • tmux window conflict: check tmux list-windows, use a unique -n name
  • Compiler session expired (pane exited before finalize): call POST /recordings/{id}/compile again to restart — session is fresh
  • Relay stuck: [observation:error] lines in SSE stream; relay to user

Signals

GitHub stars
189
Forks
18
Last commit
May 2026

ahel review

  • K1binfo
    installs-packages
  • K6low
    bundled executables the agent is told to run

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
ob-routines
Source
github.com/softpudding/openbrowser