Skip to content

feat(agents): ship the Gemini CLI agent - #319

Merged
earakely-scale merged 3 commits into
mainfrom
feat/agents-gemini
Oct 23, 2026
Merged

earakely-scale merged 3 commits into
mainfrom
feat/agents-gemini

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

This ships Google's Gemini CLI as an agent in the agents bundle: src/agent_env/agents/agents/gemini/, alias gemini. 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.

  • Kept as it was:
    • The turn. One gemini -p … --output-format stream-json --approval-mode yolo per task, in /workspace. A context's first task starts a session, and later tasks resume it with -r. File parts are attached as @path.
    • The model proxy. Gemini has no header hook, so its calls go through a proxy in the agent that adds the attribution headers. The proxy forwards to LiteLLM's /gemini passthrough. For a provider-prefixed model such as vertex_ai/..., it translates the calls to /v1/chat/completions and back.
    • Snapshots. The transcript is saved byte for byte and loaded under a fresh session.
    • Other extensions. Skills in ~/.gemini/skills, MCP server names without _, and the attribution probe.
    • The trajectory format. gemini-stream-json/1.
    • The CLI version. Gemini CLI 0.38.2.
  • Changed:
    • Each run gets its own settings. The wrapper rewrote the shared ~/.gemini/settings.json when the role changed, and the shared GEMINI.md for 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):

      • the MCP servers, with the run's role header, else the role their registration names;
      • the system prompt, as a context file loaded beside any GEMINI.md in 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 init event. 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:

      • it takes a transcript only when exactly one was written since the run started that no one claims;
      • a transcript is claimed if another context is bound to it or any turn's init event named it;
      • ownership is checked when the fallback runs, not when the turn starts.

      A turn that times out also keeps the session its init event named; the wrapper dropped it.

    • [model.headers] reach the model. Both proxy paths now forward LITELLM_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 another WORKDIR still 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_agents MCP tools replace the wrapper's sidecar, shown to Gemini as peeragents. Tool names are unchanged.

    • Changelog. It's the Claude Code agent's tar_changelog.py under Gemini's names, run as an AfterTool hook. Its fixes carry over:

      • a tar run that fails, or an upload that fails, leaves its writes for the next increment;
      • the grant's limits are held across hook processes;
      • removed roots replay;
      • resume picks the last applied tool call's session.
    • 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-slim pinned by digest, Node 22.23.3 checked against its checksum, and the CLI from package-lock.json. It builds natively for amd64 and arm64.

    • install/v1. install.sh installs into a container or onto a vm sandbox'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:

    • per-run settings, proxy routes and session recovery;
    • the image and install.sh;
    • the changelog's hook wiring, and a real hook capture that a fresh agent rewinds and resumes.

    The full unit suite passes. tst/unit/agents/gemini also 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:

    • every model call carried the run's attribution through its proxy route;
    • the system prompt reached the model;
    • the MCP server saw the turn's role;
    • the second turn resumed the first's session, even though the CLI had printed its MCP notice on the init line.
  • 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_result with an output field, 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:

      • model 529 ×2, 500, reset mid-stream and a 40 s stall;
      • 401 on every call, and the endpoint dropped mid-turn;
      • the CLI killed mid-turn, and the container killed mid-turn;
      • an install, a reinstall mid-turn, and an install killed mid-install.

      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

RetriggerView in GreptileConfidence 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.

  • Gemini turns keep their session and run settings across tasks.
  • Gemini model calls use a proxy tied to the task’s run.
  • Gemini snapshots can replay tool changes and resume the saved conversation.
  • The Gemini agent ships as an image or installs into a task environment.

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]
Loading

Reviews (3) · Last reviewed commit: "fix(agents): merge gemini proxy header l..." · Reviewed by Greptile

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]>
@socket-security

socket-security Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​google/​gemini-cli@​0.38.21002510098100

View full report

@socket-security

socket-security Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

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.

Priority Alert  (click "▶" to expand/collapse) Action
Critical priority
Critical CVE: Gemini CLI: Remote Code Execution via workspace trust and tool allowlisting bypasses in npm @google/gemini-cli

CVE: GHSA-wpqr-6v78-jr5g Gemini CLI: Remote Code Execution via workspace trust and tool allowlisting bypasses (CRITICAL)

Affected versions: = 0.40.0-preview.2; < 0.39.1; >= 0.40.0-preview.2 < 0.40.0-preview.3

Patched version: 0.39.1

From: src/agent_env/agents/agents/gemini/package-lock.json → npm/@google/[email protected]

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@google/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn
Low priority
Potential vulnerability: npm tar-stream with risk level "medium"

Location: Package overview

From: src/agent_env/agents/agents/gemini/package-lock.json → npm/@google/[email protected] → npm/[email protected]

ℹ Read more on: This package | This alert | Navigating potential vulnerabilities

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: It is advisable to proceed with caution. Engage in a review of the package's security aspects and consider reaching out to the package maintainer for the latest information or patches.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn

View full report

@earakely-scale
earakely-scale marked this pull request as ready for review October 23, 2026 16:43
@earakely-scale
earakely-scale requested a review from a team as a code owner October 23, 2026 16:43
Comment thread src/agent_env/agents/agents/gemini/a2a_server.py Outdated
Comment thread src/agent_env/agents/agents/gemini/install.sh
Comment thread src/agent_env/agents/agents/gemini/a2a_server.py Outdated
Comment thread src/agent_env/agents/agents/gemini/gemini_bridge.py
Comment thread src/agent_env/agents/agents/gemini/a2a_server.py Outdated
…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]>
Comment thread src/agent_env/agents/agents/gemini/a2a_server.py Outdated
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]>
@earakely-scale
earakely-scale merged commit 8d8a333 into main Oct 23, 2026
15 checks passed
@earakely-scale
earakely-scale deleted the feat/agents-gemini branch October 23, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant