Skip to content

Browser Recipe System

nick3 edited this page May 28, 2026 · 1 revision

Browser Recipe System

A recipe is a stored sequence of browser tool calls — navigate, wait, click, type, etc. — that you can replay deterministically. Useful for login flows, daily report downloads, repeated form submissions, anything the AI would otherwise re-figure-out every time.

Source: src/main/browser-recipes.ts. Tool: browser_run_recipe.


Recipe shape

interface Recipe {
  id: string
  name: string
  description?: string
  steps: RecipeStep[]
  createdAt: number
  updatedAt: number
}

interface RecipeStep {
  tool: string                   // any browser_* tool name
  args: Record<string, unknown>  // tool-specific arguments
  description?: string           // free-form annotation
  stopOnError?: boolean          // default: true
}

Steps run sequentially. By default, a failed step aborts the recipe; set stopOnError: false on a step to continue past it (useful for optional steps).


Example recipe

{
  "id": "daily-report-download",
  "name": "Daily report download",
  "description": "Log into dashboard, navigate to today's report, save as CSV",
  "steps": [
    {
      "tool": "browser_navigate",
      "args": { "url": "https://dashboard.example/reports/today" },
      "description": "Open today's report page"
    },
    {
      "tool": "browser_wait_for_selector",
      "args": { "selector": "#download-csv-button", "timeout_ms": 10000 }
    },
    {
      "tool": "browser_click",
      "args": { "selector": "#download-csv-button" },
      "description": "Trigger CSV download"
    },
    {
      "tool": "browser_wait_for_text",
      "args": { "text": "Download complete", "timeout_ms": 30000 },
      "stopOnError": false
    }
  ],
  "createdAt": 1718000000000,
  "updatedAt": 1718000000000
}

pane_id is not in the recipe — it's supplied at runtime when the recipe is run. This lets the same recipe target any browser pane.


Running a recipe

The model can call:

browser_run_recipe(pane_id, recipe_name)

or inline:

browser_run_recipe(pane_id, steps_json)

For each step:

  1. Resolve tool to a registered tool name
  2. Merge {pane_id} into the step's args
  3. Dispatch via toolRegistry.dispatch(tool, args, ctx)
  4. If error and stopOnError !== false, halt and return all results so far
  5. Otherwise continue

Returns:

{
  success: boolean,
  steps: Array<{
    index: number,
    tool: string,
    ok: boolean,
    result?: unknown,
    error?: string
  }>,
  stoppedAt?: number   // step index that halted the recipe
}

Authoring a recipe

Two paths:

Manual

Write the JSON by hand, save to <userData>/clusterspace-data/config/recipes/<id>.json (or wherever your recipe store points). Reload.

Record

Use the AI: "Do X step by step, then save it as a recipe called 'daily-report-download'."

The AI executes the actions normally (each tool call gets logged in the AI-Tools-Reference). At the end, it can read the action log and synthesize a recipe from the successful steps:

browser_get_action_log(pane_id, limit=30)
→ AI distills into steps array → calls a save_recipe plugin tool (you'd author)

Currently there's no built-in save_recipe tool — write one as a Plugin-Authoring if you want recording in-app. The simpler manual path is fine for most users.


Idempotency & re-runs

Recipes are not idempotent by default. If you click "Submit" twice, you submit twice. Build idempotency into the recipe with guard steps:

{
  "tool": "browser_query",
  "args": { "selector": ".already-submitted-banner" },
  "stopOnError": false,
  "description": "Skip submit if already done"
}

The model can branch on the result, but recipe execution itself is linear — there's no built-in conditional. For complex branching, use the model to drive (a Goal-Runner-Overview with a sequence in its prompt) rather than a recipe.


Where recipes live

<userData>/clusterspace-data/clusterspace-recipes.json

One JSON file holds all recipes (keyed by id). Loaded by getRecipeStore() in browser-recipes.ts.

Replace the file to bulk-import; back it up to preserve recipes across machines.


Limitations

  • No conditionals — every step runs unless an earlier step halts
  • No loops — for "click N times" use a goal that drives the model
  • No variables / placeholders — args are static JSON. Workaround: have the model construct the recipe inline (steps_json arg) with computed values
  • No nested recipes — one recipe can't include another. Inline the steps.
  • Page state assumed — recipes assume the page is in a known state (e.g., logged in). If preconditions aren't met, steps fail and you stop.

Compared with Goal-Runner-Overview

Recipe Goal
Execution Linear, deterministic Adaptive, model-driven
Best for Repeated identical flows Outcomes that may require many paths
Failure Halts (default) Loops, replans
Cost One call per step Many model + tool calls
Verification Per-step stopOnError End-to-end success criterion

Use recipes for "do exactly this." Use goals for "achieve this; figure it out."


See also

Clone this wiki locally