Skip to content

fix(a2a): Use card descriptions for initial routing - #6694

Open
ftnext wants to merge 2 commits into
google:mainfrom
ftnext:fix-a2a-transfer-description
Open

fix(a2a): Use card descriptions for initial routing#6694
ftnext wants to merge 2 commits into
google:mainfrom
ftnext:fix-a2a-transfer-description

Conversation

@ftnext

@ftnext ftnext commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

2. Or, if no issue exists, describe the change:

N/A

Problem:

When a RemoteA2aAgent is configured with an agent-card URL or file path and without an explicit description, its card is currently resolved only after the remote agent has been selected and invoked.

The parent LLM therefore builds its initial transfer prompt with an empty description.
This prevents the card description from contributing to the first delegation decision and requires applications to duplicate the description in both the agent card and the local RemoteA2aAgent configuration.

A previous proposal in #6234 addressed the same cold-start problem, but it predated invocation-scoped authenticated agent-card resolution.
Resolving a card without the current InvocationContext, or copying authenticated card metadata into shared agent state, could leak session-specific metadata between invocations.

Solution:

Add an internal, invocation-aware transfer-description hook to BaseAgent.
Regular agents return their configured description, while RemoteA2aAgent uses the following precedence:

  1. Return an explicitly configured local description without fetching the card.
  2. Otherwise, resolve the agent card using the current InvocationContext and return its description.
  3. Cache public and static cards consistently with the existing client lifecycle.
  4. Keep cards fetched through per-invocation authentication interceptors out of shared agent state.

The transfer request processor resolves descriptions for all transfer targets concurrently before constructing the parent LLM's transfer prompt.

If card discovery fails, the processor logs a warning and falls back to the locally configured description instead of preventing the parent LLM request.

This change intentionally does not add agent-card skills, tags, or examples to the transfer prompt and does not introduce a new card refresh policy.
Those concerns can be handled separately.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All relevant unit tests pass locally.

The tests cover:

  • using a remote card description in the first transfer prompt
  • preserving an explicitly configured local description without fetching the card
  • keeping authenticated card descriptions scoped to their invocation
  • continuing parent routing when card discovery fails
  • preserving description adoption when a public card was prefetched before the remote agent initializes.

Command:

uv run pytest \
  tests/unittests/flows/llm_flows/test_agent_transfer_system_instructions.py \
  tests/unittests/agents/test_remote_a2a_agent.py \
  -q

Result:

180 passed, 215 warnings in 2.01s

The existing warnings are primarily experimental A2A warnings and existing test warnings.

The changed files also pass the repository pre-commit hooks:

pre-commit run --files \
  src/google/adk/agents/base_agent.py \
  src/google/adk/agents/remote_a2a_agent.py \
  src/google/adk/flows/llm_flows/agent_transfer.py \
  tests/unittests/agents/test_remote_a2a_agent.py \
  tests/unittests/flows/llm_flows/test_agent_transfer_system_instructions.py

Relevant strict type checking also passes:

uv run mypy --strict \
  src/google/adk/flows/llm_flows/agent_transfer.py

Manual End-to-End (E2E) Tests:

Planned verification using the existing a2a_basic sample:

  1. Start the remote A2A server:

    uv run adk api_server \
      --a2a \
      --port 8001 \
      contributing/samples/a2a/a2a_basic/remote_a2a
  2. Configure the remote client without a local description and give it a neutral name such as remote_agent.

remote_agent = RemoteA2aAgent(
    name="remote_agent",
    agent_card=(
        f"http://localhost:8001/a2a/check_prime_agent{AGENT_CARD_WELL_KNOWN_PATH}"
    ),
)
  1. Remove explicit prime-agent routing instructions from the parent agent, so the agent-card description is the only capability information available for selecting the remote agent.

  2. Start ADK Web:

    uv run adk web contributing/samples/a2a
  3. In a fresh session, send:

    Is 97 a prime number?
    
  4. Verify that:

    • the parent selects remote_agent on the first turn
image
  • the remote server receives the agent-card GET before the A2A RPC request
INFO:     127.0.0.1:52674 - "GET /a2a/check_prime_agent/.well-known/agent-card.json HTTP/1.1" 200 OK
INFO:     127.0.0.1:52674 - "POST /a2a/check_prime_agent HTTP/1.1" 200 OK
  • the remote agent returns the prime-check result

debug.log (agent-card.json was fetched!)

You have a list of other agents to transfer to:


Agent name: remote_agent
Agent description: An agent specialized in checking whether numbers are prime. It can efficiently determine the primality of individual numbers or lists of numbers.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing relevant unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules. (N/A; this change has no downstream dependencies.)

Additional context

The transfer prompt uses invocation-scoped metadata objects rather than mutating the shared RemoteA2aAgent.description.
This is important for authenticated or extended agent cards whose descriptions may differ between sessions.

Public and file-based cards may still be cached using the existing shared-card lifecycle.
When the remote agent itself initializes, it retains the existing behavior of adopting the resolved public card description.

ftnext added 2 commits August 12, 2026 22:57
Resolve remote agent-card descriptions before the parent LLM builds its first transfer prompt. Keep authenticated card metadata invocation-scoped, preserve explicitly configured descriptions, and fall back without blocking routing when card discovery fails.

Fixes google#4064
@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A2A remote agent client does not load description from agent card on start up

3 participants