---
title: Start a new session
---
# Start a new session

## What it is

A **session** is one chat with Claude Code running inside a specific project's folder — the agent can read, edit, and run code against that folder, and your back-and-forth lives in the session's transcript. You can have many sessions open across many projects at the same time. Starting a new session means: pick a project, hit the launch button (or Ctrl+T / N), and Omniscio spawns a fresh `claude` CLI process inside that project's folder using your active Claude account.

Omniscio also auto-launches a session for you the first time you switch to a **project you've added** that has none, so you can start typing without ever clicking the + button. Most built-in tool panes (like the [Automation Helper](automation-helper.md)) are the exception — they wait for you to tap **+**. Two are not: clicking **[Ask Omniscio](ask-amc.md)** or **[Session Search](session-search.md)** in the sidebar drops you straight into a session, and if that pane has nothing waiting for you it launches a blank one on the spot, exactly like any other project with no open session. That launch carries no prompt (in-code: a blank launch "fire[s] no paid turn"), so nothing is billed until you send something — but a session row does appear. This holds on mobile too.

A session is per-project: the folder shown next to the project's name in the sidebar becomes the session's working directory. Anything Claude does (reading files, running tests, writing patches) happens inside that folder. Sessions persist across app restarts — the conversation history, attachments, and any pending state survive a quit.

## Where to find it

The small **+** icon at the top of the **Sessions** panel — the middle column in the sidebar that lists a project's sessions — is the most-used entry point. The same action is on a keyboard shortcut and in the project's own menu.

## How it behaves

### How to use it

1. **Pick a project.** Click any project in the left **Projects sidebar** (the strip with the colored vertical bars). The middle column will switch to that project's session list.
2. **Click the "+" icon** at the top right of the **Sessions** section header (above the session list, next to the count). The icon shows a Plus glyph; the tooltip reads "Launch new session" plus your keyboard shortcut. The session appears at the top of the list, becomes active, and the composer focuses so you can type your first message right away. If the session list was scrolled down — a long **Needs You** list can push the **Live Sessions** section below the fold — it scrolls to bring the new session into view, so you always see the one you just started. The session row then follows the session's **real** state: it reads **Starting** while the CLI process is still coming up, and only turns green once that process is genuinely up — never ready before it has started.
3. **Or use the keyboard shortcut.** Default is **Ctrl+T** (or just **N**) — both bound to the `newSession` action. Works from anywhere in the app. Pressing it twice in a row does NOT create a duplicate empty session — Omniscio reuses the existing blank session if one already exists in this project, so the keyboard shortcut is safe to mash.
4. **Pick a launch target (optional).** The + button is also a small menu — click it once to launch locally with defaults, or hold to see options:
   - **Local** — the default, runs Claude Code on your machine.
   - **One row per SSH remote** if you've configured any (Settings → SSH Remotes). Picking a remote spawns the CLI on that host instead of locally — the session's working directory lives on the remote.
   - **Isolate** — toggle a per-session **git worktree** override. With it on, Omniscio creates a fresh worktree of the project on a new branch and runs the session against that copy, so two parallel sessions don't stomp on each other's edits. The default for new sessions follows the project's **Isolate by default** setting. See [Session isolation](session-isolation.md) for where that copy is created, what is inside it, and how the work comes back to your project.
5. **Send your first message.** The composer placeholder shows "Send your first message to begin..." while the session is in `ready` state (on a phone, a shorter "Your first message..." so the narrow empty box stays one line instead of wrapping the long hint to two). Type a prompt and hit Enter (or Ctrl+Enter, depending on your **Submit key** setting). On the very first send, Omniscio auto-attaches any files in the project's `.claude/docs/` folder so the agent has your project conventions on turn one.
6. **Or pre-fill the prompt.** A few entry points launch a session with an `initialPrompt` already queued: the **Super Prompts** picker (Ctrl+Shift+K, only on the auto-created `~/Claude` project), recipe and automation runs, and `omniscio://` deep links. The prompt sends as soon as the spawn completes — you don't need to press Enter.

**Picking a tool.** The in-session chooser is organized as a **Harness → Provider → Model → Thinking** picker: you pick the **tool** (the AI harness) first; under Claude Code a **Provider** dropdown picks the brain, then a **Model** within it, then a **Thinking / reasoning** level if that model has one — each dropdown only offers what the previous pick makes available. On the collapsed pill that first picker is labeled **Harness** (so it reads e.g. _"Harness Claude Code"_), matching the muted label style of the **Model** and **Thinking** pills beside it. (Settings still calls these your _tools_ — "Tools in the picker", "Default tool" — so _tool_ and _harness_ are the same thing here.) A _tool_ usually maps one-to-one to a provider (Codex, Gemini, Cursor, OpenCode, Pi, …), but **"Claude Code" groups several**: the native Claude engine plus the Anthropic-compatible vendors that ARE the Claude binary pointed at a different endpoint (**DeepSeek**, **Kimi**). So _Claude Code → DeepSeek_ is the same harness, a different brain.

By default every session uses **Claude**, and the **New Session** button launches a Claude session in one tap. To reach the other tools, turn on **Show alternative AI providers** (Settings → Accounts — off by default), then pick the tool from _inside_ the freshly-created session before you send the first message (see the next paragraph). On desktop the launch button itself also carries a tool menu.

**Customize the list, set a default, or hide the picker entirely.** Settings → Accounts lets you tailor the picker to how you work:

- **Tools in the picker** — a checklist of which tools appear in the chooser (Claude Code is always shown — it's the guaranteed-available engine). Hide the ones you never use to keep the list short.
- **Default tool / model / thinking** — the global default a new session starts on (also settable per-project via [edit-a-project.md](edit-a-project.md), or straight from a fresh session. For **model**, **thinking**, and **provider / harness** alike: a **Project** or **Session** badge on the pill tells you at a glance when the current value isn't your plain global default, and picking something other than your current default surfaces a one-line **"Save as default"** banner just below the pickers — click it for a scope menu tailored to what changed (**Model**: _Only this project · All (engine) sessions_; **Thinking**: _This model · All (engine) sessions · All (harness) sessions · Only this project · Everywhere_; **Provider/harness**: _Only this project · Everywhere_ — the "All …" labels name the actual engine/harness, e.g. "All Codex sessions"). Promoting to a broader scope clears any narrower per-project pin for that dimension so the new default actually takes effect.)
- **"Always use my default"** — when on, the picker is **hidden** and every new session just uses your default. The empty session shows a one-line "This session uses your default tool & model" with a **"Choose for this session"** button that reveals the full picker for that one session if you ever need to override.

Once you're **inside** a brand-new session — on desktop or mobile — you can pick its tool/model/thinking before sending: while the session shows **"Start a new session"**, the **main panel** holds the launch-config pickers (Tool picker plus Model + reasoning-effort pickers for the engines that offer them — see below). Click or tap to pick Codex, Gemini, or another enabled tool — the choice is **remembered instantly and nothing actually starts up yet**; it's applied in one step **when you send your first message**, so you can flip between tools freely. The pickers disappear once you send the first message, because the choices lock in then, and the header shows a small static provider badge instead. (If you pick something but never send, the choice is simply forgotten.) You can set a **per-project default** in [edit-a-project.md](edit-a-project.md). ("Always use my default", above, hides this whole block behind the one-tap "Choose for this session" reveal.)

On a **phone**, tapping the message box opens the soft keyboard, which shrinks the screen. The launch-config pickers scroll into view just above the keyboard so they stay reachable, not hidden behind it, while you type your first message.

**Pick the model — and, on some engines, the reasoning effort.** In those same launch-config pickers, a fresh session shows a **Model** picker for every engine that offers a choice, plus a second picker (**Thinking** on Claude, **Reasoning effort** on Codex) where the engine has one:

- **Claude** — Model: the current lineup (Fable 5.1, Fable 5, Opus 5.5, Opus 5, Sonnet 5.5, Sonnet 5, Haiku 4.5) shows at the top; the older versions (Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Sonnet 4.5) tuck behind an **"Older versions"** row you expand in place — nothing is removed, every model stays pickable (and if a session is already running on an older one, the row opens expanded so you still see your pick). Plus Thinking (Auto / Low / Medium / High / Max). One exception to “nothing is removed”: a model that needs a **newer Claude Code than you have installed** is hidden from the list, and a single grey line takes its place naming the version it needs — e.g. *“Claude Fable 5.1 needs Claude Code 2.1.251+ (you have 2.1.236)”*. Nothing is lost: it reappears on its own the moment you update Claude Code (Settings → Connected Tools). It is hidden because the installed Claude Code doesn’t know that model id, so a session started on it would fail on every single turn with nothing on screen explaining why. If Omniscio can’t read your Claude Code version at all, it hides nothing.
- **Codex** — Model (GPT-6.1 Sol — the default, runs on a ChatGPT-plan login / GPT-6 Astra / GPT-6 Sol / GPT-6 Luna / GPT-5.6 Sol / GPT-5.6 Terra / GPT-5.6 Luna / GPT-5.5) + Reasoning effort (Low / Medium / High / Extra High / Max / Ultra).
- **DeepSeek** — Model (DeepSeek V4 Flash / V4 Pro / V4 Flash Vision — experimental, image input). **Gemini** — Model (3.6 Flash — the default / 3.5 Flash / 3.5 Flash-Lite / 3.1 Pro / 2.5 Flash).
- **Kimi** — Model (Kimi K3 + Kimi K2.7 Code). **Cursor** — Model (a curated 10-model list across the Anthropic / OpenAI / Gemini / Grok families plus Cursor's own Composer and Auto; the model picker doubles as the reasoning-effort picker, since Cursor bakes effort into the model id — see [cursor-provider.md](cursor-provider.md)).
- **OpenCode** — Model (Claude Sonnet 4.5, plus GPT-4o / Kimi K2 / DeepSeek via OpenRouter). **Pi** — Model (Claude Sonnet 4.5 / GPT-4o / Gemini 2.5 Pro / Kimi K2 — Pi is model-agnostic and runs whichever you've configured a key for). Both now take a **per-session** model pick (your choice wins over the global OpenCode/Pi model setting); previously they only followed that one global setting.
- **Anti-Gravity** and **Hermes** — no model picker (each manages its own model / runs a model-agnostic local CLI), so the block shows just the tool chooser. Hermes is a first-class pickable tool, so it appears in the Tool chooser alongside the others.

Under **Claude Code**, the brains it spans — Claude / DeepSeek / Kimi / GLM / MiniMax — are listed in a separate **Provider** dropdown, each shown with its brand icon. The **Model** dropdown then shows only the chosen provider's models (a flat list — no company headings, since it's single-provider). Resellers (DeepInfra, RunInfra, InferX) are never listed as a provider and their copies of these models never appear; which company serves a GLM, DeepSeek, Kimi or MiniMax model, and who pays, is that family's supply list. See [model-vendors.md](model-vendors.md).

They behave exactly like the provider chooser: editable only before your first message (each pick is staged locally and applied when you send), then locked in. The pill **names the model the session will actually use** — e.g. **"Gemini 3.6 Flash"**, **"Opus 4.8"** — whether that's the inherited default or a pick of your own. To see whether it's the default, open the dropdown: the **"Use default (…)"** row is highlighted and marked **Current** — so "default" lives in the menu, not as clutter on the collapsed pill. Click to pick a model for this session, or **Use default** to clear the override and inherit again. The dropdown rows are clean single-tap picks (just like the Thinking menu beside it). To make a model your standing default, pick it for this session — a one-line **"Save as default"** banner appears below the pickers (only while your pick differs from what the session would otherwise inherit), offering **Only this project** (new sessions in this project) and **All (engine) sessions** (new sessions of that engine everywhere); a model belongs to one engine, so those are its two scopes, above the engine's built-in floor. There's no separate "clear without replacing" action in the session picker — to revert a project back to inheriting the engine-wide default, clear its default model field in [edit-a-project.md](edit-a-project.md) instead. (On the built-in virtual projects, which can't carry their own default, only the engine-wide row shows.) After the first message a session you customized keeps a small read-only badge in the header — the engine's logo plus the model name (e.g. the Claude mark + "Claude Fable 5"; for non-Claude engines the provider badge already carries the logo, so the pill shows just the model name — the word "Model" is no longer shown) — so you can see what it's running, while a session on the plain default shows nothing extra. For **Claude** the precedence is **this session's pick → the project's default → your global default** (Settings → Accounts sets the global default, and [edit-a-project.md](edit-a-project.md) sets a per-project default); other engines resolve **this session's pick → that engine's own default** ("Make it my default" writes that per-engine default; Gemini/Codex/Cursor ship with a sensible one seeded so the picker always names a model). Each engine reads only its own default — non-Claude sessions never inherit Claude's model list.

**Thinking / reasoning effort inherits the same way — and can default per model.** The Thinking picker (Claude) / Reasoning-effort picker (Codex) resolves a default just like the model does: **this session's pick → the project's default → the per-model default → your single global Thinking Level** (Codex has no single global, so its per-model default is the lowest layer). Thinking's **same "Save as default" banner** covers the fullest set of scopes when it appears: **This model** sets the level for _that model_ everywhere (so bumping Opus to Max never touches Haiku); **All (engine) sessions** / **All (harness) sessions** set it whenever you use that engine / harness (named for the actual one, e.g. "All Codex sessions" / "All Claude Code sessions"); **Only this project** for this project only; **Everywhere** is Claude's single flat level. Promoting to **Only this project** or broader clears any narrower shadow so the new default takes effect right away. (On a single-engine tool like Codex, "This harness" is dropped — it would duplicate "This provider" — and Codex has no flat "Everywhere" thinking.) You can still set per-model defaults in bulk in **Settings → Session → Per-model thinking level** so, e.g., Opus starts on Max and Haiku on Low without you setting it each time — a level a model's engine doesn't support is validated out, so it never reaches a session. Full behavior: [per-model-thinking-level.md](per-model-thinking-level.md).

**Pick the MCP servers — at launch.** That same block also carries an **"MCP servers"** button next to the model/thinking pickers. Click it to choose which MCP servers this session gets (Default / On / Off per server) before you send the first message — the choice applies on the session's first launch. It opens the same editor as the session's **⋯ menu → MCP servers**, just up front. The button appears on real projects only (it's hidden on virtual/inbox projects, where MCP doesn't apply). Full behavior: [mcp-servers.md](mcp-servers.md).

What happens if no account exists yet: the spawn returns an error and a system message in the chat says you need to add a Claude account before sessions can run. Open Settings → Accounts and use **Log In with Anthropic** to add one. See [add-a-claude-account.md](add-a-claude-account.md).

What happens when every account is at its rate limit: the session row shows the amber "Needs You" / "Rate Limited" sub-state, the chat surfaces a banner with the absolute reset time, and a sidebar-wide **"All accounts are at their usage limit"** banner appears at the top of the Sessions panel. Omniscio auto-resumes when the first account regains capacity, so you can usually leave the session alone. See [account-pool.md](account-pool.md) and [session-stuck-in-needs-you.md](session-stuck-in-needs-you.md).

What happens if the project's folder no longer exists on disk: the session lands in `error` and a red **"Project folder is missing"** banner offers a "Recreate Folder" button. See [project-folder-missing.md](project-folder-missing.md).

What happens if a launch fails: an error toast appears carrying a **Try again** button that re-runs the same launch for you. The toast names the reason, so if the cause is something you can fix directly (no account yet, a folder that moved) do that first and then hit **Try again**; many other launch failures are simply transient and clear on a second try. The button is offered on both launch paths — the **+** button and **Ctrl+T** — because they run the same launch and the same error handling.

What happens if the app closes while a new session is starting: the request survives. Once Omniscio has accepted your New Session click, closing or losing the app before the process came up does not lose it — on the next start Omniscio brings that session up again, with the opening message you had already given it if there was one, or as an empty session if there wasn't. It is never turned into a broken/error session while it waits, and it is never resumed with a generic "please continue" in place of what you asked for. A request that has not started is kept until it starts or you cancel it — it is not dropped for being old. See [durable-new-session-request-contract.md](../../.claude/memory/contracts/durable-new-session-request-contract.md).

### Which hub a session starts in when you don't pick one

Most entry points already know their hub — the "+" button uses the active project, a repo tile
launches into its own repo, and Quick Launch has its own _Quick Launch Default Project_ setting
(Settings → Quick Launch). The one place with a smarter answer is the **Start session** button on an
**alert**.

An alert is Omniscio talking about itself — a failed land, a stuck gate, a bug report — so a session
started from one usually belongs in the repo you develop the app in, not the general-purpose
`~/Claude` workspace. That button resolves its hub like this:

1. **The repo the alert names**, when it has one (its saved session repo, else the repo it is
   grouped under).
2. **Your setting** — Settings → Inbox → _Start alert sessions in_. An explicit pick always wins
   over the automatic guess below.
3. **Automatic** (the default) — the hub whose folder is a checkout of **Omniscio itself**. It is
   found by READING THE REPO (a `package.json` identity check, seeded by the `~/.claude/amc-repo.path`
   pointer `npm run setup` writes), never by the hub's NAME — a real install can hold "Omniscio",
   "Omniscio Marketing" and "AMC Marketing" side by side, so a name match has no principled answer.
   A linked git worktree is never chosen, only a main checkout.
4. **The built-in Claude hub** (`~/Claude`).

If you have no Omniscio checkout registered as a hub — which is every non-developer install — step 3
finds nothing and the behavior is exactly as it always was: Claude is the default. Claude always
stays available in the picker either way; it just stops winning automatically. A setting pointing at
a hub you later deleted is ignored rather than obeyed, so the picker is never left pre-selecting
somewhere a session cannot start.

Every OTHER start-session dialog — inbox cards, drip, the weekly-summary suggestion, and the Weekday
Reflection Nudge — deliberately keeps the plain Claude default, and so does the mobile **Session**
tile; only alerts opt in. Implementation:
[app-repo-hub.ts](../../src/main/services/project/app-repo-hub.ts) resolves the hub and flags it on
the project list, [alert-session-default-hub.ts](../../src/renderer/src/features/alerts/alert-session-default-hub.ts)
applies your setting over it, and [spawnable-hub-options.tsx](../../src/renderer/src/components/ui/spawnable-hub-options.tsx)
puts the winner first in the picker. Contract: [project-picker-icon-contract.md](../../.claude/memory/contracts/project-picker-icon-contract.md).

### How it works

The "+" button lives in [SessionsSidebar.tsx](../../src/renderer/src/features/dashboard/SessionsSidebar.tsx) (look for `launchTriggerRef` and the `data-tour="launch-session-button"` attribute). It calls `handleLaunchSession()` which delegates to `launchSession()` on the renderer's [session-store.ts](../../src/renderer/src/stores/session-store.ts), which invokes the `SESSION_LAUNCH` IPC channel. The keyboard shortcut path runs through [useKeyboardShortcuts.ts](../../src/renderer/src/hooks/useKeyboardShortcuts.ts), which dispatches the `newSession` action to `handleNewSession` in [session-actions.ts](../../src/renderer/src/hooks/keyboard-shortcuts/session-actions.ts) — same `launchSession` action, same error handling and retry, so toasts and session-limit messages are identical between Ctrl+T and the + button. Default bindings live in [keybindings.ts](../../src/shared/keybindings.ts).

The IPC handler is in [session-handlers.ts](../../src/main/ipc/session-handlers.ts) (`IPC.SESSION_LAUNCH`). For the regular Claude provider, it first calls `findBlankSession()` from [queries-sessions.ts](../../src/main/db/queries-sessions/index.ts) — if there's already a blank session in this project (zero non-system messages, no draft text, no draft images, no scheduled response, not in a recipe lane), Omniscio navigates to that session instead of creating a duplicate. Otherwise it falls into [session-create.ts](../../src/main/services/session/session-create.ts) `createSessionWithPrompt()`, which: increments the project's session counter, writes the session row, optionally creates a git worktree (isolation), and calls `processManager.launch()` from [process-manager.ts](../../src/main/process/process-manager.ts).

Three guarantees ride along with every launch. **A failed launch is retryable in place:** both launch paths attach a **Try again** toast action that re-runs the same launch — `handleLaunchSession` in [SessionsSidebar.tsx](../../src/renderer/src/features/dashboard/SessionsSidebar.tsx) for the **+** button, `handleNewSession` in [session-actions.ts](../../src/renderer/src/hooks/keyboard-shortcuts/session-actions.ts) for Ctrl+T — with the humanized reason kept in the copy. **An accepted request cannot be lost:** the instant the row exists, [session-launch-pending-intent.ts](../../src/main/services/session/session-launch-pending-intent.ts) records a `launch-pending` intent on the existing recovery rail and clears it in the spawn tail's `finally`; if the process dies inside that window the row is left behind, and on the next start [session-recovery-rearm.ts](../../src/main/services/session/session-recovery-rearm.ts) drives it again — with the user's own last message when there is one, and as an empty start when there is not (every other intent kind is still dropped when it has nothing to replay). **The sidebar tells the truth the whole time:** the optimistic blank row mints `status: 'starting'` with no CLI session id ([session-store.ts](../../src/renderer/src/stores/session-store.ts), `buildOptimisticBlankSession`), and the `SESSION_LAUNCHED` push now carries the row's real status instead of a hard-coded `ready`, so a session with no process is never shown as ready. Invariants: [durable-new-session-request-contract.md](../../.claude/memory/contracts/durable-new-session-request-contract.md) and [session-start-visibility-contract.md](../../.claude/memory/contracts/session-start-visibility-contract.md).

**Why it feels instant (the pre-warm).** When you open a project, Omniscio quietly pre-warms one blank `ready` session for it in the background (a debounced `SESSION_ENSURE_BLANK`, auto-numbered "Session N" like any new session). So when you then hit **+** / **Ctrl+T** — or tap **New Session** on a phone — `tryInstantNewSession()` flips straight to that already-ready session with **zero IPC**, instead of waiting on the full spawn above (worktree + CLI process, which over a phone's tunnel is several seconds). This runs on desktop **and** mobile: a phone is a thin client of the same desktop main process, so the pre-warm does identical work, and skipping it on mobile (a bug fixed 2026-06-17) was what made mobile "New Session" feel slow. On the rare cold tap before a warm blank exists, the mobile button navigates to the session view immediately and then **reveals the real, typable composer the moment the new session activates** — the `SESSION_LAUNCHED` push activates the row _before_ the ~7-8s worktree build finishes, so you start typing right away while the spawn completes in the background, instead of staring at a non-typable "Starting session…" spinner for the whole spawn. If the launch request later times out over the tunnel but the session was already created, you **stay in that live session** rather than getting bounced back — which is what finally makes the inbox-group **+** (always a cold tap, since those projects are never pre-warmed) as reliable as **New Session**. Gated by the **Pre-load new sessions** setting (Settings → Performance, on by default). Mechanism + invariants: [keep-alive-pool-contract.md](../../.claude/memory/contracts/keep-alive-pool-contract.md).

The launch-config pickers (shown under the "Start a new session" title) reuse the same `ChangeProviderButton` + `SessionModelThinkingChips`, on desktop AND mobile. [SessionPanel.tsx](../../src/renderer/src/features/sessions/SessionPanel.tsx) computes one `bodyHostsStartConfig` predicate — true on a fresh **generic** empty state with at least one control to show, keyed on **zero NON-system messages** (every new session is created with a "Session ready" system row, so the gate counts non-system messages — mirroring the backend `sessionHasNonSystemMessages` rule — never total `messages.length`; counting total was the 2026-06-07 regression that stranded the pickers in the header on every real new session) and not the bootstrap-seed / history-loading / Ask Omniscio / Search / first-mission sub-branches — from the pure helpers `getEmptyStateProvider()` ([empty-state-provider.ts](../../src/renderer/src/features/sessions/empty-state-provider.ts), which gates the chooser on the **Show alternative AI providers** toggle + `isSwitchableProvider()` — generalized from the old hardcoded claude/codex/gemini check, so deepseek/kimi/cursor/opencode/pi sessions also get a chooser) and `resolveEmptyStateVariant()` ([empty-state-variant.ts](../../src/renderer/src/features/sessions/SessionPanel/empty-state-variant.ts)). That single predicate drives BOTH the body render and the header **yield**: when the body hosts the pickers, the header's chooser (`getHeaderChooserProvider`, now gated on `emptyStateActive`) and its Model/Thinking chips are suppressed, so the pickers appear in exactly **one** place — never doubled, never vanished. The header keeps its pickers only as the **fallback** for the sub-branches where the body block does NOT render — a non-generic variant, history-loading, or a bootstrap-seed bubble; a plain blank session (only a "Session ready" system row) hosts them in the **body**. After the first message the header shows a static `ProviderBadge` + any read-only override badge; that static badge renders right after the session name on **both desktop and mobile** (before the snooze/recipe chips), default-inclusive so **every** session shows its engine mark (Claude in brand orange included), so a crowded screen can't shove it under the right-side toolbar (see [mobile-in-session-provider-chooser-contract.md](../../.claude/memory/contracts/mobile-in-session-provider-chooser-contract.md) § "Static header badge"). Switching a session that turns out to already have history is rejected by the handler-side `sessionHasNonSystemMessages` guard, so the gate is a UI convenience, not the source of truth.

Because that block lives in the scrollable body, the mobile soft keyboard (which shrinks the layout viewport via `interactive-widget=resizes-content`) would otherwise clip it below the collapsed message area. [SessionEmptyState.tsx](../../src/renderer/src/features/sessions/SessionPanel/SessionEmptyState.tsx) listens for the keyboard to settle (a debounced `visualViewport` resize) and scrolls the body to its bottom — where the pickers sit — keeping them visible above the keyboard **without** relocating them out of the body (so the one-place invariant holds). Mobile-only; desktop is untouched.

Picking a provider / model / thinking does NOT hit the backend on tap — each pick is staged locally in [pending-start-config-store.ts](../../src/renderer/src/stores/pending-start-config-store.ts) (key presence marks a field as staged; `null` = explicit "use default"). The staged config is applied in ONE step at first-message submit by `applyPendingStartConfig` ([apply-pending-start-config.ts](../../src/renderer/src/lib/apply-pending-start-config.ts)), called at the top of BOTH `sendResponse` and `sendResponseAndNavigate` so a first message sent any way (composer, voice) picks it up. `SESSION_CHANGE_PROVIDER` is therefore DB-only now (no eager terminate/relaunch — that would churn an engine the user may never use and double-spawn at submit); the first message's spawn reads the persisted provider/model/thinking fresh and starts the chosen engine exactly once. Invariants: [start-config-staging-contract.md](../../.claude/memory/contracts/start-config-staging-contract.md).

The **Tool** grouping is renderer-only presentation: [picker-tools.ts](../../src/renderer/src/lib/picker-tools.ts) derives the tool list from the registry's `runtimeKind` (Claude Code spans the `claude` + `anthropic-compat` providers, so a future compat engine folds in automatically), and the customize-list (`pickerToolIds`) / master toggle filter it. The backend stays per-provider — switching brains is an explicit **Provider** pick (which stages the provider via `setProvider`), and the **Model** dropdown then lists only that provider's models, so a Model pick is always same-provider. (The store keeps a cross-provider `setProviderModel` for other surfaces, but the in-session Model dropdown no longer reaches it.) **The one exception is a session whose engine is a reseller host** (an older DeepInfra session): its Model dropdown draws the cross-provider, deduped list and a pick under a DIFFERENT maker stages provider + model together via `setProviderModel` — see [model-vendors.md](model-vendors.md). **"Always use my default"** (`alwaysUseDefaultStartConfig`) hides the block via a single `hideStartConfig` predicate in [SessionPanel.tsx](../../src/renderer/src/features/sessions/SessionPanel.tsx) that suppresses it in BOTH the body AND the header (a body-only suppression would just bounce the picker up to the header), with a per-session `revealStartConfigOnce` flag behind the "Choose for this session" button that resets on session change.

The **named default + "Save as default" banner** route through one helper, [provider-default-model.ts](../../src/shared/providers/provider-default-model.ts): `getProviderGlobalDefaultModel` resolves each engine's editable global default (claude → `defaultModel`; OpenCode/Pi → `opencodeModel` / `piModel`; every other engine → the `providerDefaultModels` map, falling back to the registry pin in [provider-models.ts](../../src/shared/providers/provider-models.ts), validated against the engine's selectable list), and the `build*Patch` writers persist a promotion to the SAME key the read + the spawn (`resolveSpawnModel`) use — so the picker label can never disagree with what launches. **Model, thinking, and provider/harness defaults are ALL promoted through one banner**, [SetDefaultHint](../../src/renderer/src/features/sessions/SetDefaultHints.tsx), rendered below the pickers by [SessionEmptyState.tsx](../../src/renderer/src/features/sessions/SessionPanel/SessionEmptyState.tsx) and by the mobile **create-on-send** composer too (StartConfigPicker value mode with `showDefaultHints`, desktop parity). Each dimension is its own hook (`useModelDefaultScopes` / `useThinkingDefaultScopes` / `useProviderDefaultScopes`) that returns nothing while the current pick already matches the resolved default, so the banner appears only once you've actually changed something; when it does, it lists every changed dimension on one line with ONE [SetDefaultScopeMenu](../../src/renderer/src/features/sessions/SetDefaultScopeMenu.tsx) offering that dimension's scopes: **Model** → Only this project · All (engine) sessions (a model is engine-bound), over the engine's built-in floor; **Thinking** → This model · All (engine) sessions · All (harness) sessions (multi-provider tools only) · Only this project · Everywhere (Claude's flat `defaultThinkingLevel` only); **Provider/harness** → Only this project · Everywhere. The per-scope writers live beside their reads: model → `buildProviderDefaultModelPatch` / `buildProjectDefaultModelPatch`; provider → `settings.defaultProvider` / `buildProjectDefaultProviderPatch`; thinking → `buildModelDefaultThinkingPatch` (per-model) / `buildProviderDefaultThinkingPatch` / `buildHarnessDefaultThinkingPatch` / `buildProjectDefaultThinkingPatch` / the flat `defaultThinkingLevel`. The three thinking conditional layers fold in ONE place — `resolveModelGlobalThinking` (per-model → per-provider → per-harness → flat) — read by the Claude spawn, the Codex spawn, and the chip, so the pill can't drift from what launches. **Promoting a MODEL or THINKING pick to a global-tier scope also clears the per-project shadow** (`buildClearProjectModelOverridesPatch` / `buildClearProjectThinkingOverridesPatch`, merged into the same atomic write) so the broader default actually takes effect instead of staying silently shadowed — provider is the deliberate exception (a per-project engine choice is intentional, so promoting it never wipes a project pin). The menu tags the winning scope **"In effect"** (never a checkmark — a row whose action is "save here" must never look like it's already saved there) and shows each scope's `currentValueLabel` so you can see what it's set to before you overwrite it. There is no separate "clear without replacing" action in this banner — reverting a project's model/thinking pin back to inheriting the engine default means clearing that field in [edit-a-project.md](edit-a-project.md) instead. Each chip also shows a **source badge** (`sourceBadgeFor` — amber "Project" / indigo "Session") whenever its value isn't the plain global default, so the "why did it revert to Luna" shadow is visible at a glance. Every write `await`s the save and only toasts success once a store re-read confirms it took, so a failed save never shows a false confirmation (see [ipc-error-surfacing-contract.md](../../.claude/memory/contracts/ipc-error-surfacing-contract.md)). Gemini/Codex/Cursor are seeded in the registry pin so the picker names a model on day one. Invariant: [start-config-staging-contract.md](../../.claude/memory/contracts/start-config-staging-contract.md) `model-pill-names-the-default`/`provider-chip-is-a-provider-picker`/`new-session-provider-precedence` (provider) + **`set-default-hint-is-the-one-surface`** (`SetDefaultHint` is the one surface for a Model/Thinking default — the per-level editor `superseded-per-level-editor` introduced is retired).

The actual spawn is `spawnLocalStreamJson()` — same module. It calls `pickActiveAccount()` (synchronous, cache-only) which delegates to the account pool's tiered selection (pinned → primary capacity → extra-capacity → API key, gated by `allowApiKeySessionSpawn`). The chosen account's credentials get injected into the spawned CLI's environment as `CLAUDE_CODE_OAUTH_TOKEN` (login) or `ANTHROPIC_API_KEY` (api key). The CLI is spawned with `--output-format stream-json --input-format stream-json --verbose --permission-prompt-tool stdio`, with `cwd` set to the project's folder (or the worktree path if isolated). On every subsequent message in the same session, Omniscio re-spawns with `--resume <cliSessionId>` so multi-turn context is preserved. See [process-management.md](../../.claude/memory/process-management.md) for the full lifecycle.

## Related

- [add-a-project.md](add-a-project.md) — sessions live inside a project; add one if you don't have any yet
- [add-a-claude-account.md](add-a-claude-account.md) — every spawn needs an account; this is where you add yours
- [mobile-in-session-provider-chooser-contract.md](../../.claude/memory/contracts/mobile-in-session-provider-chooser-contract.md) — the invariant behind "one provider chooser on mobile, not two" (header chooser yields to the centred switcher)
- [start-config-staging-contract.md](../../.claude/memory/contracts/start-config-staging-contract.md) — why picking a provider/model/thinking is instant: picks are staged locally and applied at first-message submit (no engine spin-up on tap)
- [keep-alive-pool-contract.md](../../.claude/memory/contracts/keep-alive-pool-contract.md) — why launching a session is instant (the pre-warmed blank), on desktop AND mobile
- [account-pool.md](account-pool.md) — when you have multiple accounts, the pool decides which one this session uses
- [send-a-message.md](send-a-message.md) — the next thing you do after the session is up
- [attach-a-file.md](attach-a-file.md) — drop a file into the composer alongside your first message
- [use-super-prompts.md](use-super-prompts.md) — Ctrl+Shift+K launches a session pre-filled with a curated starter prompt
- [project-folder-missing.md](project-folder-missing.md) — what to do if the spawn fails because the folder moved
- [durable-new-session-request-contract.md](../../.claude/memory/contracts/durable-new-session-request-contract.md) — why a session you asked for survives closing the app, and comes back with your own opening message
- [session-start-visibility-contract.md](../../.claude/memory/contracts/session-start-visibility-contract.md) — why the sidebar never shows a session as ready before its process is actually up
