Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions agentex/docs/docs/development_guides/gemini_cli_agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Gemini CLI Agents

A Gemini CLI agent wraps the `gemini` CLI as a local subprocess and streams its output through the [unified harness](streaming_patterns.md#unified-harness-surface-framework-agents). You spawn the CLI in headless mode with `--output-format stream-json`, pass the prompt with `-p`, and hand its newline-delimited JSON stream to a `GeminiCliTurn`. The `UnifiedEmitter` then delivers the canonical `StreamTaskMessage*` events and derives tracing spans automatically, exactly like the Claude Code and Codex harnesses.

Scaffold one with `agentex init` by picking the **Gemini CLI** framework option (available for Sync, Async-base, and Temporal).

## Prerequisites

- The `gemini` CLI installed and on your `PATH` (`npm install -g @google/gemini-cli`).
- A `GEMINI_API_KEY` in the environment (the CLI's other login methods also work in a shell where you have signed in). Optionally `GEMINI_MODEL` to pin a model; the CLI defaults to `auto`.

## How it works

The template spawns the CLI in streaming-JSON mode with the prompt on the command line and stdin closed:

```python

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Prompt Exposed in Arguments

Passing the potentially user-supplied prompt through -p puts its full contents in the subprocess argument list, where process listings or process telemetry may capture it. This is a non-blocking documentation concern: please warn readers not to include secrets and recommend suitable isolation or redaction for shared environments.

How this was verified: The documented command directly inserts prompt into the argument list passed to create_subprocess_exec.

Prompt To Fix With AI
This is a comment left during a code review.
Path: agentex/docs/docs/development_guides/gemini_cli_agents.md
Line: 16

Comment:
**Prompt Exposed in Arguments**

Passing the potentially user-supplied prompt through `-p` puts its full contents in the subprocess argument list, where process listings or process telemetry may capture it. This is a non-blocking documentation concern: please warn readers not to include secrets and recommend suitable isolation or redaction for shared environments.

**How this was verified:** The documented command directly inserts `prompt` into the argument list passed to `create_subprocess_exec`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Claude Code Fix in Codex

cmd = ["gemini", "-p", prompt, "--output-format", "stream-json"]
if model := os.environ.get("GEMINI_MODEL"):
cmd.extend(["-m", model])
proc = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.DEVNULL,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
```

Stdin is closed on purpose: in non-interactive mode the CLI reads stdin to EOF and appends it to the prompt, so an open pipe would make it wait forever.

`GeminiCliTurn(lines)` wraps the iterator of stdout lines (raw JSON strings or pre-parsed dicts). Under the hood it runs the `convert_gemini_cli_to_agentex_events` tap, which maps the CLI's events onto the canonical stream:

| Gemini CLI event | Canonical events |
|---|---|
| `init` | none (session id and model are captured on the turn) |
| `message` (`role: user`) | none (the CLI echoes the prompt) |
| `message` (`role: assistant`, `delta: true`) | `Start(TextContent)` once, then a `Delta(TextDelta)` per chunk; the slot is closed on the next tool event or the `result` |
| `tool_use` | `Start(ToolRequestContent)` + `Done`, keyed by `tool_id` |
| `tool_result` | `Full(ToolResponseContent)` with the output (or the error message and `is_error`) |
| `error` | logged, nothing emitted |
| `result` | closes any open text slot; its `stats` become the turn's `TurnUsage` |

The turn exposes `session_id` and `model` from the `init` event, and `usage()` maps `stats` (`input_tokens`, `output_tokens`, `cached`, `total_tokens`, `duration_ms`, `tool_calls`) onto `TurnUsage`. The CLI does not report cost, so `cost_usd` stays `None`.

## Sync delivery (HTTP yield)

```python
import agentex.lib.adk as adk
from agentex.lib.adk import UnifiedEmitter, GeminiCliTurn

@acp.on_message_send
async def handle_message_send(params: SendMessageParams):
task_id = params.task.id
async with adk.tracing.span(
trace_id=task_id, task_id=task_id, name="message",
input={"message": prompt},
data={"__span_type__": "AGENT_WORKFLOW"},
) as turn_span:
emitter = UnifiedEmitter(
task_id=task_id, trace_id=task_id,
parent_span_id=turn_span.id if turn_span else None,
)
turn = GeminiCliTurn(_spawn_gemini(prompt)) # iterator of CLI stdout lines
async for event in emitter.yield_turn(turn):
yield event
```

## Async and Temporal delivery

For Async-base and Temporal agents the body is the same, except you call `auto_send_turn` (which pushes to Redis and returns a `TurnResult`) instead of `yield_turn`. Under Temporal, run the subprocess inside an activity and pass `created_at=workflow.now()`:

```python
result = await emitter.auto_send_turn(turn, created_at=workflow.now())
# result.final_text, result.usage
```

Always tear the subprocess down in a `finally` block so a cancelled or failed turn does not leak a `gemini` process.

## Multi-turn conversations

The Gemini CLI's `--resume` flag takes `latest` or a session index rather than a session id, which is not safe when one worker serves several tasks. The templates therefore run each turn as an independent prompt and keep the reported `session_id` for observability only. If you need conversational memory, carry the relevant history into the prompt yourself.

## Tool approval

The CLI's `--approval-mode` (`default`, `auto_edit`, `yolo`, `plan`) governs what its built-in tools may do without confirmation. The templates do not pass it, so the CLI's default applies; add `--approval-mode yolo` to the spawn only for agents that are meant to run tools unattended, and prefer the CLI's policy engine for anything shared.

## See also

- [Unified Harness Surface](streaming_patterns.md#unified-harness-surface-framework-agents)
- [Observability & Tracing](observability_and_tracing.md)
- [Claude Code Agents](claude_code_agents.md) and [Codex Agents](codex_agents.md), the other CLI harnesses
8 changes: 4 additions & 4 deletions agentex/docs/docs/getting_started/choose_your_agent_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Choosing an agent type is only the **first** prompt in `agentex init`. After you

| `agentex init` prompt | Framework options |
|---|---|
| **Sync ACP** | Basic · OpenAI Agents SDK (Recommended) · OpenAI Agents SDK + Local Sandbox · LangGraph · Pydantic AI · Claude Code · Codex |
| **Async - ACP Only** | Basic · OpenAI Agents SDK · LangGraph · Pydantic AI · Claude Code · Codex |
| **Async - Temporal** | Basic · OpenAI Agents SDK (Recommended) · Pydantic AI · LangGraph · Claude Code · Codex |
| **Sync ACP** | Basic · OpenAI Agents SDK (Recommended) · OpenAI Agents SDK + Local Sandbox · LangGraph · Pydantic AI · Claude Code · Codex · Gemini CLI |
| **Async - ACP Only** | Basic · OpenAI Agents SDK · LangGraph · Pydantic AI · Claude Code · Codex · Gemini CLI |
| **Async - Temporal** | Basic · OpenAI Agents SDK (Recommended) · Pydantic AI · LangGraph · Claude Code · Codex · Gemini CLI |

!!! note "OpenAI Agents SDK + Local Sandbox"
The **Local Sandbox** variant is the OpenAI Agents SDK starter wired to run tools inside a local sandbox. It shares the same harness wiring and tutorial base as the plain [OpenAI Agents SDK](../development_guides/tutorials.md#sync-acp-simple-agents) starter — there is no separate tutorial for it.
Expand All @@ -42,7 +42,7 @@ How to choose:

- **Writing the loop yourself / LiteLLM only** → Basic.
- **Already standardized on a framework** → pick it directly (LangGraph, Pydantic AI, OpenAI Agents SDK).
- **Wrapping a coding CLI** → Claude Code (spawns the `claude` CLI) or Codex (spawns the `codex` CLI) as a local subprocess, streamed through the harness.
- **Wrapping a coding CLI** → Claude Code (spawns the `claude` CLI), Codex (spawns the `codex` CLI), or Gemini CLI (spawns the `gemini` CLI) as a local subprocess, streamed through the harness.
- **Want tools + good defaults and unsure** → OpenAI Agents SDK (the Recommended option for Sync and Temporal).

To add a framework that isn't in the list, see the `agentex-add-agent-framework` workflow.
Expand Down
1 change: 1 addition & 0 deletions agentex/docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ nav:
- Framework Agents:
- Claude Code: development_guides/claude_code_agents.md
- Codex: development_guides/codex_agents.md
- Gemini CLI: development_guides/gemini_cli_agents.md
- OpenAI Local Sandbox: development_guides/local_sandbox.md
- Race Conditions: development_guides/race_conditions.md
- Message Handling: development_guides/message_handling.md
Expand Down