Skip to content

BigQueryAgentAnalyticsPlugin: tool_origin not set to "A2A" when RemoteA2aAgent is used via sub_agents #5073

@evekhm

Description

@evekhm

BigQueryAgentAnalyticsPlugin: tool_origin not set to "A2A" when RemoteA2aAgent is used via sub_agents

Summary

When a RemoteA2aAgent is registered via sub_agents=[RemoteA2aAgent(...)] on an LlmAgent,
the BigQueryAgentAnalyticsPlugin logs tool_origin = "TRANSFER_AGENT" instead of "A2A".
This makes it impossible to distinguish A2A remote calls from regular local agent transfers in
observability data.

Workaround: Wrap the RemoteA2aAgent in AgentTool and pass it via tools=[]:

from google.adk.tools.agent_tool import AgentTool

root_agent = Agent(
    tools=[AgentTool(agent=agent2_server), ...]   # tool_origin = "A2A" ✅
)

AgentTool extends BaseTool, so it passes Pydantic validation. _get_tool_origin then
hits the isinstance(tool, AgentTool) and isinstance(tool.agent, RemoteA2aAgent) branch
and returns "A2A" correctly.


Steps to Reproduce

agent2_server = RemoteA2aAgent(
    name="agent2_server",
    agent_card="http://localhost:8001/a2a/app/.well-known/agent-card.json"
)

# Path A — sub_agents (idiomatic, broken observability)
root_agent = Agent(
    sub_agents=[agent2_server],          # tool_origin = "TRANSFER_AGENT" ❌
    plugins=[BigQueryAgentAnalyticsPlugin(...)]
)


# Path B — tools=[AgentTool(agent=...)] (workaround, works)
from google.adk.tools.agent_tool import AgentTool
root_agent = Agent(
    tools=[AgentTool(agent=agent2_server), ...],  # tool_origin = "A2A" ✅
    plugins=[BigQueryAgentAnalyticsPlugin(...)]
)

Query to confirm (run against the raw events table):

SELECT
  event_type,
  agent,
  JSON_VALUE(content, '$.tool') AS tool_name,
  JSON_VALUE(content, '$.tool_origin') AS tool_origin,
  FORMAT_TIMESTAMP("%H:%M:%S", timestamp) AS time
FROM `<project_id>.<dataset_id>.<table_id>`
WHERE event_type IN ('TOOL_STARTING', 'TOOL_COMPLETED')
ORDER BY timestamp DESC
LIMIT 20
Configuration Tool class seen by plugin tool_origin Works?
sub_agents=[RemoteA2aAgent(...)] TransferToAgentTool(agent_names=["name"]) "TRANSFER_AGENT" ✅ runs, ❌ wrong origin
tools=[AgentTool(agent=RemoteA2aAgent(...))] AgentTool(agent=RemoteA2aAgent) "A2A" ✅ runs, ✅ correct origin

Root Cause

Two-layer issue:

1. TransferToAgentTool discards agent object references.
In flows/llm_flows/agent_transfer.py:51, when building the tool for sub-agent transfers,
only the agent names (strings) are passed — not the agent objects:

transfer_to_agent_tool = TransferToAgentTool(
    agent_names=[agent.name for agent in transfer_targets]  # objects discarded here
)

2. _get_tool_origin cannot introspect TransferToAgentTool for A2A targets.
In plugins/bigquery_agent_analytics_plugin.py:197-204, the check for TransferToAgentTool
fires unconditionally before the A2A check, with no way to know the target is a
RemoteA2aAgent:

def _get_tool_origin(tool):
    if isinstance(tool, TransferToAgentTool):
        return "TRANSFER_AGENT"   # always fires for sub_agents, even for A2A targets
    if isinstance(tool, AgentTool):
        if isinstance(tool.agent, RemoteA2aAgent):
            return "A2A"          # only reachable via AgentTool(agent=RemoteA2aAgent(...))

Proposed Fix (for the underlying bug)

Option A (minimal — 3 files):

  1. tools/transfer_to_agent_tool.py — store agent objects alongside names:
def __init__(self, agent_names: list[str], agents: list[BaseAgent] | None = None):
    super().__init__(func=transfer_to_agent)
    self._agent_names = agent_names
    self._agents = agents or []
  1. flows/llm_flows/agent_transfer.py — pass agent objects when constructing the tool:
transfer_to_agent_tool = TransferToAgentTool(
    agent_names=[agent.name for agent in transfer_targets],
    agents=transfer_targets,
)
  1. plugins/bigquery_agent_analytics_plugin.py — check for A2A targets in _get_tool_origin:
if isinstance(tool, TransferToAgentTool):
    if (RemoteA2aAgent is not None
        and hasattr(tool, '_agents')
        and any(isinstance(a, RemoteA2aAgent) for a in tool._agents)):
        return "A2A"
    return "TRANSFER_AGENT"

Option B (cleaner): Introduce a TransferToA2aAgentTool subclass for A2A transfers,
handled as a dedicated case in _get_tool_origin.


Expected Behavior

tool_origin = "A2A" should be logged whenever a RemoteA2aAgent is invoked, regardless
of whether it was registered via sub_agents or tools=[AgentTool(agent=...)].


Environment

  • google-adk version: 1.28.0
  • BigQueryAgentAnalyticsPlugin version: same

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementation

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions