---
title: Gemini Provider
---

# Gemini Provider

## What it is

Omniscio supports spawning Claude-Code-style sessions backed by Google's Gemini CLI as one of several alternative providers, alongside `claude` (the default), `codex`, and the other registry providers (Antigravity, DeepSeek, Kimi, GLM, MiniMax, Cursor, Hermes, Pi, Meta, Grok, OpenCode, OpenClaw).

### What the user sees

A Gemini session looks identical in the sidebar and main pane to a Claude session — same status dots, same streaming bubbles, same Ctrl+Enter to send. The provider is invisible in normal chat. The only user-visible differences:

- The session header shows the four-point Gemini bloom logo (Google's blue→purple→pink brand gradient), with no text label, instead of nothing. Every started session shows its engine mark next to the title — Claude included, in its brand orange (only logo-less internal engines like OpenClaw / orchestrator show nothing). The per-launch provider dropdown on a fresh session also shows the same bloom mark next to each row, in the same brand gradient.
- Tool-use auto-approval: every tool call runs without prompting — Omniscio answers gemini-cli's ACP permission requests itself (allow, except a tool that targets a denylisted dangerous path, which is denied).
- It works in a **copy** of your project, not the real one. Because it auto-approves every tool call, a Gemini session runs in its own git worktree (a separate copy of the repo on its own branch) **by default, whatever your worktree-isolation setting says** — the same barrier Grok and Antigravity get, and for the same reason: with nothing asking you before a file is written, that copy is the only thing standing between the session and your real checkout. You review and merge its work like any other session's. See [session-isolation.md](session-isolation.md).

## Where to find it

### How to enable

> **Master toggle required first.** Gemini and Codex are alternative providers — by default Omniscio ships as a Claude-only product, and the entire Gemini setup section below is hidden in Settings → Accounts. Flip **Settings → Accounts → Show alternative AI providers** to ON (off by default) and the Gemini panel appears. With the master off, the per-project "Default provider" radio collapses to a single Claude row and the session header's provider switcher (ChangeProviderButton) is hidden — Gemini effectively does not exist in the UI even if every gate below is configured. Your saved API key, "Allow Gemini sessions" toggle, and per-project defaults are preserved across master-toggle flips.

You need these in place:

1. **Gemini CLI binary** — must be present on PATH or installable from the same Settings panel.
2. **Settings → Account → Allow Gemini sessions** toggle — off by default; a small opt-in step.
3. **Auth — sign in to Gemini, OR (optionally) paste a key.** An API key is no longer required. If you've signed in to the Gemini CLI with Google (running `gemini` once and completing the browser sign-in), Omniscio spawns keyless and that login is used. You don't have to find a terminal yourself: once the Gemini CLI is installed, **Settings → Accounts → Gemini** shows a one-click **Sign in with Google** button that opens a real, visible terminal window and runs `gemini` in it (its first interactive run is the Google sign-in) — complete it there, then return (re-open Settings to confirm). Alternatively, paste a Gemini API key in **Settings → Account → Gemini → API key** (encrypted via `safeStorage`); a pasted key is OPTIONAL and, when present, is used instead of the login. Omniscio strips any stray `GEMINI_API_KEY` from your shell so it can't silently bill an account. With neither, a Gemini turn fails with a "sign in with `gemini`, or add a key" message. See [cli-login-auth-contract.md](../../.claude/memory/contracts/cli-login-auth-contract.md).

Once the binary + toggle are green (and you're signed in or have a key), you can launch a Gemini session in three ways:

- **Per-launch override** — on a fresh (zero-message) session, the **ChangeProviderButton** in the new session's main panel (the launch-config pickers on a fresh session) lets you pick a one-off provider before sending the first message. Non-ready providers appear `aria-disabled` with a "Set up…" / "Install…" suffix and deep-link to Settings.
- **Per-project default** — Edit Project dialog (three-dot menu → Edit) has a "Default provider" section with three radios. Pick Gemini and the project's sidebar "+ New Session" button spawns Gemini automatically.
- **Programmatically** — anything that creates a session with `provider: 'gemini'` (recipes, agent-driven sessions, the API). Same readiness gates apply on the backend.

## How it behaves

### Choosing the model

A fresh Gemini session lets you pick the model in the launch-config pickers (next to the provider chooser): **Gemini 3.8 Flash** (`gemini-3.8-flash`, newest GA Flash — the default), **Gemini 3.7 Flash** (`gemini-3.7-flash`) and **Gemini 3.6 Flash** (`gemini-3.6-flash`, earlier Flash generations), **Gemini 3.5 Flash** (`gemini-3.5-flash`, an older Flash whose output rate never took the promo cut), **Gemini 3.5 Flash-Lite** (`gemini-3.5-flash-lite`, cheapest — fast, lightweight tasks), **Gemini 3.1 Pro** (`gemini-3.1-pro-preview`, preview), or **Gemini 2.5 Flash** (`gemini-2.5-flash`, fast/affordable fallback). The pick is staged locally and applied when you send your first message (passed to the `gemini` CLI as `--model`); **Use default** omits the flag so Gemini picks its own. Gemini has no separate "reasoning effort" knob, and the list is Gemini's own — never your Claude default model. See [start-a-new-session.md](start-a-new-session.md).

### What it costs

Gemini sessions are metered. gemini-cli reports the turn's token usage over ACP, Omniscio prices those tokens at the picked model's published Google rate, and the session records both — so a Gemini session appears in your spend totals, the per-session cost chip and the engine breakdown alongside every other engine, marked **"≈"** because the figure is an estimate rather than a vendor bill. Rates come from [ai.google.dev/gemini-api/docs/pricing](https://ai.google.dev/gemini-api/docs/pricing): the 3.8 / 3.7 / 3.6 Flash generation is on an introductory $0.75 / $3.75 per million tokens (in / out) through 2026-12-31, rising to $1.50 / $7.50 from 2027-01-01.

Two things to know if you are using a **free** Google account rather than a billed API key: the recorded figure is Google's list price for the work performed, not money leaving your account, and a turn your Gemini CLI cancelled or failed reports no usage at all — that is recorded as **0**, which there is a true zero rather than "not reported". (Until 2026-09-25 Gemini recorded every turn as $0 because Omniscio read only the stop reason out of that same ACP response and discarded the usage; the history before that date under-reports Gemini spend.)

### Per-project default

Each project remembers a default provider in the `projectDefaultProviders` setting (a `Record<projectId, ProviderId>`). Empty by default — every project falls back to Claude. Change it from the project's three-dot menu → Edit → Default provider radios; the change persists immediately.

When you set a project's default to Gemini but Gemini isn't ready (toggle off or binary missing), an amber **"!" badge** appears on that project's sidebar row. Hover the badge for a tooltip explaining the gap; click it to deep-link straight to the right Settings panel. The badge re-checks on every render, so fixing the gap clears it without a refresh. Claude defaults never show the badge — Claude is always considered ready.

### Error states and fixes

Two gap codes — same shape as the codex provider (the API key is optional, so it is NOT a readiness gate):

| Gap              | What it means                              | Click-to-fix lands you at…                        |
| ---------------- | ------------------------------------------ | ------------------------------------------------- |
| `toggle-off`     | "Allow Gemini sessions" is OFF in Settings | Settings → Account → Allow Gemini sessions toggle |
| `binary-missing` | `gemini` CLI not on PATH                   | Settings → Account → Gemini CLI binary panel      |

Auth (a Gemini Google sign-in or a pasted key) is checked at the turn, not as a readiness gate; with neither, the turn fails with a humanized "sign in with `gemini`, or add a key" message.

**Detection survives a slow cold-start.** `binary-missing` confirms the CLI via a `--version` probe (5 s budget); under heavy app load that probe can be killed before it answers, so detection falls back to the CLI's **presence on disk** at a known install dir (npm-global / well-known paths). An installed-but-slow Gemini CLI therefore never false-flags as missing, and a successful detection is cached so the check stops re-probing. See [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md) (a-cold-start-probe-does-not-report-a-present-binary-missing).

## For agents

### How it works under the hood

Omniscio drives Gemini through the **ACP harness** (`gemini --acp` — Agent Client Protocol, JSON-RPC 2.0 over stdio): a long-lived child per session with real turn boundaries (`session/prompt` resolves a `stopReason`), a clean mid-turn `session/cancel`, and proper resume. Spawning is **lazy by default** (like Codex and Pi): a fresh session sits in `ready` with no child, and the `gemini --acp` process starts on the first message, so creating/switching to a Gemini session is instant. Kill switch `AMC_DISABLE_GEMINI_ACP_LAZY_SPAWN=1` reverts to eager spawn at launch (mirrors `AMC_DISABLE_PI_LAZY_SPAWN`). It extends the shared persistent-external base (`BaseExternalSessionManager` — resurrect guard, stall watchdog, first-response timer, force-recovery, resume replay) and owns a runaway cap. A long turn is **never cut off by a fixed per-request deadline** — its liveness is governed by that activity-based stall watchdog (which intervenes only when the engine genuinely goes silent), so a slow-but-streaming turn runs to completion; the underlying ACP transport's short per-request timeout applies to control requests (handshake / new-session), not the turn itself. This replaced an earlier per-turn one-shot stdout-scrape that once looped into a 185 KB / 69-min wall with no turn boundary and no cancel.

Working directory: each `session/new` is bound to the session's project folder, **resolved to a real absolute path first** via `resolveProjectWorkDir` (the same step the Claude spawn path does) — so the Claude virtual project's `__claude__` sentinel becomes `~/Claude`, and managed scratch projects are created on demand. Handing gemini-cli the raw sentinel made it resolve against its own process cwd, hit a non-existent dir, and fail `session/new` with an opaque `-32603 "Internal error"` (surfaced as "Failed to start Gemini session: Internal error") → [gemini-acp-cwd-resolution-contract.md](../../.claude/memory/contracts/gemini-acp-cwd-resolution-contract.md).

Routing: a session with `provider: 'gemini'` is dispatched by `getAltSessionManager('gemini')` → `geminiAcpSessionManager`. The session row stores `provider: 'gemini'`; the renderer's `ProviderBadge` reads that column and renders the Gemini icon.

Telemetry: every successful non-Claude spawn fires the `spawn_non_claude_session` feature event, recording `{ provider }` only — no session ID, project ID, or prompt content.

### History-compression corruption (prevented + auto-recovered)

gemini-cli auto-compresses its own chat history once it reaches **50%** of the model's context window, and its compressor can leave the history beginning with an orphaned `functionResponse` (a tool result with no preceding tool call). Gemini's API then rejects **every** subsequent request with `400 INVALID_ARGUMENT — "function response turn comes immediately after a function call turn"`; gemini-cli retries it forever, writing the real error **only to stderr**. The symptom a user saw: a long-running Gemini session went silent and, ~15 minutes later, died with a generic "stopped responding." This is a known, still-open upstream P0 (google-gemini/gemini-cli #4105 / #3709) across versions, so upgrading is not a reliable fix.

Omniscio handles this in three layers (see [gemini-acp-compression-guard-contract.md](../../.claude/memory/contracts/gemini-acp-compression-guard-contract.md)):

1. **Prevention — the compressor never fires.** Each `gemini --acp` child is pointed (via `GEMINI_CLI_SYSTEM_SETTINGS_PATH`) at an **Omniscio-owned** gemini system-settings file carrying `model.compressionThreshold = 0.95`. gemini-cli merges the _system_ settings layer last, so it wins over schema-default/user/workspace — and the default gemini-3.8-flash still has a large context window, so at 95% the buggy compressor effectively never runs. This **never touches your `~/.gemini/settings.json`**, has no write race, and reverts cleanly. The default model is already gemini-3.6-flash (large context), so no model is pinned.
2. **Recovery — hear the error, restart clean.** If corruption still occurs (e.g. an enormous session), the ACP client buffers stderr and detects the _specific_ fatal 400 (a narrow pattern that ignores benign router-fallback noise), fires an `onApiError` signal, and the session manager tears down the wedged child and re-drives your in-flight prompt on a **fresh** child whose history is replayed as clean text (no function-call structure → no orphan → accepted). Bounded per turn; on exhaustion it shows the **real** reason, not the generic message.
3. **Watchdog flap fix.** The shared stall watchdog's "machine busy, wait longer" grace no longer collapses on a single noisy load sample — it requires several consecutive idle samples before force-recovering, so a momentary load blip can't kill a still-working turn early.

### Context-window overflow (auto-recovered)

In ACP mode gemini-cli keeps every tool's output in its own working history **untrimmed** — its 40,000-character shell-output truncation, its tool hooks and its history compression all run only in its interactive mode. So one command that prints megabytes (a large JSON dump, a log file) can push that private history past the model's window (1,048,576 tokens) even though the conversation Omniscio stores is small, and Gemini rejects the turn with `The input token count exceeds the maximum number of tokens allowed 1048576.`

Omniscio treats that as the **child** overflowing, not the conversation: it stops the overflowed `gemini --acp` child, starts a fresh one that replays the stored conversation as compact text, and re-sends your message with a one-time note asking the model to keep command output small. This shares the per-turn budget (two restarts) with the recoveries above. If the turn still overflows, you see one plain message saying Gemini ran out of room on that turn and that your conversation is saved; the child has already been replaced, so your next message runs fresh. Before this, Omniscio said "the conversation got too long — start a fresh session" and kept the overflowed child, so every later message failed in seconds until the app restarted. Locked by `overflow-restarts-the-child` and `overflow-give-up-keeps-the-chat` in [gemini-acp-compression-guard-contract.md](../../.claude/memory/contracts/gemini-acp-compression-guard-contract.md).

### Image attachments

A Gemini session is **multimodal** — attach an image in the composer (paste, drag, or the attach button) and Gemini sees it, just like Claude and Codex. The image rides the turn as an inline ACP **image content block** (`{ type:'image', data, mimeType }`, appended after your text on the `session/prompt` request); Gemini's model reads it alongside the prompt. An image-only message (no text) still works — Omniscio seeds a default "analyze the image" instruction so the model has something to act on. The attachment chip shows on your message bubble and survives a reload.

Only actual **image** files are delivered (`image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/heic`, …). A non-image attachment (a PDF or `.docx`) on a Gemini session is NOT sent to the model — delivering it as an image block would make Gemini reject the whole turn — so it is skipped silently; document support for Gemini is not wired yet. **Before this change, Gemini sessions silently DROPPED every attachment** (image or not), so the model never saw them — the chip even vanished on reload.

The image is sent as raw base64 over the same stdio pipe as the prompt; normal screenshots are well within the ~32 MB attachment cap, and a pathologically large image degrades to a graceful turn error rather than a hang. Verified live (2026-06-18): a real `gemini --acp` turn carrying a generated test image returned a model description matching the image's actual content. Locked by images-ride-as-content-blocks in [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md).

### Tool activity → single-line markers (and why)

Each ACP `tool_call` becomes ONE `▸ <Label>: <detail>` marker line — a friendly label from the ACP tool `kind` (`execute` → `Shell`, `read` → `Read`, …) plus a single-lined, clipped detail from the call's `title`. Omniscio folds every `▸` line into the collapsed "N actions" activity pill (and treats the text after the last marker as the real reply), and that fold REQUIRES one marker per physical line. Gemini's shell `tool_call` title is the full command — and Gemini `cd`s into the worktree first, so the raw title was almost always multi-line; emitted verbatim, only the first line got the `▸`, and every continuation line (the actual `git grep …`, an 18-line `echo … >> file` block, …) was misclassified as prose and rendered as a wall. The translator now single-lines + clips + labels (mirroring Claude's `singleLine` and Codex's `clipDetail`). Locked by tool-calls-become-single-line-markers in [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md); the one-line invariant lives in `singleLineToolLabel` ([src/shared/agent-content-markers.ts](../../src/shared/agent-content-markers.ts)).

Tools gemini-cli gates behind a confirmation (most shell commands) are announced only inside its permission request, never as a `tool_call`. Omniscio surfaces the allowed request as the same marker, so those tools appear in the activity pill — and count as a running tool for the stall watchdog and the runaway cap — like every other tool. Before this (a-permission-gated-tool-is-announced-like-any-other), 12 of 73 tool calls in one live session were invisible until they finished, and a 9.5-minute shell command's silent wall-clock was charged to the model and tripped the runaway cap the moment it resumed.

### Markdown in the prompt (a historical quirk, resolved under ACP)

In its **old one-shot `-p --output-format stream-json` mode**, Gemini CLI 0.41.2 had a quirk: a prompt containing Markdown — a `---` thematic break, backticks, etc. — made it abandon `stream-json` and print the answer as PLAIN TEXT instead of NDJSON. Omniscio's stream parser only understood NDJSON, so the (perfectly good) answer was discarded and the user saw the misleading **"Gemini CLI exited (code=0) without emitting a result."**

That quirk no longer applies. Omniscio now drives Gemini through the **`gemini --acp` harness** (structured JSON-RPC, `agent_message_chunk` frames) — a separate boolean mode with no `--output-format` to abandon. A live probe confirmed Gemini's ACP path handles a Markdown-heavy prompt cleanly and still returns a structured reply. So both old defenses are retired:

1. **Gemini now RECEIVES the QuestionWidget format hint.** The Markdown-heavy hint Omniscio appends to the first message of every session (to teach clickable-question formatting) used to be SKIPPED for Gemini via an `acceptsMarkdownPrompt: false` capability flag. With the quirk gone under ACP, that flag was removed — Gemini gets the hint like Claude/Codex, so a Gemini session **can** format clickable QuestionWidgets. (The capability still exists as a dormant opt-out for any future Markdown-fragile CLI; no provider sets it today.)
2. **(Legacy) plain-text salvage — gone.** The earlier one-shot stdout-scrape salvaged a clean-exit / no-NDJSON / non-empty stdout as the answer. The ACP harness uses structured JSON-RPC, not stdout scraping, so that failure mode no longer exists.

The hint-skip is locked by the-markdown-prompt-hint-is-received in [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md).

### Files

- [src/main/services/engines/gemini-acp-session-manager.ts](../../src/main/services/engines/gemini-acp-session-manager.ts) — ACP session manager (extends the persistent-external base)
- [src/main/process/gemini-acp-client.ts](../../src/main/process/gemini-acp-client.ts) — `gemini --acp` transport (initialize → session/new → prompt/cancel/stop)
- [src/main/process/acp-client.ts](../../src/main/process/acp-client.ts) — provider-agnostic JSON-RPC2 transport
- [src/main/process/acp-update-translator.ts](../../src/main/process/acp-update-translator.ts) — `session/update` → text/thought/tool (tool → a single-line `▸ <Label>: <detail>` marker)
- [src/main/services/engines/gemini-binary-resolver.ts](../../src/main/services/engines/gemini-binary-resolver.ts) — binary discovery
- [src/main/services/engines/gemini-credential-store.ts](../../src/main/services/engines/gemini-credential-store.ts) — API-key storage
- [src/main/services/provider-setup/provider-readiness.ts](../../src/main/services/provider-setup/provider-readiness.ts) — single source of truth for the 3-gate readiness check (used by ChangeProviderButton, Edit Project radios, project-row cue, spawn guard)
- [src/renderer/src/features/sessions/ProviderSplitButton.tsx](../../src/renderer/src/features/sessions/ProviderSplitButton.tsx) — sidebar "+ New Session" button (spawns the project default; no provider picker)
- [src/renderer/src/features/sessions/ChangeProviderButton.tsx](../../src/renderer/src/features/sessions/ChangeProviderButton.tsx) — per-launch provider override on a fresh session header
- [src/renderer/src/features/sessions/ProviderBadge.tsx](../../src/renderer/src/features/sessions/ProviderBadge.tsx) — Gemini icon in session header

### Phase 2 (deferred)

`--approval-mode plan` integration: instead of `--yolo`, spawn with plan-mode first, surface the plan as an inbox approval, then re-spawn with `--yolo` on user approve. This restores user-in-the-loop tool gating at the cost of two spawns per turn.

## Related

- [codex-provider.md](codex-provider.md) — same launcher / readiness / per-project-default model, different binary + credential
- [openclaw-provider.md](openclaw-provider.md) — separate "alternative provider" model (remote WebSocket gateway, not a local CLI)
