runner.md
May 26, 2026 · View on GitHub
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. --resume latestis also valid in the CLI but Untether always uses explicit session IDs.
Non-interactive runs
The runner invokes:
gemini -p --output-format stream-json --skip-trust --model <model> --prompt=<prompt>
Flags:
-p— non-interactive (print mode)--output-format stream-json— JSONL output--model <model>— optional, from config or/configoverride--prompt=<value>— prompt bound directly to flag (prevents injection when prompt starts with-)--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"
```sh
untether config set default_engine "gemini"
untether config set gemini.model "gemini-2.5-pro"
```
=== "toml"
```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 -p --output-format stream-json [--resume <session_id>] [--model <model>] [--approval-mode <mode>] --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