Bake-Off (fan-out)
Run the same prompt against several models at once, side by side, and keep the answer that wins — how to open a Bake-Off, save a setup as a preset, let contestants see each other's work, and what stops it running away with your spend.
What it is
Name: shown in the UI as Bake-Off. The internal/code name stays fan-out (setting
fanoutEnabled, IPCSESSION_FANOUT, control-server route/fanout, thefanoutfeature id) — renaming the label never renames the identifiers.
Status: shipped — visible to everyone. Bake-Off ships in the Prompt Tools sidebar group (its Bake-Off row) and via the command palette; the
fanoutfeature is nowstatus: 'shipped', so the oldfanoutEnabledLab toggle no longer gates it. This page is the authoritative reference for how it works.
Bake-Off lets you type one prompt once and launch it as many separate sessions at the same time — each session running in a project you choose, on a harness/model setup you choose. It's the "run this same task across everything and compare" launcher: the same prompt against Opus vs. Sonnet vs. Codex, or against three different repos, or any mix.
You don't build a list of individual sessions row-by-row. Instead you fill in two sections and Bake-Off launches the cross-product:
- Projects / repos — a multi-select dropdown of your real projects. Pick as many as you want.
- Harness & model setups — a list of setups. Each setup is the exact same configuration bar you get on a new session (Harness · Provider · Model · Thinking level · MCP servers · Local/Cloud), reused as a shared module so there is only one thing to maintain, not two. Add as many setups as you want.
It then launches one session per (project × setup) pair. A live line shows the math — "2 projects × 3 setups = 6 sessions" — and the Launch button carries the count. If a setup leaves a field on default, the session inherits the project/global default exactly like a normal new session.
Each session is named Bake Off [<model>]: <title>. The model (or the provider, when a setup
names no explicit model) comes first so you can tell the variants apart in the sidebar even when
the name is truncated, and <title> is a real AI-generated title of your prompt — produced by the
same titler every other session uses, not the raw prompt text (so a prompt that opens with a URL no
longer becomes a URL "name"). The title is generated once per launch and shared across all the
variants (they run the same prompt); a name you type into Bake-Off replaces the <title> half.
Launching is instant (fire-and-forget). Clicking Launch closes Bake-Off immediately and runs the launch in the background — you're never held on a "Launching…" spinner. A summary toast confirms the result once it settles. If the launch fails — the whole batch errored, or every target was skipped so nothing started — an error toast appears with a Try again link that reopens Bake-Off (a fresh dialog) so you can adjust and relaunch. (Above 10 sessions it still asks you to confirm before it dismisses.)
Where to find it
How you open it
Two ways, both opening the same dialog described above:
- The Bake-Off row in the Prompt Tools sidebar group (an action row — clicking it opens the dialog rather than switching projects).
- The command-palette action "Bake-Off…" (Quick Launch / command palette).
How it behaves
Presets (save a setup and reload it)
Bake-Off can save your configuration as a named preset so you can rebuild a comparison in one click instead of re-picking projects and setups every time. The Presets bar sits at the top of the dialog:
- Save — names the current setup (its projects, its harness/model setups, and — if you leave the "Include prompt" box checked — the prompt) and stores it. Saving under a name that already exists asks you to confirm an overwrite.
- Load a preset… — pick a saved preset from the dropdown to drop it back into the dialog. It restores your projects and setups; it fills the prompt only if that preset saved one, so a "contestants-only" preset (saved with the prompt box unchecked) never wipes a prompt you are in the middle of writing.
- Delete — removes the loaded preset after a confirmation, with an Undo in the toast.
Presets are stored in your app settings (the same place your other preferences live), so they persist across restarts. A preset that points at a project you have since deleted still loads — the missing project is simply dropped. A preset that names a model or harness that has since been retired also loads fine; that one setup is just skipped-with-a-reason at launch, exactly like any other bad target.
Contestants who know about each other (optional)
By default the contestants are strangers: each one gets your prompt and nothing else, and none of them knows the others exist. That is usually what you want — the whole point of a bake-off is comparing independent attempts.
The "Let the contestants know about each other" toggle, under the harness setups, changes that. It is off by default, and turning it on prefixes every contestant's first prompt with a short briefing — sent to the model, never shown in your chat, which still displays exactly what you typed.
Each contestant is told:
- that it is one of N sessions running the same task at the same time, and which one it is;
- every other contestant — its model (the same label the sidebar shows), its project, and its real session id;
- the exact call to message one:
POST /sessions/<their id>/peer-message, which token file to authenticate with, and which header to send.
It never tells them what to do with that. The copy states who is there and how the channel works and stops — no "collaborate", no "stay independent". That is a deliberate owner decision: a nudge in either direction would quietly decide the outcome of the comparison you launched. What the contestants do with the channel is up to them.
The ids are real, not a lookup hint. Bake-Off mints every contestant's session id before it starts launching, so contestant 1 can be told contestant 3's id even though contestant 3 does not exist yet. Without that, no contestant could ever know about the ones created after it.
Details worth knowing:
- A skipped target never appears in anyone's roster. Validation runs first, so if a project was deleted the survivors are told there are two contestants, not three.
- One contestant gets nothing. If only a single session survives, there is no "you are 1 of 1" preamble — the prompt is untouched.
- A cloud contestant gets the roster but no messaging recipe. It runs on a remote VM that cannot reach the control server on your machine, so it is told that plainly instead of being handed an address that would fail. It still appears in everyone else's roster and can still be messaged by them — delivery happens inside Omniscio, not over the network to the VM.
- A contestant that failed to start refuses messages, and the briefing says so, so a peer does not read a refusal as its own mistake.
- Presets remember the toggle. A preset saved before this existed loads with it off.
Guardrails (why it can't hurt you)
- Capped at 20 sessions per launch. Over the cap, the Launch button is disabled with a hint — it never silently drops targets. Above 10, it asks you to confirm first ("This starts N sessions").
- It can't freeze your machine. Every fan-out spawn is a background spawn and is NOT marked user-initiated, so it flows through Omniscio's existing session-spawn pacer (the same throttle that protects the box from a burst of respawns). A fan-out is a controlled, paced burst — not an un-throttled storm. Session count is never the cause of a freeze; the pacer serializes the launches.
- Bad targets are skipped, not fatal. If a target's project is missing, isn't a real spawnable folder, or names a model a harness no longer offers, that one target is skipped with a reason and the rest still launch. You get a "launched X, skipped Y" summary toast.
- A paced target is queued, not skipped. When the box is already busy the spawn pacer may accept a target and hold it for a moment instead of refusing it. That target is queued — it is coming, and the summary says so — rather than reported as a failure or silently dropped.
- MCP applies to the first turn. Because a fan-out session's whole point is the one first turn, any per-setup MCP-server override is stamped on the session row before the first spawn (not after), so it takes effect immediately.
For agents
Under the hood (for agents)
- UI:
src/renderer/src/features/fanout/FanOutDialog.tsx(two sections + cross-product launch),ProjectMultiSelect.tsx(built on the sharedAnchoredPopover). Each setup renders the sharedStartConfigPickerin value mode — the same component the new-session empty state renders in session mode (src/renderer/src/features/sessions/StartConfigPicker.tsx+start-config-sink.ts). Value mode drives a plain config value with no live session and no store/IPC writes. - Backend: the renderer calls
IPC.SESSION_FANOUT→fanoutLaunch(...)(src/main/services/session/fanout-launch.ts), which loops the targets through the normallaunchSessionInMainchokepoint (one per target,background: true,source: 'fanout'). Provider, model, thinking, MCP, and Local/Cloud are all applied the same way a normal single launch applies them. - CLI:
POST /fanouton the control server (cli-server-fanout-routes.ts) mirrors the exact deny-by-default auth ofPOST /project/:name/new(header Bearer; agent-session tokens are refused unlessallowAgentTokenSpawnis on). Body is the same{ prompt, targets }shape. - Plugins: a first-party plugin can call
session.fanout(webview or worker bridge) behind a newsessions.launchAnypermission — but the gate is builtin-only: a marketplace or dev plugin that declares the permission is still refused (dispatchPluginFanoutenforces builtin source + declared permission before it ever reachesfanoutLaunch). - Not on your phone:
SESSION_FANOUTis blocked from the mobile/web WS bridge, because it spends real money. - Peer awareness:
peerAwarenessonfanoutLaunchSchema.fanoutLaunchsplits into a validate+RESERVE pre-pass (each surviving target gets arandomUUID()before any spawn) and a launch pass that renders one briefing per contestant via the purebuildBakeOffBriefinginsrc/shared/fanout.ts. The reserved id rideslaunchSessionInMain's main-processoptsbag — never the renderer schema — and is forwarded by all three row-creating branches; a structural guard (tests/unit/lint/launch-forwards-reserved-id.test.ts) fails the build if one stops. The flag is part offanoutDedupKey, so toggling it makes a genuinely different batch. Invariant F12 infanout-launch-contract.md. - Presets: saved in the governed
fanoutPresetssettings array (race-safe per-item merge, reusingSETTINGS_PATCH_ARRAY_ITEM— no new channel). Shape + Zod insrc/shared/fanout-presets.ts(deliberately LENIENT so a retired provider/model can't poison the whole array); renderer mappers + the Presets bar insrc/renderer/src/features/fanout/. Loading a promptless preset never clobbers a typed prompt.
Contract
Invariants (skip-with-reason, cap, background+paced, MCP-before-spawn, the fanout source, the gated
CLI route, the phone-block, the first-party plugin gate, the one shared config module, the
fire-and-forget launch + re-open-on-problem UX, and opt-in peer awareness with pre-reserved ids) are
locked in fanout-launch-contract.md. The plugin
builtin-only gate is plugin-bridge-hardening-contract.md session-fanout-is-builtin-only.
Related
The provider pages — Claude providers, Gemini, DeepSeek and the rest — explain which models a Bake-Off can put in the ring and what each costs.
Last verified 2026-09-28