When a [SkillToolset] skill's instructions reference a tool (e.g. confirmtool) that is registered via an external/frontend toolset [AGUIToolset] but is not yet present in the agent's available tool list at request time, the agent enters an infinite list_skills → load_skill → list_skills loop and never produces a response.
Root cause (from source analysis):
[DEFAULT_SKILL_SYSTEM_INSTRUCTION] is appended to the agent instruction via [process_llm_request] on every LLM call, telling the model: "you MUST use load_skill before proceeding". There is no guard to skip this injection after the skill has already been activated in the current turn (the adk_activated_skill state key exists but is never used to suppress re-injection).
After load_skill returns, the LLM needs to call a tool referenced in the skill instructions (e.g. confirmtool). If that tool is absent from the tool list — e.g. because it comes from a frontend/HITL toolset that hasn't registered yet — the LLM has no valid next action and falls back to list_skills → load_skill again.
There is no max-iteration guard on this cycle.
To reproduce:
from google.adk.agents import Agent
from google.adk.tools.skill_toolset import SkillToolset, DEFAULT_SKILL_SYSTEM_INSTRUCTION
from google.adk.skills import load_skill_from_dir
from ag_ui_adk import ADKAgent, AGUIToolset
skill-creator/SKILL.md references a tool called "confirmtool"
that comes from AGUIToolset but is NOT yet in RunAgentInput.tools
e.g. on the first request before the frontend finishes mounting)
skill = load_skill_from_dir("skills/skill-test")
skill_toolset = SkillToolset(skills=[skill])
agent = Agent(
name="test",
model=model,
instruction="..." + DEFAULT_SKILL_SYSTEM_INSTRUCTION,
tools=[AGUIToolset(), skill_toolset],
)
Send a request where [RunAgentInput.tools = []]. Observe infinite loop.
Expected behavior:
One of:
ADK detects that adk_activated_skill is already set and does not re-inject [DEFAULT_SKILL_SYSTEM_INSTRUCTION] on subsequent LLM calls within the same turn
ADK surfaces a clear error when a tool referenced in skill instructions is not available
[SkillToolset] has a max tool-call iteration guard (e.g. 5 cycles) before giving up
Actual behavior:
Infinite loop of list_skills → load_skill → list_skills → load_skill ... until the request times out or is manually killed.
Logs
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: list_skills]
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: load_skill]
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: load_skill_resource] (×4)
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: list_skills]
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: load_skill]
[ADK_EVENT] author=generate_skill_with_ai content=[FunctionCall: list_skills]
... repeats indefinitely
Versions:
Workaround:
Remove [SkillToolset] and [DEFAULT_SKILL_SYSTEM_INSTRUCTION] from the agent when skill instructions reference frontend/HITL tools. Embed the skill instructions directly in the agent's system prompt instead.
When a [SkillToolset] skill's instructions reference a tool (e.g. confirmtool) that is registered via an external/frontend toolset [AGUIToolset] but is not yet present in the agent's available tool list at request time, the agent enters an infinite list_skills → load_skill → list_skills loop and never produces a response.
Root cause (from source analysis):
[DEFAULT_SKILL_SYSTEM_INSTRUCTION] is appended to the agent instruction via [process_llm_request] on every LLM call, telling the model: "you MUST use load_skill before proceeding". There is no guard to skip this injection after the skill has already been activated in the current turn (the adk_activated_skill state key exists but is never used to suppress re-injection).
After load_skill returns, the LLM needs to call a tool referenced in the skill instructions (e.g. confirmtool). If that tool is absent from the tool list — e.g. because it comes from a frontend/HITL toolset that hasn't registered yet — the LLM has no valid next action and falls back to list_skills → load_skill again.
There is no max-iteration guard on this cycle.
To reproduce:
Send a request where [RunAgentInput.tools = []]. Observe infinite loop.
Expected behavior:
One of:
ADK detects that adk_activated_skill is already set and does not re-inject [DEFAULT_SKILL_SYSTEM_INSTRUCTION] on subsequent LLM calls within the same turn
ADK surfaces a clear error when a tool referenced in skill instructions is not available
[SkillToolset] has a max tool-call iteration guard (e.g. 5 cycles) before giving up
Actual behavior:
Infinite loop of list_skills → load_skill → list_skills → load_skill ... until the request times out or is manually killed.
Logs
Versions:
Workaround:
Remove [SkillToolset] and [DEFAULT_SKILL_SYSTEM_INSTRUCTION] from the agent when skill instructions reference frontend/HITL tools. Embed the skill instructions directly in the agent's system prompt instead.