Repository navigation
feat(agents): ship the Gemini CLI agent - #319
Conversation
Adds agents/gemini: Google's Gemini CLI behind the protocol's A2A agent framework, with the alias `gemini`, a smoke task, and a published image, agentenv-gemini. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
…I cwd - A turn cancelled while the CLI is still starting waits for it, then kills it and its tools; asyncio's own cleanup killed the CLI alone and hung on a tool's pipes. - A first turn with no init event takes a transcript only when exactly one unclaimed one was written since it started (1 s slack for coarse mtimes), with ownership read when it recovers: sessions bound to other contexts and every session a turn's init named. - A timed-out turn keeps the session its init named. - Both proxy paths forward [model.headers] (LITELLM_EXTRA_HEADERS), with the run's attribution on top. - The CLI runs in GEMINI_CWD whatever the server's cwd, so an install into a container with another WORKDIR keeps transcripts where snapshot looks. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The request's headers, [model.headers] and the run's attribution are merged by lowercased name, later layers winning, so an `authorization` or `X-LiteLLM-Tags` from the endpoint's config replaces the default or loses to the run's value instead of going out beside it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
What and why
This ships Google's Gemini CLI as an agent in the
agentsbundle:src/agent_env/agents/agents/gemini/, aliasgemini. It's the wrapper we've been running internally, ported so the deployment that runs it can switch to this folder without a regression. A release also publishes its image,ghcr.io/scaleapi/agentenv-gemini:src-<hex>, built again only when the folder changes.gemini -p … --output-format stream-json --approval-mode yoloper task, in/workspace. A context's first task starts a session, and later tasks resume it with-r. File parts are attached as@path./geminipassthrough. For a provider-prefixed model such asvertex_ai/..., it translates the calls to/v1/chat/completionsand back.~/.gemini/skills, MCP server names without_, and the attribution probe.gemini-stream-json/1.Each run gets its own settings. The wrapper rewrote the shared
~/.gemini/settings.jsonwhen the role changed, and the sharedGEMINI.mdfor each system prompt. So two turns starting together could take each other's role or system prompt. Now each run gets its own system settings file (GEMINI_CLI_SYSTEM_SETTINGS_PATH):GEMINI.mdin the workspace. Before, the wrapper overwrote that file.The proxy attributes each run's calls. Its URL now names the run, so it uses that run's own model, project and task rather than the agent's current config.
Session capture. When an MCP server fails to connect, the CLI prints a notice on the same line as its
initevent. The wrapper then lost the session id and fell back to the newest transcript on disk, which a concurrent context could own. The event is now recovered. The fallback is now strict:initevent named it;A turn that times out also keeps the session its
initevent named; the wrapper dropped it.[model.headers]reach the model. Both proxy paths now forwardLITELLM_EXTRA_HEADERS, which the wrapper dropped. Header names are merged case-insensitively, with the run's attribution last.The CLI always runs in the workspace. It's spawned in
GEMINI_CWD, whatever directory the server started in. So an install into a container with anotherWORKDIRstill keeps transcripts where snapshots look for them.One home for the CLI's state. The wrapper read transcripts, skills and hook settings from
$HOME/.gemini, while the CLI keeps them in$GEMINI_CLI_HOME/.gemini. Under Modal, which runs the image as root, those differ. So on Modal, snapshot save failed (500), skills weren't found, and the changelog hook never ran. The agent now reads them where the CLI keeps them.Peer agents. The SDK's
peer_agentsMCP tools replace the wrapper's sidecar, shown to Gemini aspeeragents. Tool names are unchanged.Changelog. It's the Claude Code agent's
tar_changelog.pyunder Gemini's names, run as an AfterTool hook. Its fixes carry over:Shutdown and cancel. The CLIs of running turns are killed as the agent stops, and uvicorn's graceful shutdown is bounded to 5 s. A turn cancelled while its CLI is still starting waits for it to start, then kills it and its tools, as the Claude Code agent does.
TLS checks stay on. No
NODE_TLS_REJECT_UNAUTHORIZED=0, and the proxy's upstream hop verifies its certificate. The wrapper fell back to no verification when it wasn't configured.Image.
python:3.11-slimpinned by digest, Node 22.23.3 checked against its checksum, and the CLI frompackage-lock.json. It builds natively for amd64 and arm64.install/v1.install.shinstalls into a container or onto avmsandbox's host, as the Claude Code and Codex agents' do. It brings its own Node, CLI and Python, and leaves the target's alone.How it was tested
Unit tests.
tst/unit/agents/gemini/ports the wrapper's suite, and adds tests for:install.sh;The full unit suite passes.
tst/unit/agents/geminialso passes on Linux, Python 3.11, with GNU tar and setsid. Under 2 CPUs, its cancel and session tests passed 15 runs in a row (315 tests).The image, locally. The real image ran against a fake LiteLLM and a fake MCP server. Over two turns:
initline.Live, on a dev deployment, against the agent this replaces. This ran at d4a68eb. The review fixes since then are covered by unit tests only. The image was built natively on an amd64 Linux VM with the protocol pinned to 0.1.366, and registered as a test id. It was compared with the current internal image under another test id.
A2A validator: 57 checks each. The new agent passes them all. The current agent fails snapshot save, load and recall, the changelog round-trip, and the skill prompt, both concurrently and when run alone. That's the home mismatch above: dev deploys agents on Modal, and those checks passed there when its image was last validated, which wasn't on Modal. Everything else matches.
Trajectory: one prompt to each agent. The task and context trajectories have the same event shapes; the new run also made one
tool_resultwith anoutputfield, from one more tool call, on the same CLI.Chaos battery: a fault proxy sat in front of the model gateway, and each agent met the same faults:
All 12 give the same outcome on both agents. The next prompt passed after every one, no CLI was left running, and no key appeared in any output.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; the remaining numbered finding is fixed.Summary
Adds the Gemini CLI agent to the shipped bundle, with model routing, sessions, snapshots, changelog capture, and installation support.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Task[A2A task] --> Agent[Gemini agent] Agent --> CLI[Gemini CLI] CLI --> Proxy[Run-specific proxy] Proxy --> Headers[Merge defaults, configured headers, then attribution] Headers --> Native[Gemini passthrough] Headers --> Bridge[Chat completions bridge]Reviews (3) · Last reviewed commit: "fix(agents): merge gemini proxy header l..." · Reviewed by Greptile