---
title: Use Recipes (multi-step agent workflows)
---

# Use Recipes (multi-step agent workflows)

## What it is

A **Recipe** is a saved multi-step workflow that runs Claude Code for you — think "a playbook you can press Play on." Each step has a prompt, a kind (regular step, approval gate, sub-recipe call, etc.), and a connector telling the engine to run the next step sequentially, in parallel, or conditionally. Recipes live on disk as `.recipe.json` files either globally in `~/.claude/recipes/` or inside a project at `{project}/.claude/recipes/`. You build them visually in the Recipes view, save them, then run them against one or many projects — either from the UI, via a scheduled trigger, or from a cron job. Omniscio remembers run outputs and AI-generated summaries in a run-memory table so later steps can reference earlier ones and the next run can see what happened before.

## Where to find it

Recipes is a virtual project in the left sidebar; click **Recipes** to open a view that shows a
card for every recipe on disk, global plus the current project, next to a sidebar of live and
historical runs. To run one without opening the view, right-click any real project row in the
sidebar and hover **Run Recipe**. **Create Recipe** in the view opens the visual editor, which is
also where scheduling is set up; the recipe-related options live under Settings → Sessions and
Settings → Features.

## How it behaves

### How to use it

1. **Open the Recipes view.** Click **Recipes** in the left sidebar. You'll see cards for every recipe on disk (global + current project).
2. **Create a new recipe.** Click **Create Recipe** → the Recipe Editor opens. Add steps by clicking **Add step** — each step is a card with name, prompt, kind, and optional variables. Connect cards with the connector dropdown (sequential / parallel / conditional). A `homeProjectId` is required for project-scoped recipes; individual steps can override with `targetProjectId` to hop across projects (except in _same-session_ mode, which is bound to one project).
3. **Save and keep drafting safely.** Hitting **Save** writes the `.recipe.json` to disk. If you navigate away with unsaved changes, the editor persists your draft to localStorage (500ms debounce) so a reload or accidental close won't lose work. If the same recipe was changed somewhere else while your editor was open (a second window, the mobile web bridge, or a scheduled change), **Save** is **refused** rather than silently overwriting that change — _"This recipe was changed somewhere else since you opened it. Reload it to get the latest version, then re-apply your edits."_ Your draft stays put so you can reload and re-apply. A brand-new recipe (no prior version) always saves.
4. **Run a recipe.** Click **Run** on any recipe card → the Run dialog collects parameters (values for the recipe's declared variables) and the target project(s). Confirm → the orchestration engine executes step by step, showing a live dashboard with each step's output. Approval-gate steps pause and wait for your click. By default an approval-gate step has a **7-day timeout** — if you don't approve or reject within that window the step's `timeoutPolicy` decides what happens (`abort` cancels the run, `wait` keeps holding indefinitely until you act). You can change the global default in Settings → Sessions → **Recipe approval timeout** (1–90 days), or override it per step in the recipe editor. To make stale approvals visible at a glance, the approval modal shows an amber **"Pending since X day(s)"** warning row whenever the request has been waiting more than 24 hours — so a request you forgot about doesn't sit silently until it auto-times-out.
5. **Schedule or chain it.** Click **Schedule Recipe** in the editor to put the recipe on a calendar — an interval, daily, weekly or monthly recurrence, with a **Next run** preview before you save. Full walkthrough of every control: [Scheduling a recipe](recipe-scheduling.md). Alternatively point an Omniscio cron job at it via `cron-recipe-executor` when you want the extra job switches — see [Cron jobs](cron-jobs.md). For composition, call another recipe as a sub-recipe step — parent/child runs link in history.

### Launch from a project's right-click menu (Run Recipe submenu)

A faster path that skips the Recipes view entirely: right-click any **real project** row in the sidebar (i.e. not a virtual project like Settings / Skills / Recipes itself) and hover **Run Recipe**. A submenu opens to the side with two sections:

- **My Recipes** — every saved `.recipe.json` whose `homeProjectId` matches the right-clicked project. Click one to jump straight to the recipe's Run dialog with the target project already selected; you only need to fill in any variables it declares and confirm.
- **Templates** — built-in `RecipePattern` entries (the pre-authored starting points like "Audit this codebase", "Refactor for clarity", etc.). Clicking a template opens the pattern instantiate flow so you can fill in any pattern-defined variables before it materializes into a real recipe and runs.

The submenu is **hover-activated** and stays open via a 200 ms hover-leave timer so brief mouse jitter doesn't dismiss it. If a project has no matching recipes AND no templates would apply, the **Run Recipe** row still shows but the submenu just renders the "Loading…" stub then nothing (which is the cue to head into the Recipes view and build one). The submenu auto-flips left and shifts up if it would clip past the viewport edges.

Gating: the **Run Recipe** row is suppressed for virtual-project rows (Settings, Skills, Inbox, etc.) and when Settings → Features → **Enable Recipes** is off. There is no recipe-running UI on virtual projects because they don't have a `homeProjectId`.

### Running runs and history (sidebar sections + entry points)

The Recipes view's left-hand sidebar lists more than just your saved recipes — at the top it surfaces **every recipe run** (live and historical) across every recipe. Two sections sit above the per-recipe list:

#### Running now

Every recipe run currently in flight (state ≠ `completed` / `error` / `idle`). Each row shows a state chip (Starting / Running / Paused / Completing / Awaiting approval), the recipe name (looked up from the live recipe configs), step progress (`N/M steps`), and total cost so far (`$X.XX`). Clicking a row opens that run's detail panel on the right — the same panel the orchestrator session links to. The section header reads `Running now (N)` and disappears entirely when nothing is live.

#### Concurrent run warning

Multiple recipes can run concurrently — the engine no longer serializes top-level runs. When you click **Run** while another recipe is already in flight, Omniscio shows a one-time confirmation dialog noting that the new run will share Claude account capacity with the live ones. The dialog lists how many runs are currently active (e.g. _"1 other run is live"_) and offers two paths: click **Run anyway** to start the new run immediately, or check **Don't show this again** before clicking to suppress the warning forever. The opt-out is stored in the `recipeConcurrencyWarningEnabled` setting (default ON) — re-enable at Settings → Sessions → **Recipe concurrency warning** if you change your mind.

The warning fires only when at least one other run is in an active state (`starting | running | paused | awaiting_approval | completing`) at the moment you click Run. If you have the warning suppressed and the engine is idle, clicking Run goes straight to the run with no extra dialog (same as before).

#### History

Up to the most recent **1,000 past runs** across every recipe, newest first. Each row shows a status badge (Completed / Aborted / Error / Awaiting approval), the recipe name, the date the run started, step progress, total run duration (`12s`, `3m 14s`, `1h 22m`), and total cost. Click a row to open its detail panel; you'll see the variables bag the run was started with (parameters that fed `{{vars.X}}` substitution), one line per step with status icon, summary, and any error message, and a **View** button next to each step that ran in a Claude session — clicking jumps you to that session. For runs in `error` or `aborted` state, a **Resume** button appears below the row. Clicking it pops a confirmation dialog showing the recipe name, total cost so far, and last checkpoint time, then re-spawns the engine to continue from where it stopped. Resume is no longer gated on engine idle — multiple runs can resume / start concurrently. **One safety stop:** if a step was still _running_ when the run was interrupted (a crash mid-step — its session may already have sent a message, made a commit, or spent AI budget), Resume will not silently re-run it. Instead it stops and names the interrupted step(s) so you can review them, then dismiss the run or start it fresh — this avoids duplicating work the crashed step had already done. Steps that simply _failed_ still re-attempt normally.

The History section accepts a temporary **filter banner** when Omniscio opens it after a boot reconcile (see "Boot reconcile toast" below). The banner reads `N interrupted run(s)` with a **Show all** link that clears the filter so the rest of the history reappears.

#### Boot reconcile toast

When Omniscio starts up after an unclean shutdown, it sweeps the database for recipe runs that were still marked `running` and flips them to `error` (since the engine is gone). If any runs were flipped, a single yellow toast appears ~3 seconds after the window paints — for example _"3 recipe runs were interrupted by a shutdown."_ — with a **View** button. Clicking **View** opens **Recipes** with the History section filtered to just those interrupted runs.

#### Recipe-spawned session chip

Open any Claude session that was spawned as a step of a recipe. A small amber chef-hat chip appears next to the session title showing the parent recipe's name (e.g. _"My Refactor Recipe"_). Clicking the chip jumps to **Recipes** with that run selected — the right-hand pane opens to the run's detail view. The chip is hidden on the orchestrator session itself — that one already shows a full-width banner.

#### How it differs from the per-recipe history view

When you click a single recipe in the sidebar, the right-hand pane shows that recipe's editor + its own **Recent Runs** strip (last ~5 runs for that recipe only). The sidebar's **History** section is the **cross-recipe** view — it answers questions like:

- "Did anything fail overnight?"
- "How much have I spent on recipes this week?" (look at the cost column)
- "Which run was the one that hit `awaiting_approval` an hour ago?"

If you only care about one recipe's history, click that recipe and use its Recent Runs strip. If you want the whole picture, scroll the sidebar's History section.

### Timeline tab (single-run Gantt chart)

Open any recipe run with **2 or more steps** in the Run detail panel and a third tab labeled **Timeline** appears next to **Overview** and (when present) **Sub-runs**. It renders the run as a horizontal Gantt chart — one row per step, time flowing left-to-right, the same status colors used everywhere else (green = completed, red = failed, amber-pulsing = running, blue = awaiting approval, surface-grey = skipped/pending, amber-static = `completed_incomplete`). The tab is hidden for 1-step runs (a Gantt chart of a single bar isn't useful).

What you see:

- **Time axis** at the top with tick marks. A **Relative / Absolute** toggle button in the section header switches the labels between "+0s / +30s / +1m" relative offsets from the run start and absolute wall-clock times.
- **Step rows** with the step number + name in a fixed-width left column (200px) and the bar drawn in the time area. Bars carry a tooltip on hover showing duration, cost, retry count, and any error.
- **Approval-gate steps** are drawn with a 45° striped overlay so they read differently from regular work — a paused gate looks visually distinct from a running step.
- **Retries** stack as ghost bars above the main bar (up to 3 are drawn at 20% opacity so a step that retried 5 times still reads as "this retried a lot" without flooding the row).
- **Cost annotation** appears inside the bar when the bar is wide enough (>8% of the row), formatted as `$X.XX`. Steps with no cost data simply have no annotation.
- **Bottleneck indicator** — the longest step in the run gets an amber `⚠` icon next to its name with the tooltip "Bottleneck — longest step in this run." Single-step runs and runs with no completed steps don't get one.
- **forEach expansion** — steps that fanned out via a forEach iterator show a chevron in the label column; click it to expand a list of iterations underneath the bar, each with a status dot, the iterator value, and any per-iteration error.
- **Missing-timing-data** bars (steps that completed but have no startedAt/endedAt because of an older run) render at 40% opacity and the tooltip says "No timing data" instead of a duration.

The Gantt chart is built on top of pure helpers in [/src/renderer/src/features/recipes/timeline-utils.ts](/src/renderer/src/features/recipes/timeline-utils.ts) (`computeTimeScale`, `computeStepBars`, `generateTickMarks`, `detectBottleneck`, `formatDurationMs`); the component itself is [/src/renderer/src/features/recipes/RecipeRunTimeline.tsx](/src/renderer/src/features/recipes/RecipeRunTimeline.tsx), lazy-loaded by [/src/renderer/src/features/recipes/RecipeRunDashboard.tsx](/src/renderer/src/features/recipes/RecipeRunDashboard.tsx) so the chunk only ships when you actually open the Timeline tab.

### Multi-run comparison (sparklines + side-by-side stacked timelines)

The per-recipe **Recent Runs** strip — the list under a single recipe's editor pane — adds two new affordances when the recipe has at least 2 runs in history: an inline sparkline on every row (visible on `md+` viewports) and a **Compare runs** toggle at the top of the strip.

- **Sparklines.** Each history row gets a tiny 120px-wide stacked bar to the right of the row showing that run's per-step durations as proportional segments in step-status colors. At a glance you can tell "run A spent most of its time on the early steps; run B was bottlenecked at the end." Sparklines hide on narrow viewports so phone history rows don't crowd.
- **Compare runs button.** A small `Compare runs` button (BarChart3 icon) appears in the header of the Recent Runs strip when there are 2+ historical runs. Click it to flip into comparison mode: the page reveals a stacked panel with up to 10 runs drawn one above another on a **shared global time axis** (each run's bars are rescaled so the longest run defines 100%, making slow runs visually obvious). Click the button again or click any run row in the stacked view (which dismisses comparison and opens the per-run detail) to exit.

What the comparison view tells you:

- **Step legend** at the top — one chip per unique step name across all runs. Clicking a chip **highlights** that step across every stacked timeline (other steps dim to 20% opacity), so you can answer "is step 3 always slow, or did this one run go bad?" with one click.
- **Recurring-bottleneck badge** — a chip carries an amber `⚠` icon when its step was the longest in **3 or more** of the visible runs. That's the "this step is consistently the bottleneck, not just a one-off slow run" signal.
- **Failure correlation badge** — a chip shows a red `N/M` count (`failures/runs`) when its step **failed in 30%+** of the visible runs, and the failed bars in the stacked view get a red dashed right border so the failures jump out.
- **Date + duration labels** flank each row — date on the left ("May 11"), formatted total duration on the right ("3m 14s") — so you can scan history fast.

Sparklines and the comparison view live in [/src/renderer/src/features/recipes/RecipeRunSparkline.tsx](/src/renderer/src/features/recipes/RecipeRunSparkline.tsx) and [/src/renderer/src/features/recipes/RecipeRunComparison.tsx](/src/renderer/src/features/recipes/RecipeRunComparison.tsx), wired into [/src/renderer/src/features/recipes/RecipeHistoryPanel.tsx](/src/renderer/src/features/recipes/RecipeHistoryPanel.tsx). The recurring-bottleneck and failure-correlation detectors are in [/src/renderer/src/features/recipes/timeline-utils.ts](/src/renderer/src/features/recipes/timeline-utils.ts) (`detectRecurringBottlenecks`, `detectFailureCorrelations`).

### Prompts from a file (promptFile)

The recipe editor has an **Inline** / **From file** radio inside each prompt-step card. Choosing **From file** hides the prompt textarea and reveals a path input, a **Browse…** button that opens a native file picker (filtered to `.md`, `.markdown`, `.txt`), and a live preview pane rendering the first ~4 KB of the chosen file (debounced 300 ms after you stop typing). Path errors — missing file, outside the project sandbox, too-big, non-UTF-8 — surface inline in the preview area so you see the problem before you click Run.

**When to use it:**

- Your prompt is long enough that inlining it clutters the recipe card.
- You keep a prompt library in version control and want multiple recipes (or multiple steps) to point at the same canonical file.
- Your team reviews prompts as `.md` files in pull requests instead of as JSON blobs.

**Templating still works:** The same `{{vars.x}}`, `{{steps.y.output}}`, `{{steps.y.output.field}}` substitutions that work in an inline prompt work inside a `promptFile` too. Substitution happens **after** the file is loaded — so you write `{{vars.topic}}` in your `.md` and Omniscio swaps it in before the session sees the text.

**What happens at run time:** Paths resolve relative to the recipe's home project folder (or the step's `targetProjectId` folder). Files are capped at **256 KB**, must be UTF-8 text, and must resolve either inside the project folder _or_ inside the user's home directory (absolute paths under `~/...` are accepted so prompt libraries can live outside any one project — `..` traversal out of both bases and symlink escapes are refused). Before the engine spawns any session, it **preflights every `promptFile` in the recipe** — if any file fails to load (missing, too big, binary, outside sandbox), the entire run is refused before any database writes, so there are no half-started runs. A file deleted mid-run fails only that one step; prior steps stay intact.

For the full rules — sandbox algorithm, error codes, preflight ordering, XOR semantics — see the authoring guide: [recipe-authoring-guide.md](/.claude/memory/recipe-authoring-guide.md).

**Example** — project-scoped recipe pulling a long analysis prompt from a co-located file:

```json
{
  "name": "Deep Analysis",
  "scope": "project",
  "homeProjectId": "<project-uuid>",
  "executionMode": "multi-session",
  "parameters": [{ "name": "topic", "label": "Topic", "type": "text", "required": true }],
  "steps": [
    {
      "name": "Analyze",
      "promptFile": "./prompts/deep-analyze.md"
    },
    {
      "name": "Summarize",
      "prompt": "Summarize {{steps.Analyze.output}} for the brief on {{vars.topic}}."
    }
  ]
}
```

The file at `<project>/prompts/deep-analyze.md` can itself contain `{{vars.topic}}` and it will be substituted before the session reads it.

### What a step can see of the steps before it

This is the single most common way a working-looking recipe quietly does nothing, so it is worth being precise about.

In **multi-session** and **same-session** recipes, a step sees an earlier step's work **only** where you wrote a `{{steps.NAME.output}}` reference. There is no implicit carry-over. Writing "consolidate the reviews above" or "using the report from the previous step" gets you a step that runs in a fresh, empty session — it will not error, it will produce a confident summary of nothing. Only **parallel** recipes get an automatic `[Context from prior steps]` block, and only from earlier *waves*.

A **forEach** step is referenced the same way, and its output is the **list of its iterations** — one entry per item, in order:

```jsonc
[
  { "item": "12", "status": "completed", "summary": "READY — no blocking issues.", "error": null },
  { "item": "15", "status": "failed",    "summary": "",                            "error": "timed out" }
]
```

So a consolidate step can either take the whole list — `{{steps.Review.output}}` — or reach into one entry with `{{steps.Review.output[0].summary}}`. Two entries deserve explicit handling in the prompt you write:

- **`status` is not `completed`** — that item's work failed. Say so in the output rather than dropping the row; a missing row reads as a clean result.
- **`item` is `(truncated)`** — the combined output of all the iterations was too large to carry, so the list you received is partial and the remaining items are named in that entry's `summary`. Treat the run as incomplete and say so.

(If a forEach step declares its own `output: { "type": "json" }`, it opts out of this and keeps the older last-iteration-wins behaviour instead.)

**Where to edit:** Open the recipe, click a step card, switch the radio to **From file**, then paste the path or click **Browse…** to pick it. Save the recipe — the path is stored in the step's `promptFile` field. A prompt-kind step must have exactly one of `prompt` or `promptFile` set (never both, never neither); on approval and sub-recipe steps those fields are **ignored** (no prompt executes), not rejected.

### Pipeline lane suppression

Recipes that use a `pipeline-expansion` step fan one pipeline template out across many items — for example, a 44-prompt audit pipeline run against three buckets becomes 132 sessions plus the orchestrator. Without help, that floods the sidebar and makes every per-step `ended` push fire a chime. Omniscio handles this automatically:

- **Hidden from the sidebar by default.** Every fan-out lane session is flagged `is_pipeline_lane=true` in the database and filtered out of the sidebar list query. The orchestrator session and any _mainline_ steps you authored (consolidate, repair, approve, anything outside the pipeline block) stay fully visible — you still see the run happening, you just don't see the lane noise.
- **Reveal them when you want to peek.** Settings → **Sessions** → **Show recipe pipeline lane sessions in sidebar** flips the filter off so the lanes appear alongside everything else. The toggle takes effect immediately for live sessions; you don't need to restart.
- **Silenced for all notifications, always.** Lane sessions never fire a chime, OS toast, or mobile push — even when the visibility toggle is on. The toggle controls visibility only; silence is unconditional. Mainline notifications (orchestrator finishing, an approval gate asking for input) are unaffected and notify normally.
- **Auto-archived on clean completion.** When a lane reaches `ended` it is archived automatically so the "with lanes" view shows the run wound down rather than a wall of green dots. Lanes that hit `error`, `paused`, or `needs_you` stay visible at that status so you can investigate, resume, or respond.

The 44-prompt audit pipeline that motivated this feature now leaves a single orchestrator + a single `consolidate` row in the sidebar, with 132 silent lanes that archive themselves as they finish.

### Silent recipe step sessions

Recipes also produce mainline (non-lane) step sessions — anything you authored as a regular `prompt` step outside a `pipeline-expansion`. For long recipes that move quickly through several mainline steps that all succeed cleanly, leaving every finished step visible is a lot of green dots to scroll past. By default Omniscio auto-archives those rows once each step ends cleanly, while still keeping any step that flagged trouble fully visible. Turn the setting off to keep finished mainline steps in the sidebar like any other Claude session.

- **Setting:** Settings → **Sessions** → **Silence recipe step sessions on success** (default ON). When ON, a mainline step that ends cleanly (`status='ended'`, no trouble marker in the final agent message) is auto-archived the moment it ends. The orchestrator session itself is also auto-archived if every step ran clean (no failed steps, no marker tripped). Switching the setting affects new runs only — already-running recipes use the value that was in effect when they started.
- **Trouble markers keep a step visible.** Steps emit a marker on a fresh line when they want to surface a problem to the user: `STEP_FAILED:`, `LANE_FAILED:`, `PREFLIGHT_FAILED:`, `NEEDS_ATTENTION:`, `RECIPE_NEEDS_ATTENTION:`. A marker is recognized only when it's the first non-whitespace token on a line. Markers inside fenced code blocks (` ``` `) are ignored — a consolidate step quoting prior lane output verbatim won't trip the guard.
- **Errors, pauses, and `needs_you` always stay visible**, regardless of the setting. The setting only changes what happens to _clean_ exits.
- **Pipeline lanes are unaffected by this setting.** Lanes already auto-archive on `ended` and are sidebar-filtered by default — the lane behavior described in the previous section is unchanged.
- **Diagnostic log:** when a step finishes, the engine logs an `[orchestration] Auto-archived step session …` line with one of these reasons: `lane-clean`, `clean`, `setting-off`, `marker`, or `status=<x>`. If the orchestrator stays visible at the end of a run, look for `marker — kept visible` lines in the same run to find which step tripped.

### Soft refusal on missing completionCheck

A recipe step can declare a `completionCheck` — usually a sentinel file the agent is expected to write before it stops, e.g. `{ "type": "file-exists", "path": ".audit/done.txt" }`. Earlier versions of the engine treated a missing sentinel after a clean session exit as a failure: Omniscio would re-launch the same step up to three times, then loudly abort the whole run with a system message and an `error` final status. That retry loop produced a lot of noise on the (very common) case where the agent simply concluded the work didn't need to be done — for example, an "audit the codebase" step that legitimately found nothing to flag.

The current behavior is **soft refusal**: a clean exit (`status='ended'`) with no sentinel is treated as the agent's considered "no output" decision rather than a failure. The engine does **not** retry. The step is marked `completed_incomplete`, the run finalizes with a new top-level status `incomplete` (not `error`), and a single inbox card is surfaced for you to review.

- **Inbox card.** The card uses a low-priority **blue dot** (no chime, no OS toast — it's a passive review prompt, not an alert). The body shows the recipe name and the missing-output wording — _"Frontend Code Audit — ended without output"_ — followed by `Step: <name>` and `Missing: <sentinelPath>` so you can tell at a glance which step refused and which sentinel it was supposed to produce. An **Acknowledge** button sits at the bottom; middle-clicking the row dismisses it the same way.
- **Click-through.** Clicking anywhere on the card opens the orchestrator session for that run, so you can read the agent's final output and decide whether to re-run, edit the recipe, or just leave it acknowledged.
- **Acknowledge.** Clicking **Acknowledge** (or middle-clicking the row) writes `acknowledged_at` on the orchestration_runs row. The run stays in the recipe history forever — you can still see it in the History sidebar with status `incomplete` — but it stops surfacing as an inbox attention item. There is no time-based auto-acknowledgement; the inbox card remains until you act on it.
- **Crash evidence still loud-aborts.** Soft refusal only fires on a **clean** exit with no sentinel. If the session ends in `error` (CLI crash, kill signal, network rip), the existing failure path is unchanged — the engine retries up to its `maxRetries` budget and finalizes the run as `error` with a "Step N failed" system message and the usual loud notification. The discriminator is the session's exit status, not just the sentinel presence: `ended + no sentinel` is soft, `error + no sentinel` is loud.
- **Multi-step latching.** In a recipe where step 1 soft-refuses but step 2 succeeds, step 2 still runs to completion (the soft refusal does not abort the run mid-recipe). At finalize time, the run-level status is forced to `incomplete` regardless of later step success, so the inbox card always surfaces — you don't lose the signal because something downstream worked. Per-step status is still per-step (`completed_incomplete` for step 1, `completed` for step 2).

When you start needing the old retry behavior — for example, you genuinely depend on the sentinel as a hard precondition for downstream steps — wrap that step in an explicit `approval` step or have a later step assert the sentinel and fail loudly. Soft refusal removes the implicit retry, not your ability to declare a strict requirement.

### Pre-flight credential check

Before spawning the Claude session for **each recipe step**, Omniscio asks the rate-limit recovery service whether the currently active account has budget headroom. If the active account is at its 5-hour or 7-day cap, Omniscio silently switches to the freshest tracked account first, then spawns the step on the new account. This means a long recipe that sprawls across hours doesn't grind to a halt because step 1 burned the active account's budget — step 2 finds the next-freshest login and keeps going.

The check is conservative on purpose:

- **Three outcomes**: `switched` (a different account is now active), `same` (current account still has room), `all-exhausted` (every tracked account is capped).
- **Non-blocking on `all-exhausted`**: the engine still launches the step. The existing post-spawn rate-limit recovery surfaces the failure through the inbox banner and OS notification — same UX as a session that hits its cap mid-turn — so the recipe doesn't fail in a new, surprising way.
- **Rejection-safe**: a transient error (network blip during the headroom probe) is caught and treated as `same` so a flaky probe never crashes the recipe.
- **Per step, not per follow-up**: the pre-flight runs only on the initial spawn for each step, not on every continuation message inside a step. A step that runs for many turns won't keep re-evaluating headroom — that's the existing post-spawn recovery's job.
- **Multi-session and parallel modes only**: same-session-mode recipes reuse one Claude process across every step, so there's no per-step spawn for the pre-flight to gate. Same-session runs still get the standard post-spawn rate-limit recovery, but the proactive pre-flight only applies when the engine launches a fresh session for each step (multi-session and parallel).
- **Respects the 30-second auto-switch cooldown**: the pre-flight calls the recovery service with `force: false`, so if Omniscio just performed an auto-switch it won't immediately switch again. This is intentional — back-to-back recipe steps don't thrash through accounts faster than the regular auto-switch path would.

This is the fifth path in Omniscio's overall rate-limit recovery picture (timer, account switch, usage poll, stale-account immediate restart, **and pre-flight before recipe step spawn**). The pre-flight is the only proactive one — the others kick in after a session has already been rate-limited. For the post-spawn paths see [.claude/memory/process-management-ratelimit.md](/.claude/memory/process-management-ratelimit.md).

**Subagent rate-limit attribution**: when a recipe step's session uses Claude Code's Task tool to spawn subagents, a rate limit triggered by a subagent's API call is now logged with a `(subagent)` label and tagged with the originating `parent_tool_use_id` from the CLI's `rate_limit_event` payload. Recovery itself is unchanged — the parent session still gets restarted as a unit — but logs no longer hide whether the cap was hit by the top-level agent or one of its tools.

## For agents

### How it works

The Recipes view is [/src/renderer/src/features/recipes/RecipesView.tsx](/src/renderer/src/features/recipes/RecipesView.tsx); the sidebar (saved recipes + Running now + History sections) is [/src/renderer/src/features/recipes/RecipesSidebarContent.tsx](/src/renderer/src/features/recipes/RecipesSidebarContent.tsx); the run-detail right pane is [/src/renderer/src/features/recipes/RunDetailPanel.tsx](/src/renderer/src/features/recipes/RunDetailPanel.tsx); the visual editor is [/src/renderer/src/features/recipes/RecipeEditor.tsx](/src/renderer/src/features/recipes/RecipeEditor.tsx) with [/src/renderer/src/features/recipes/RecipeStepCard.tsx](/src/renderer/src/features/recipes/RecipeStepCard.tsx) for each step and [/src/renderer/src/features/recipes/RunRecipeDialog.tsx](/src/renderer/src/features/recipes/RunRecipeDialog.tsx) for the parameter collection. The recipe-spawned session header chip is [/src/renderer/src/features/sessions/RecipeRunChip.tsx](/src/renderer/src/features/sessions/RecipeRunChip.tsx); the boot-reconcile toast hook is [/src/renderer/src/hooks/useRunsReconciledAtBootToast.ts](/src/renderer/src/hooks/useRunsReconciledAtBootToast.ts). Draft persistence uses [/src/renderer/src/hooks/useRecipeDraft.ts](/src/renderer/src/hooks/useRecipeDraft.ts) (key `recipe-draft:${configId || '__new__'}`). The frontend store is [/src/renderer/src/stores/recipe-store.ts](/src/renderer/src/stores/recipe-store.ts) (mutex selection model: `activeRunId` and `activeConfigId` are mutually exclusive — selecting one clears the other). The History-filter highlight store (used by the boot toast deep-link) is [/src/renderer/src/stores/runs-history-store.ts](/src/renderer/src/stores/runs-history-store.ts). On the backend, top-level recipe runs are dispatched through [/src/main/services/engine-registry.ts](/src/main/services/engine-registry.ts) — a `Map<runId, OrchestrationEngine>` that holds one engine instance per live run, so multiple top-level recipes can execute in parallel. Each engine is an [/src/main/services/orchestration-engine.ts](/src/main/services/orchestration-engine.ts); sub-recipes flow through [/src/main/services/recipe/recipe-sub-invoker.ts](/src/main/services/recipe/recipe-sub-invoker.ts). Expression templating (`{{vars.X}}`, `{{steps.Y.output}}`, conditionals, dotted-path resolution, JSON extraction via regex) lives in [/src/main/services/recipe/recipe-expressions.ts](/src/main/services/recipe/recipe-expressions.ts), [/src/main/services/recipe/recipe-templating.ts](/src/main/services/recipe/recipe-templating.ts), [/src/main/services/recipe/recipe-output-transform.ts](/src/main/services/recipe/recipe-output-transform.ts), and [/src/main/services/recipe/recipe-dotted-path.ts](/src/main/services/recipe/recipe-dotted-path.ts). Run history and parent/child linkage are in [/src/main/db/queries-orchestration.ts](/src/main/db/queries-orchestration.ts) (`listOrchestrationRuns` LIMIT 1000, `listRunningOrchestrationRuns` for the `/state` projection); AI-summarized run memory (migration v68, 1–50 retention, default 5) is in [/src/main/db/queries-recipe-run-memory.ts](/src/main/db/queries-recipe-run-memory.ts); schedules in [/src/main/db/queries-recipe-schedules.ts](/src/main/db/queries-recipe-schedules.ts). The `/state` `runningRuns` projection (`RunningRunSummary` + `buildRunningRuns()`) is in [/src/main/services/ask/ask-amc-state.ts](/src/main/services/ask/ask-amc-state.ts). IPC entry points: [/src/main/ipc/recipe-handlers.ts](/src/main/ipc/recipe-handlers.ts), plus sibling handlers for schedule, approval, and supervisor events; the boot-reconcile pull is `RECIPE_GET_BOOT_RECONCILED_RUNS`. Full authoring cheat sheet: [recipe-authoring-guide.md](/.claude/memory/recipe-authoring-guide.md); deep gotchas: [gotchas-recipes.md](/.claude/memory/gotchas-recipes.md).

### Visibility for external agents (Ask-Omniscio `/state`)

The `GET /state` snapshot served by the CLI control server includes a `runningRuns` array — one bounded summary per live run sourced from `orchestration_runs WHERE status IN ('running', 'awaiting_approval')`. The shape per row is:

```jsonc
{
  "runId": "run-xxxx",
  "configId": "cfg-xxxx",
  "configName": "My Refactor Recipe",
  "projectId": "proj-xxxx",
  "state": "running", // or "awaiting_approval"
  "currentStepIndex": 1, // null when no step is running
  "stepsCount": 5,
  "startedAt": "2026-05-06T12:34:56.000Z",
  "totalCostUSD": 1.42
}
```

This is what answers the "what recipes are on step 3 of 5 right now?" question for an outside AI without forcing it to make a separate `/runs` call. The endpoint is read-budgeted at 60/min per token.

## Related

- [create-cron-job-with-ai.md](create-cron-job-with-ai.md) — cron jobs can execute a recipe on a schedule
- [use-super-prompts.md](use-super-prompts.md) — reusable single-prompt templates (the smaller cousin of recipes)
