Description
When running an A2UI-enabled agent using adk run, the CLI crashes with a KeyError as soon as the first user message is sent.
KeyError: 'Context variable not found: `expression`.'
Full traceback excerpt:
File ".../google/adk/utils/instructions_utils.py", line 124, in inject_session_state
return await _async_sub(r'{+[^{}]*}+', _replace_match, template)
File ".../google/adk/utils/instructions_utils.py", line 122, in _replace_match
raise KeyError(f'Context variable not found: `{var_name}`.')
KeyError: 'Context variable not found: `expression`.'
Steps to Reproduce
-
Create an ADK agent that uses A2uiSchemaManager.generate_system_prompt() with include_schema=True to build its instruction.
-
Expose root_agent at module level so it is compatible with adk run.
# agent.py
root_agent = LlmAgent(
model=Gemini(model="gemini-2.0-flash"),
name="my_agent",
instruction=schema_manager.generate_system_prompt(
role_description="...",
include_schema=True,
include_examples=True,
),
tools=[...],
)
-
Run the agent:
-
Type any message and press Enter → crash.
Root Cause
The BasicCatalog JSON schema bundled at agent_sdks/python/src/a2ui/assets/0.9/basic_catalog.json contains the following text in the description of the formatString function:
The value string can contain interpolated expressions in the `${expression}` format.
When generate_system_prompt() is called with include_schema=True, this description is embedded verbatim into the agent's instruction string.
When adk run processes the first user message, the ADK framework calls inject_session_state() in instructions_utils.py. This function scans the full instruction string with the regex r'{+[^{}]*}+'. It matches {expression}, strips the braces to extract "expression", validates it as a Python identifier (which passes), looks it up in the active session state dict (which is empty because adk run manages sessions internally), and raises a KeyError.
The key line in the ADK source:
# google/adk/utils/instructions_utils.py
return await _async_sub(r'{+[^{}]*}+', _replace_match, template)
Why Existing A2A Server Samples Are Unaffected
The existing A2A-based samples (e.g. samples/agent/adk/restaurant_finder/) manually create the ADK session and inject a dummy variable before running:
# restaurant_finder/agent.py
session_state = {"base_url": self.base_url, "expression": "{expression}"}
session = await runner.session_service.create_session(
...,
state=session_state,
)
This satisfies the ADK template engine — it finds "expression" in the state and silently replaces the tag. adk run manages sessions internally and provides no hook to pre-seed the session state before the first message, so this workaround is not available when using the CLI.
Description
When running an A2UI-enabled agent using
adk run, the CLI crashes with aKeyErroras soon as the first user message is sent.Full traceback excerpt:
Steps to Reproduce
Create an ADK agent that uses
A2uiSchemaManager.generate_system_prompt()withinclude_schema=Trueto build its instruction.Expose
root_agentat module level so it is compatible withadk run.Run the agent:
Type any message and press Enter → crash.
Root Cause
The
BasicCatalogJSON schema bundled atagent_sdks/python/src/a2ui/assets/0.9/basic_catalog.jsoncontains the following text in the description of theformatStringfunction:When
generate_system_prompt()is called withinclude_schema=True, this description is embedded verbatim into the agent'sinstructionstring.When
adk runprocesses the first user message, the ADK framework callsinject_session_state()ininstructions_utils.py. This function scans the full instruction string with the regexr'{+[^{}]*}+'. It matches{expression}, strips the braces to extract"expression", validates it as a Python identifier (which passes), looks it up in the active session state dict (which is empty becauseadk runmanages sessions internally), and raises aKeyError.The key line in the ADK source:
Why Existing A2A Server Samples Are Unaffected
The existing A2A-based samples (e.g.
samples/agent/adk/restaurant_finder/) manually create the ADK session and inject a dummy variable before running:This satisfies the ADK template engine — it finds
"expression"in the state and silently replaces the tag.adk runmanages sessions internally and provides no hook to pre-seed the session state before the first message, so this workaround is not available when using the CLI.