Gemini Runner
Below is the implementation spec for the Gemini CLI runner shipped in Untether.
Deprecated
The
geminiengine is deprecated and no longer supported (as of v0.36.0): it still loads, but gets no fixes, is excluded from testing, and may be removed in a future release (#722). Google ended Gemini CLI support for individual accounts (free, Google AI Pro and Ultra) on 18 June 2026 (announcement;IneligibleTierError); under Untether the subprocess hangs until the watchdog cancels the run. Enterprise / Google Cloud licences are unverified. Antigravity CLI ships as a separate engine in v0.36.1 (#558), not a rename.
Below is the implementation spec for the Gemini CLI runner shipped in Untether.
Scope
Goal
Provide the gemini engine backend so Untether can:
- Run Gemini non-interactively via the Gemini CLI (
gemini). - Stream progress by parsing
--output-format stream-json(newline-delimited JSON). Each line is a JSON object with atypefield. - Support resumable sessions via
--resume <session_id>.
Non-goals (v1)
- Plan mode interaction — Gemini supports
enter_plan_mode/exit_plan_modetools but these require interactive stdin.
UX and behavior
Engine selection
- Default: use
default_enginefrom config - Override:
/gemini <prompt>in Telegram
Resume UX (canonical line)
Untether appends a single backticked resume line at the end of the message:
`gemini --resume abc123def`
Notes:
- The resume token is the session id (short alphanumeric string, e.g.,
abc123def), captured from theinitevent’ssession_idfield. - Replies use the explicit session id;
/continuepasses--resume latest.
Non-interactive runs
The runner invokes:
gemini [--resume <session_id>|latest] --output-format stream-json [--model <model>] --approval-mode <mode> [--skip-trust] --prompt=<prompt>
Flags:
--prompt=<value>— non-interactive (headless) mode;--promptis the long form of-p, so no separate-pis passed--output-format stream-json— JSONL output--model <model>— optional, from config or/configoverride- The prompt is bound directly to
--prompt=(and space-prefixed if it starts with-), so it is never read as a flag --resume <session_id>— when resuming a session--approval-mode <mode>— defaults toyolo(full access) when no override is set; configurable via/configorpermission_moderun option--skip-trust— passed by default as of v0.35.3 (#471) so headless runs work outside~/.gemini/trustedFolders.json. Gemini CLI rejects runs from any directory not in the trust list — even with--approval-mode yolo— and there is no interactive prompt path in headless usage, so projects outside the trust list previously failed silently before any agent output. Set[gemini] skip_trust = falseinuntether.tomlto opt out (security-conscious operators who want Gemini’s project-local extension/MCP trust gate enforced).
Config additions
untether config
untether config set default_engine "gemini"
untether config set gemini.model "gemini-2.5-pro"
toml
# ~/.untether/untether.toml
default_engine = "gemini"
[gemini]
model = "gemini-2.5-pro" # optional; passed as --model
skip_trust = true # optional; default true — opt out to enforce trustedFolders.json
Notes:
- Gemini auto-routes between Pro (planning) and Flash (implementation) when no model is specified.
- Authentication is handled by the Gemini CLI itself (Google AI Studio or Vertex AI).
Code changes (by file)
src/untether/runners/gemini.py
Exposes BACKEND = EngineBackend(id="gemini", build_runner=build_runner, install_cmd="npm install -g @google/gemini-cli").
Runner invocation
gemini [--resume <session_id>|latest] --output-format stream-json [--model <model>] --approval-mode <mode> [--skip-trust] --prompt=<prompt>
Event translation
Gemini JSONL output uses a type discriminator field. The runner translates:
init->StartedEvent(captures session_id and model)tool_use->ActionEvent(phase: started)tool_result->ActionEvent(phase: completed)message(role=assistant) -> text accumulation for final answerresult->CompletedEvent(with usage fromstats)error->CompletedEvent(ok=false)
Tool name normalisation
Gemini uses snake_case tool names. The runner normalises them via _TOOL_NAME_MAP:
| Gemini tool | Normalised |
|---|---|
read_file | read |
edit_file | edit |
write_file | write |
web_search | websearch |
web_fetch | webfetch |
list_dir | ls |
find_files | glob |
search_files | grep |
Installation and auth
Install the CLI globally:
npm install -g @google/gemini-cli
Run gemini once interactively to authenticate with Google AI Studio or Vertex AI.
Known pitfalls
- Gemini has no
--stream-json-inputmode, so interactive features (approve/deny, plan mode toggle) are not possible in headless mode. --approval-modecontrols tool access in headless mode. Untether defaults toyolo(full access — all tools auto-approved) when no override is set, since headless mode has no interactive approval path. Without this default, Gemini’s CLI read-only mode disables write tools (run_shell_command,write_file,edit_file), causing most tasks to stall as the agent cascades through sub-agents. Users can restrict via/config→ Approval mode: edit files (auto_edit, blocks shell but allows file operations) or read-only (denies most tool calls).- Tool names are snake_case (e.g.,
read_file) unlike Claude Code’s PascalCase — the runner normalises these.
See also
- Error Reference — actionable hints for common engine errors