11 KiB
Interface Priority Analysis & Open Questions
The Big Picture
We're defining framework-agnostic interfaces that allow gemini-cli to:
- Keep its existing execution loop working unchanged (Legacy path)
- Swap in ADK as an alternative runtime via config flag
- Eventually support OpenRouter or other agent backends
- Maintain all existing CLI behavior: hooks, policies, confirmations, UI events
Proposed Interface Layers (Priority Order)
P0 (Critical Path - Must Define First)
1. AgentEvent / Event Stream Contract
Why first: Everything else consumes or produces these events. The UI renders them. The hooks intercept them. The adapters translate to/from them.
Key decision: Merge Dewitt's simpler model with Coworker's richer model?
Recommendation: Coworker's approach is more complete. Key additions:
threadIdfor sub-agent tracking (AG-UI hasparentRunId)tool_updatefor progress on long-running toolselicitation_request/responseas first-class (not just tool_confirmation)usageevent for token tracking_metaescape hatch (matches AG-UI's extensibility philosophy)initializeevent (matches AG-UI's RunStarted)
Open questions:
- Do we need AG-UI's start/content/end triple pattern for streaming? Or is yielding partial events sufficient?
- How do ContentPart types map to existing gemini-cli Part types?
- Should events carry a
sourcefield? (useful for hook attribution)
2. Agent Interface
Why second: This is the primary abstraction that LocalAgentExecutor, ADK adapters, and future OpenRouter adapters all implement.
Key decision: Dewitt's runAsync/runEphemeral vs Coworker's
send(Trajectory|string)
Recommendation: Hybrid approach:
- Dewitt's
runAsync/runEphemeralsplit is ADK-aligned and cleaner for the factory pattern - BUT add Coworker's elicitation support via AgentSend union type
- The Trajectory concept is powerful but may be too opinionated for Phase 2
Agent<TInput, TOutput>
name: string
description: string
runAsync(input, options) → AsyncGenerator<AgentEvent, TOutput>
runEphemeral(input, options) → AsyncGenerator<AgentEvent, TOutput>
Open questions:
- Should Agent also support
send()for mid-stream interactions (elicitations)? - How does AbortSignal propagate through the adapter boundary?
- Do we need a
capabilitiesfield (supports elicitation? supports HITL? etc.)?
3. Tool Execution Contract
Why third: Tools are the primary action mechanism. Both the policy engine and hooks system wrap tool execution.
What needs abstracting:
- Tool declaration (name, schema) — already somewhat generic via JSON Schema
- Tool execution (args → result)
- Tool confirmation flow (ASK_USER → user decision → proceed/deny)
- Tool result shape (llmContent + displayContent + error + tailCalls)
Key decision: Keep DeclarativeTool pattern or flatten to a simpler interface?
Recommendation: Define a minimal ToolExecutor interface:
ToolExecutor {
name: string
description: string
schema: JSONSchema
execute(args, context): Promise<ToolResult>
requiresConfirmation?(args, context): Promise<boolean>
}
DeclarativeTool remains the concrete implementation. ADK's BaseTool adapts to this.
Open questions:
- How do MCP tools fit? They already have their own protocol.
- Tool annotations (destructive hints) — should these be in the interface?
- Long-running tools need progress reporting — how does this interact with tool_update events?
P1 (Important - Define After P0)
4. Policy / Permission Interface
Why important: Every tool call goes through policy. External agents need policy enforcement too.
Current state: gemini-cli has a sophisticated TOML-based policy engine with tiered priorities. ADK-TS has a simpler SecurityPlugin with PolicyOutcome (DENY/CONFIRM/ALLOW).
What needs abstracting:
PolicyEngine {
evaluate(toolName, args, context): PolicyDecision // ALLOW | DENY | ASK_USER
getExcludedTools(): string[] // Tools statically denied
}
Key decision: Do external agents (OpenRouter, etc.) get the same policy enforcement?
Open questions:
- If an ADK agent calls a tool internally, does gemini-cli's policy apply?
- With
pauseOnToolCalls: truein ADK, the CLI controls execution — but what about headless mode? - How do agent-level policies work? (allow/deny entire agents, not just tools)
- Should policy be a middleware (AG-UI pattern) or a callback (ADK plugin pattern)?
5. Hooks Interface
Why important: Hooks are a major gemini-cli feature. They need to work regardless of which agent backend runs.
Current state: 11 hook types firing at specific lifecycle points.
What needs abstracting:
- Hook lifecycle must be backend-agnostic
- BeforeModel/AfterModel hooks need to work even when ADK controls the model
- BeforeTool/AfterTool hooks need to intercept regardless of who executes the tool
Key challenge: When ADK runs the model internally, gemini-cli hooks can't easily intercept. Dewitt's solution: ADK uses gemini-cli's model via AdkGeminiModel adapter — hooks fire inside GeminiChat.
Open questions:
- If OpenRouter runs the model, how do BeforeModel/AfterModel hooks work?
- Do we need a "model steering" abstraction (injecting context mid-stream)?
- Can hooks be expressed as AG-UI middleware? (intercept event stream)
6. Model / LLM Interface
Why important: Model abstraction enables swapping LLM providers.
Dewitt's approach: Exposes Model interface, ADK uses it via AdkGeminiModel adapter. Coworker's approach: Model is internal to Agent (no separate Model interface).
Recommendation: Keep Dewitt's separate Model interface BUT make it provider-agnostic:
- Remove
@google/genaitypes from the interface signature - Define generic Message/Content types
- Model interface is an implementation detail, not part of the Agent contract
Open questions:
- Can we define a truly provider-agnostic Model interface?
- Or is the Model always tied to the agent backend? (ADK uses Gemini, OpenRouter uses whatever)
- Model routing (choosing which model) — is this a concern of the Model interface or a separate service?
P2 (Important but Can Follow)
7. Session / State Interface
Current state: gemini-cli uses ChatRecordingService (JSON files). ADK uses Session with BaseSessionService.
What needs abstracting:
- Session creation/retrieval
- State persistence across turns
- History/trajectory management
Open questions:
- Does the trajectory (coworker's concept) replace gemini-cli's chat recording?
- Should session state be shared between gemini-cli and the agent backend?
8. Elicitation / User Interaction Interface
What it covers: Model fallback dialogs, tool confirmations, Ctrl+B interrupts, user questions
Current state: gemini-cli uses ConfirmationBus + MessageBus. AG-UI uses frontend tools.
Open questions:
- Is elicitation just a special case of tool calls (AG-UI approach)?
- Or is it a first-class event type (coworker's approach)?
- How does Ctrl+B (cancel/interrupt) propagate through the agent boundary?
9. Configuration / Capability Discovery
What it covers: Feature flags, experiment settings, agent capabilities
Open questions:
- How does an external agent declare its capabilities?
- Does OpenRouter support HITL? Elicitation? Tool confirmation? Each agent may differ.
- Need a
capabilitiesnegotiation at connection time?
P3 (Future / Can Defer)
10. A2UI / Rich UI Interface
- Declarative UI generation from agents
- Not critical for Phase 2 but important for differentiation
11. Memory / Artifact Interface
- ADK has memory/artifact services
- gemini-cli has ChatRecordingService + memory tools
- Can standardize later
12. Telemetry / Observability Interface
- Both systems have telemetry
- Can standardize later
Critical Open Questions (Need Team Discussion)
1. OpenRouter Integration Model
Question: When OpenRouter (or any external agent) is used, what does the integration look like?
Option A: Full Agent Interface — OpenRouter implements the Agent interface directly
- Pro: Clean, uniform
- Con: OpenRouter doesn't support HITL, hooks, policies natively
Option B: ACP Shim — Agent Communication Protocol between CLI and external agents
- Pro: Standards-based
- Con: Additional protocol layer, may be premature
Option C: Model-only Integration — OpenRouter is just an alternative Model, not Agent
- Pro: Simpler, leverages existing agent loop
- Con: Doesn't support OpenRouter-specific features
Recommendation: Start with Option C (model-only). OpenRouter provides an LLM endpoint. Gemini-cli's own agent loop handles tools, policies, hooks. This means defining a provider-agnostic Model interface is the key enabler.
2. Tool Execution: Client-side vs Agent-side
Question: Who executes tools — the CLI or the agent backend?
Option A: Always client-side (CLI executes, agent suspends)
- ADK:
pauseOnToolCalls: true - Pro: CLI maintains control, policies enforced, hooks fire
- Con: Higher latency, more round-trips
Option B: Agent-side execution (agent runs tools internally)
- Pro: Faster, simpler
- Con: Bypasses CLI policies, hooks, confirmations
Option C: Configurable — CLI decides per-tool or per-agent
- Pro: Flexible
- Con: Complex
Recommendation: Option A for safety-critical CLI use case. Option B only for trusted/sandboxed sub-agents.
3. Model Steering (Hooks that inject context mid-stream)
Question: How do user-local hooks (like injecting project context) work with external agents?
Answer: They can only work if:
- The CLI controls the model (via Model interface adapter) — then BeforeModel hook injects context
- OR the agent supports a "system instruction update" mechanism
For OpenRouter: model steering works because CLI controls the model call. For ADK: model steering works because AdkGeminiModel wraps GeminiChat. For fully opaque agents: model steering cannot work — this is a known limitation.
4. Elicitation Flow
Question: When the agent needs user input (model fallback, clarification), how does it work?
For CLI-controlled agents: Agent yields an elicitation_request event → CLI renders prompt → user responds → CLI sends response back via session.stream({ kind: 'elicitation_response', ... }) to resume
For external agents: Agent uses A2A protocol or similar to send elicitation → CLI bridges the request to user → response sent back via protocol
Key insight: Elicitation is fundamentally about the agent SUSPENDING and
waiting for user input. ADK already supports this via pauseOnToolCalls. Can we
generalize to pauseOnElicitation?
5. Sub-agent Identity and Policies
Question: When a sub-agent spawns, does it inherit parent policies? Get its own?
Current gemini-cli behavior: Sub-agents registered as tools, go through same policy engine. ADK behavior: Sub-agents are child nodes in agent tree, get parent's plugins.
Recommendation: Sub-agents inherit parent policy context. Additional restrictions can be layered (e.g., sub-agent X cannot use shell tool). This is already how gemini-cli works.