Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Instant new session (Ctrl+T fast path)

Starting a new session feels instant: the empty panel appears and the text box is focused in the same frame you press the key, with no spinner and no flicker. It works by quietly keeping one real, empty session prepared in the background, so the work that used to happen after the keypress has already happened before it.

What it is

What it does

Pressing Ctrl+T (or N) to start a new Claude Code session in the current project now feels instant — the empty session panel appears and the composer textarea is focused in the same frame you press the key, with no spinner, no "Starting…" flicker, and no round-trip to the main process before the panel paints.

It works by quietly preparing one real, empty session ahead of time. While you're working in a project, Omniscio keeps a single genuine blank session ready in the background — a real database row with a real CLI process, not a fake placeholder — and keeps its panel pre-rendered off-screen. When you hit Ctrl+T, Omniscio just reveals that already-prepared panel. The work that used to happen after you pressed the key (create the row, spawn the process, mount the panel, attach the textarea) has already happened before you pressed it.

If no prepared blank is available for any reason — you've already started typing in it, the kill switch is set, the project can't be pre-warmed, or the background prep hasn't finished yet — Ctrl+T silently falls through to the normal launch path. You get exactly the behavior you had before: a brief spawn, then the panel. Nothing breaks; it's just not as fast that one time.

Where to find it

There is nothing to open — press Ctrl+T (or N) in the current project and the new session is simply there, text box focused. It is on by default; Settings → Performance → "Pre-load new sessions" can turn it off.

How it behaves

What the user sees

  • Ctrl+T / N is immediate. The new blank session is active and the cursor is in the composer before you've finished lifting your finger off the key. You can start typing your first message with zero perceptible delay.
  • No duplicate empty sessions. Mashing Ctrl+T five times in a row does not create five empty sessions. The first press reveals the prepared blank; the rest land on that same session (the background prep is idempotent — it reuses an existing blank instead of minting a new one).
  • Your typed text is never lost. If you've started typing a message into the blank session and then press Ctrl+T again, Omniscio will not swap you onto a different blank or reset your draft. The fast path deliberately refuses any blank that has draft text in it, so a half-written message is safe. (This is a direct defense against a historical bug — see "The two regressions it avoids" below.)
  • The session is real from the first frame. There's no moment where a fake/temporary session shows up and then gets replaced by "the real one." What you switch into on Ctrl+T is the real session, with a real ID, that was created in the background — so there's no flicker, no re-mount, and no risk of your first keystrokes landing on a placeholder that's about to be thrown away.
  • It only ever opens a session Omniscio prepared for you. A session that something else created — an agent spawning work through the CLI, a bug-intake auto-spawn, a wave of audit fixes — is never a candidate, even while it is still empty and waiting for its first message. That distinction is not cosmetic: such a session can sit empty for a while, because its prompt waits behind worktree setup, so "looks empty at this moment" is not the same as "yours to start".
  • It's scoped to the project you're in. The prepared blank belongs to the active project. Switch to a different project and Omniscio prepares a blank for that project (after a short debounce), so Ctrl+T is fast in whichever project you're currently looking at.

How pre-warmed sessions look in the sidebar

A pre-warmed blank looks like any other new session — it gets the same auto-numbered "Session N" name (e.g. "Session 42") a session gets when you create one yourself, and it isn't badged or specially marked. It simply sits ready at the top of the project's list. The fast path recognizes it structurally (an empty ready session), never by its name, so the ordinary numbered name costs it nothing, and its sidebar status dot is a plain solid dot like every other session's.

Once you press Ctrl+T and start typing, your first message triggers the usual automatic title generation, so "Session N" is replaced by a real title describing your conversation (exactly like any other new session). If you use numbered names instead of AI titles, the blank simply keeps its "Session N" — already the correct final name, nothing to replace. The recognizer that lets the auto-titler rename a placeholder blank lives in session-placeholder-name.ts — see session-placeholder-name-contract.md.

Blanks were briefly (2026-06-11 → 2026-07-01) labeled "Session Ready" with a pulsing dot; that name stranded permanently whenever an AI title never landed (always, in numbered-name mode), so it was reverted to the plain numbered default. The "Session Ready" recognizer is kept only so any blank still saved with the old name gets re-titled.

Turning it off

The pre-warm is on by default. If you'd rather not have Omniscio create sessions in advance — for example, if you want tighter control over what gets created in the background — you can turn it off in Settings → Performance → "Pre-load new sessions." When off, Ctrl+T works exactly as it did before this feature: a normal session spawn (~700 ms), no background preparation, no pre-created blank waiting in the sidebar.

There's also a developer-level environment variable kill switch (AMC_DISABLE_INSTANT_NEW_SESSION=1) that force-disables both the background pre-warm and the Ctrl+T fast path regardless of the setting. The setting and the kill switch are independent gates — either one being off disables the feature.

The two regressions it avoids

This feature exists in the shadow of two specific bugs from Omniscio's history. Both were the reason a naive "make Ctrl+T instant" attempt was dangerous, and the design is shaped around never reintroducing them.

  1. The phantom session. An earlier fast-new-session attempt showed a fake session immediately (a temporary client-side placeholder) and then swapped in the real session once the backend caught up. The user's words: "It would only be a fake thing at the start, and then the real thing would appear later on." That swap caused flicker, lost focus, and a window where keystrokes could land on the soon-to-be-discarded placeholder. This design never uses a placeholder. The blank that Ctrl+T reveals is a genuine sessions row, created in the database by the background pre-warm before you ever press the key. There is nothing to swap — the "fast" session and the "real" session are the same object.

  2. The typed-text overwrite. A second historical bug "sometimes it would overwrite the user's message that they'd already started typing." If you'd begun composing a message and then triggered a new-session action, the draft could get clobbered. This design refuses to touch any blank with a draft. The "is there a typed draft?" check lives in the renderer's store layer (where drafts actually live), and a blank that has any draft text is excluded from the fast path — so Ctrl+T can never land you on, or reset, a session you're mid-way through typing into.

What didn't change

  • The "+" button. A plain click on the + icon in the Sessions panel header (or on the empty-state New Session button) takes the same instant pre-warmed path as Ctrl+T (tryFastNewSession), and falls back to the normal launch path on a miss. Only a specialised launch — an SSH remote, a chosen provider, forced isolation, or an OpenClaw project — runs the full launchSession() path. (The slow path still benefits from the backend's existing findBlankSession() reuse, so it also won't create duplicate blanks — that behavior predates this feature.)
  • Blank-session reuse on the slow path. The backend's findBlankSession() dedupe (don't create a second empty session when one already exists) is unchanged and still runs on the SESSION_LAUNCH path. The pre-warm uses the same helper, so the two paths agree on what counts as a reusable blank.
  • openclaw / codex / gemini / antigravity / opencode. Those projects are never pre-warmed and always take the normal launch path on Ctrl+T. DeepSeek and Kimi projects ARE pre-warmed — they run the Claude binary, which is what the gate actually tests.
  • Mobile. Mobile gets the instant reveal too — and as of 2026-06-20 the panel is pre-built there as well. The background pre-warm (the blank ready row) runs on mobile, and all three mobile new-session controls — the footer New Session button, the inbox-group +, and the breadcrumb + — route through ONE shared action (startNewSessionMobile in launch-and-reveal-session.ts) and so hit the fast path (tryInstantNewSession) exactly like desktop Ctrl+T (skipping it on mobile was a bug fixed 2026-06-17; the breadcrumb + was the LAST to be unified, 2026-06-23 — it previously called launchSession directly and only navigated on a non-null return, so a tunnel-timeout left the tap dead, no navigate and no toast). What used to stay desktop-only was the panel pre-mount: a phone has no multi-panel keep-alive pool, so the new-session screen was rebuilt from scratch on every tap (the textarea + chat subtree assembled live inside the slide — the "fast-path activates instantly but the screen still rebuilds" gap). That's now closed by a mobile warm host: Omniscio keeps ONE new-session panel mounted heavy in the background (the single-panel mobile counterpart of the desktop P3 render-warm), so tapping New Session reveals a ready composer instead of constructing one. It's flash-free because an empty blank's box is one line tall (a browser can't measure a hidden textarea, so pre-mounting pre-pays the expensive subtree build, not the measurement). So the warm panel is ready even for the most common gesture — open a project and immediately tap New Session. As of 2026-06-27 the blank is reserved and inserted optimistically the instant you enter a project: the renderer picks the new session's id locally (safeRandomUUID) and shows the real blank synchronously (a transient "New Session" label until the backend row lands), then confirms it with the backend underneath using that same id — so the warm host has a blank to reveal on the first tap regardless of network. That closes the last quick-tap gap: previously the blank waited on a ~quarter-second SESSION_ENSURE_BLANK round-trip over the phone tunnel, so a fast tap inside that window fell to the "Starting session…" spinner (the "still not instant on a quick tap" report). It mints no extra sessions (the same one-per-project blank, just shown before the round-trip finishes) and is gated to spawnable Claude projects (there is no session cap to check). The backend creates the row with the same reserved id (no id-swap — the phantom-session class the postmortems forbid); if the create ever fails it rolls back cleanly (you're never stranded on a removed row), and if another device already minted a blank it adopts that one. Non-Claude / rare transient-failure cases still fall back to navigating immediately and revealing the real composer the instant the session activates (the SESSION_LAUNCHED push beats the worktree build), keeping you in that session even if the launch request later times out. Gated by the same "Pre-load new sessions" setting; kill switch AMC_DISABLE_MOBILE_BLANK_PREWARM=1. See keep-alive-pool-contract.md ("Mobile surface — the new-session PANEL is pre-mounted hidden" + the mobile miss path) and start-a-new-session.md.
  • Virtual projects and the Inbox. Ask Omniscio, the Gmail/SMS virtual hosts, and the Inbox can't host a spawnable session, so they're excluded from the pre-warm trigger.

For agents

How it works

Three coordinated pieces, plus a kill switch:

1. The background pre-warm (idempotent, debounced)

When you switch to a real (non-virtual, non-Inbox) project on desktop, the Dashboard fires a debounced (500 ms) request to ensure a blank exists for that project. This is fire-and-forget — the UI doesn't wait on it.

The request is the SESSION_ENSURE_BLANK IPC channel, handled in session-handlers.ts. Its contract is "make sure a blank session exists for this project, but do not activate anything." It runs inside the same withProjectLaunchLock chokepoint that the normal SESSION_LAUNCH no-prompt path uses, so calling it N times yields exactly one blank (idempotent): it calls findBlankSession(), returns the existing blank if there is one, and only mints a fresh one (via createSessionWithPrompt({ initialPrompt: undefined, background: true })) when none exists. The background: true flag is important — it keeps your current active session and project unchanged when the new-session push lands, so the pre-warm never yanks your focus.

Reviving a settled blank. A pre-warmed blank settles to ended when Omniscio is quit and restarted (or its idle CLI dies). When the pre-warm or the Ctrl+T fallback reuses such a blank, it revives it to ready via reviveBlankToReady — not updateSessionStatus(id, 'ready'), which silently no-ops a settled row (its terminal-state guard only lets an ended row exit via 'starting'). That no-op was a real bug: Ctrl+T landed the user on a gray, closed "Session Ready" panel ("Continue the conversation…", absent from Live Sessions) instead of a fresh one. A ready blank has no live process (the CLI cold-spawns on the first message), so reviving to ready is exactly equivalent to a fresh blank. See session-status-transition-contract.md.

Startup cleanup keeps only a usable blank. The once-per-startup sweep purgeStaleBlankSessions keeps only the newest ready blank per project and soft-deletes stale ended/error blanks (even a project's only one) — so closed "Session Ready" rows don't pile up across restarts; the pre-warm mints a fresh one when you next open the project. It only ever touches genuine user blanks (source = 'ui'/legacy NULL, not council), so a momentarily-empty programmatic session is never swept.

Pre-warm applies to every provider that runs the Claude binary — plain Claude, the Anthropic-compatible vendors (deepseek, kimi, glm, minimax, meta) and the proxy-backed engines. The gate is usesClaudeBinary(effectiveProvider). If the project's effective provider is one of the bespoke-manager engines (openclaw, codex, gemini, antigravity, opencode), the handler bails with a "not pre-warmable" result and Ctrl+T just uses the normal path — those have heavier readiness checks and their own session managers, so eagerly spawning one for a session you might never open isn't worth it.

The renderer side of this lives in ensureBlankSession() in session-store.ts. It debounces per-project (so rapid project switches collapse into one IPC), short-circuits when a blank already exists in renderer state, and — on success — seeds the conversation cache with an empty message array for the new blank. On mobile the Dashboard passes { eager: true }, which mints on the next tick instead of after the 500 ms debounce (so the mobile warm panel is ready before a quick New Session tap); it keeps the 500 ms debounce on desktop, and because the trailing timer already mints exactly one blank per distinct project entered, eager only moves that same mint earlier — never an extra one. As of 2026-06-27 the mobile eager path goes one step further for a spawnable Claude project (canOptimisticallyPrewarm has no session-cap check — the per-project cap was removed 2026-08-25): optimisticallyPrewarmBlank reserves the id (safeRandomUUID) and inserts the real ready blank row (a transient "New Session" label that the backend row replaces on confirm) + seeds its cache synchronously, then fires SESSION_ENSURE_BLANK with that reservedId (now accepted by the schema → createSessionWithPrompt → createSession, which uses reservedId ?? randomUUID()). When the mint resolves it confirms (same id, replace in place — no swap), reconciles (the backend reused a different existing blank — adopt it, move the active selection if the user already tapped), or rolls back (the rare mint failure — drop the row, restore the prior session). This makes the reveal network-independent — see the "Mobile optimistic pre-warm" section + invariants I16–I19 of the contract. That seed is what makes the blank immediately eligible for the fast path without waiting for a separate history fetch (a true blank has zero messages by definition, so an empty array is the correct, complete history).

Desktop committed "+ New" MINTS its own placeholder — it is never reconciled away (the anti-takeover guarantee). The desktop MISS path (tryOptimisticNewSession) uses the same optimistic-blank machinery, but the user has EXPLICITLY started a session and is already active on the reserved placeholder — so it passes dedicated: true on SESSION_ENSURE_BLANK, and the handler MINTS that exact id instead of reusing a pre-existing blank. That means the mint can only confirm (same id) — it can never reconcile the user off the placeholder they are typing into onto a shared blank that another flow then claims. Without it, under heavy multi-agent load a slow pre-warm dropped "+ New" to this path, the handler reused whatever blank existed, and the reconcile-swap made the user's new session "turn into" an auto-spawned agent session (the report that motivated the fix). The mobile SPECULATIVE pre-warm above OMITS dedicated, keeping its dedup reconcile (the user isn't committed to that id yet). See invariant I19 in the contract + background-spawn-blank-takeover-postmortem.md (recurrence 2).

2. The pre-rendered panel (keep-alive pool + render-warm)

A session being created in the background isn't enough — its panel also needs to be already mounted and "heavy" (fully rendered, textarea attached) so that revealing it is a pure CSS show, not a mount. Omniscio's keep-alive pool is the mechanism that keeps certain off-screen session panels mounted. The instant-new-session blank joins that pool through two carve-outs:

  • It's pooled even though its status is ready. Normally the keep-alive pool only holds the active session, needs_you sessions, and running/starting sessions; a plain ready session would be excluded and mount only on click. The pool adds a dedicated single-element priority class (called P3) for the active-project blank so that this one ready row stays mounted. See selectKeepAlivePool() in keep-alive-pool.ts.
  • Its panel is rendered heavy even before its history is cached. The background render-warm pump (the same machinery that pre-mounts needs_you panels so switching into them is instant) normally waits until a session's history is in the cache before rendering it heavy — rendering before the data lands would paint a wrong empty list. The blank is exempt from that wait, because a blank genuinely has no messages: "rendered before the cache loads" produces the correct empty list, not a wrong one. See selectWarmHeavyIds() in the same file.

Both carve-outs are locked by named invariants (I14 = the blank is an additional render-warm class exempt from the cache gate; I15 = the blank is pooled despite its ready status via the P3 class) in keep-alive-pool.test.ts. The full behavior contract is keep-alive-pool-contract.md.

3. The Ctrl+T fast path (synchronous, zero IPC)

The keyboard handler for the newSession action lives in useKeyboardShortcuts.ts. Before falling into the normal launchSession() flow, it calls tryInstantNewSession(projectId) (defined in session-store.ts):

  • On a hit, it synchronously activates the prepared blank — bumps it to the head of the session list, sets it as the active session/project, bumps the focus token so the textarea auto-focuses, and returns the session. The keyboard handler then focuses the composer textarea and stops. No IPC crosses the preload bridge — every mutation is a local Zustand setState. That zero-IPC property is what makes it feel instant, and it's pinned by a lint test.
  • On a miss (no eligible blank, draft present, kill switch set, openclaw project, etc.), it returns null and the handler falls through to the existing launchSession() path — identical toasts (binary-missing), identical error handling, identical behavior to before this feature existed. It's strictly additive: the slow path is never made slower or different.

The "is this blank eligible?" decision is selectExistingBlankForProject() in session-store.ts. It layers the slice-dependent gates that the pure pool predicate can't see:

  • the BACKEND has vetted this session id as a reusable blank — the gate everything else cannot replace. Only an id the main process answered with (SESSION_ENSURE_BLANK's mint or its reuse), or a reserved id the renderer just minted for this purpose, is ever adoptable, and that confirmation is dropped the moment the session gains a message, leaves the idle statuses, is sent into, or is archived. The renderer's own view is not proof: output for a session you are not looking at is deliberately withheld, so its cached history can look empty for a session that has turns, and a launch push deliberately reports ready for a row the database holds as starting. See blank-confirmation.ts;
  • the session passes isWarmableActiveProjectBlank (active project, ready/starting status, no scheduled response, not a recipe/pipeline row, a user-creatable source, and no deferred opening waiting to fire);
  • it has no typed draft (the typed-text-overwrite defense — drafts live in a Zustand slice, checked here);
  • its history is known to be cached (an undefined cache entry means "we don't know yet," which is treated as not-a-blank — never guess);
  • the cached history has zero non-system messages (a real blank).

The backend's own reuse query (findBlankSession) carries the matching rules for the slow path — a user-creatable source, an idle status (ready/ended/error), no deferred launch stash, no draft, no queued send, no playbook lane, and zero non-system messages. Status matters there because every session row is INSERTed starting and a launch with a prompt keeps that status, with no persisted message, for the whole worktree-setup phase — so "empty right now" is not the same as "a blank".

If multiple candidates qualify, it picks the most recently active one.

The kill switch (developer reference)

There are two ways to disable this feature:

  1. User setting: preloadSessionsEnabled in Settings → Performance → "Pre-load new sessions" (see "Turning it off" above). This is the user-facing control.
  2. Environment variable: AMC_DISABLE_INSTANT_NEW_SESSION=1 — force-disables regardless of the setting.

With either gate off, both halves shut off together: the Dashboard stops firing the background pre-warm, and tryInstantNewSession() always returns null so every Ctrl+T takes the legacy launchSession() path. There is no half-on state — both gates cover both halves, by design, so you can't end up pre-warming blanks that the fast path then ignores (or vice versa).

The env flag is read once and bridged from the main process to the renderer by the preload (window.__amcDisableInstantNewSession), exactly like the needs_you render-warm kill switch (AMC_DISABLE_NEEDS_YOU_WARM). The renderer reads both gates via isInstantNewSessionEnabled(settingValue?) in instant-new-session-flag.ts. The setting false disables immediately; a missing/undefined setting defaults to ON. For the env kill switch, only a literal boolean true disables — a missing bridge, an empty string, or a malformed value all keep the fast path on, so the feature fails safe toward "enabled" rather than silently disabling on a quirk.

Where it lives in code

What Where
Settings toggle (preloadSessionsEnabled) src/shared/types/settings/chat-ui-settings.ts (type + default), PerformanceSettings-definitions.tsx (UI)
Two-gate reader (isInstantNewSessionEnabled(settingValue?)) src/renderer/src/lib/instant-new-session-flag.ts
Preload bridge (window.__amcDisableInstantNewSession) src/preload/index.ts
Backend idempotent ensure-blank IPC handler IPC.SESSION_ENSURE_BLANK in src/main/ipc/session-handlers.ts — mints a numbered "Session N" blank (name: undefined → counter default)
Sidebar status dot SidebarSessionRow.tsx — solid getStatusBgColor(status); the pre-warm-blank pulse was removed (sidebar-status-dot-solid-contract.md)
Placeholder-name recognition (auto-titler + 60s recovery sweep) session-placeholder-name.ts — dep-light SESSION_NAME_PATTERN / PRELOAD_BLANK_SESSION_NAME; contract session-placeholder-name-contract.md
IPC channel + Zod schema src/shared/ipc-channels/session.ts, src/shared/ipc-schemas/session.ts
Renderer debounced pre-warm (ensureBlankSession) src/renderer/src/stores/session-store.ts
Mobile optimistic pre-warm (optimisticallyPrewarmBlank + confirm/reconcile/rollback; backend reservedId) session-store.ts + lifecycle.ts / session-create.ts / launch.ts / sessions.ts
Synchronous Ctrl+T fast path (tryInstantNewSession) src/renderer/src/stores/session-store.ts
Eligibility gate (selectExistingBlankForProject) src/renderer/src/stores/session-store.ts
Pool + render-warm carve-outs (isWarmableActiveProjectBlank, selectKeepAlivePool, selectWarmHeavyIds) src/renderer/src/features/dashboard/keep-alive-pool.ts
Dashboard wiring (pre-warm trigger, activeProjectBlankId) src/renderer/src/features/dashboard/Dashboard.tsx
Render-warm pump (renderWarmIds, synchronous warm + idle prefetch) src/renderer/src/features/dashboard/useRenderWarmPump.ts
Ctrl+T keyboard handler (case 'newSession') src/renderer/src/hooks/useKeyboardShortcuts.ts
Invariants I14 / I15 (pool + render-warm) tests/unit/features/dashboard/keep-alive-pool.test.ts
Zero-IPC + wiring lint tests/unit/lint/instant-new-session-wiring.test.ts
Kill-switch default-on behavior tests/unit/lib/instant-new-session-flag.test.ts
Full behavior contract (I1–I15, P0–P3, change checklist) .claude/memory/contracts/keep-alive-pool-contract.md

Related

  • start-a-new-session.md — the full new-session flow, the + button, launch targets, and what happens when there's no account or the folder is missing.
  • keyboard-shortcuts.md — Ctrl+T / N and every other binding; the new-session action is rebindable like the rest.
  • keep-alive sessions / lazy content load — the broader "make switching sessions instant" effort this builds on; the keep-alive pool and render-warm pump are shared machinery.

Last verified 2026-10-06