Repository navigation
run_live: change instruction/tools or transfer agents on the open Live connection (send_context_update, in-session transfer)聽#7692
Description
馃敶 Required Information
Is your feature request related to a specific problem?
With Runner.run_live(), every transfer_to_agent closes the Gemini Live connection and opens a new one for the child agent. This is a problem for Live Avatar (video) use cases, where the connection is also the avatar session:
- The connection and avatar session are rebuilt on every transfer. Measured with
gemini-3.8-liveand a Vertex Live avatar (eu, ADK 2.12): about 4.6 s per transfer. That is a fixed 1.0 sDEFAULT_TRANSFER_AGENT_DELAY, about 0.9 s connect, and about 2.7 s until the new avatar session produces output. The user sees a frozen or blank avatar and hears silence. child_ctx.live_session_resumption_handle = Noneandrun_config_for_new_live_session(...)force a fresh session, so the conversation is replayed withsend_history(). The server-side state of the first connection is lost.- Mic frames that queue in
LiveRequestQueueduring the swap are flushed into the new connection at once. We hit1011 Client overran StreamHandler internal queue sizeand had to cap the queue backlog ourselves.
The Live API now allows changing the model preamble on an open session. google-genai >= 2.32 has AsyncSession.send_context_update(system_instruction=..., tools=...). Updates are processed in order with the other inputs. ADK has no way to use it: BaseLlmConnection, GeminiLlmConnection and LiveRequestQueue have no equivalent, and a running run_live agent cannot change its instruction or tools.
Describe the Solution You'd Like
Two layers. The first is small and useful alone.
-
Expose
send_context_updatethrough ADK.BaseLlmConnection.send_context_update(system_instruction=None, tools=None)and theGeminiLlmConnectionimplementation, which forwards to the genai session.- A way to call it for an existing agent: either
LiveRequestQueue.send_context_update(...), or a field on tool and callback output, e.g.tool_context.actions.live_context_update. The update must be sent before the tool response of the same call, so the model's next turn already uses it. - The system instruction and tool declarations that ADK keeps for the active agent (
llm_request) are updated too, so later tool dispatch andsession_resumptionreconnects use the new values.
-
Agent-to-agent transfer without a new connection.
- An opt-in mode, e.g.
RunConfig(live_transfer_mode="in_session")(default"reconnect", today's behaviour). - On
transfer_to_agent, ADK keeps the connection open. It builds the child'sllm_request(instruction, tools, and the callbacks that run in the request processors), sends only the difference assend_context_update, and continues the loop with the child as the active agent. There is noDEFAULT_TRANSFER_AGENT_DELAY, nosend_history(), the resumption handle is kept, andLiveRequestQueuekeeps its backlog. - Limits that can be rejected with a clear error: same model and same Live config for parent and child (voice, avatar, modalities), and
transfer_to_agenttargets only the agents of the existing tree.
- An opt-in mode, e.g.
Impact on your work
We build an AI assistant with a Live Avatar and several role agents (routing, consultation, purchase). The per-transfer pause of about 4.6 s is not acceptable for a live session, so we dropped ADK's transfer. We now keep one ADK agent with a fixed instruction and all tools, switch a "virtual agent" in session state with a custom switch_agent tool, and return the role's playbook as the tool result as is specified in LiveAPI Best practices. That works, but it has no restrictive per-agent toolset or static system instructions at the model level that cannot be removed by ContextCompression. We would prefer real sub-agents with their own instruction and tools, as ADK models them. There is no hard deadline; this decides whether we stay on a workaround in production.
Willingness to contribute
Yes. We can send a PR for layer 1 (send_context_update on the connection and queue) first, then discuss layer 2 against the design that the maintainers prefer.
馃煛 Recommended Information
Describe Alternatives You've Considered
| Alternative | Result |
|---|---|
ADK transfer_to_agent under run_live |
Works, but about 4.6 s per transfer with an avatar, and the mic backlog problem above. |
Workflow nodes instead of transfers |
A Live session per speaking node, so no gain. Conversational agents inside a workflow-as-tool never received the next user turn, and task-mode delegation was not dispatched under run_live. Workflows stay useful for non-speaking tool steps. |
One agent with a fixed instruction, all tools, and a state-based switch_agent tool plus a before-tool gate |
What we run today. It needs custom gating, every tool is always declared, and the instruction carries all playbooks. |
Calling genai send_context_update ourselves, outside ADK |
Possible without run_live, but then ADK's session service, resumption, tools and callbacks are lost. |
Proposed API / Implementation
# Layer 1: from a tool (update is sent before this call's tool response)
def become_specialist(tool_context: ToolContext) -> dict:
tool_context.actions.live_context_update = LiveContextUpdate(
system_instruction="You are now the specialist. ...",
tools=[specialist_tool], # None keeps the current tools, [] clears them
)
return {"status": "ok"}
# Or from application code driving the queue
live_request_queue.send_context_update(system_instruction="...", tools=[...])
# Layer 2: opt-in transfer on the same connection
run_config = RunConfig(live_transfer_mode="in_session")
async for event in runner.run_live(..., run_config=run_config):
...Where to change it in ADK 2.12:
google/adk/models/base_llm_connection.pyandgemini_llm_connection.py: newsend_context_update, forwarded tosession.send_context_update.google/adk/agents/live_request_queue.py: newLiveRequestQueuemessage type and its handling in the send task oflive/_live_llm_flow.py.live/_live_llm_flow.py(the transfer branch aroundtransfer_to_agent): inin_sessionmode, skipasyncio.sleep(DEFAULT_TRANSFER_AGENT_DELAY),llm_connection.close()andchild_ctx.live_session_resumption_handle = None. Build the child'sllm_request, diff its instruction and tools against the current ones, and sendsend_context_update.
Open questions: tool declarations versus the per-agent tool dispatch table when the active agent changes inside one loop; prefix-cache invalidation (the genai docs say system_instruction and tools updates invalidate it, so updates should be explicit, not automatic).
Additional Context
- Versions:
google-adk 2.12.0,google-genai 2.32.0, Python 3.12, modelgemini-3.8-liveon Agent Platform, Live Avatar enabled, VAD, session resumption and context window compression on. send_context_updateon an open session had no measurable cost in our probes: the tool call after the update took the same 0.4 s as without it, the speech after the tool response within about 0.1 s (audio), and an instruction of about 9k tokens did not slow the update.
Activity