This sample demonstrates how to forward client-side session state to a remote ADK agent over the Agent-to-Agent (A2A) protocol using A2A request metadata.
ADK's A2A transport is stateless: when a RemoteA2aAgent sends a message to
a remote agent, the caller's session.state is not automatically copied
to the remote side. If the remote agent expects state values such as
user_name (e.g. to resolve an instruction template placeholder like
{user_name}), those values will be missing and the agent will not behave
as intended.
This sample shows a working client-and-server configuration for bridging that gap:
- The client attaches a
RequestInterceptorthat copies a whitelisted subset ofsession.stateinto the outgoing A2A request metadata. - The server reads the incoming metadata (which ADK exposes as
run_config.custom_metadata['a2a_metadata']) from abefore_agent_callbackand writes the values back into its own session state so the remote agent'sinstructiontemplate can resolve them.
This pattern was confirmed as the recommended approach by an ADK maintainer
in google/adk-python#3098,
following the metadata-propagation work in commit
ba631764.
This sample complements that discussion by showing both sides wired together
in a working minimal example.
┌─────────────── Client (adk web) ───────────────┐
│ │
│ root_agent (Agent) │
│ └─ before_agent_callback: seed state │
│ state["user_name"] = "Alice" │
│ └─ sub_agent: greet_agent (RemoteA2aAgent) │
│ └─ RequestInterceptor.before_request │
│ whitelisted session.state keys │
│ └─→ parameters.request_metadata │
│ │
└──────────────────────┬─────────────────────────┘
│ A2A JSON-RPC
│ (MessageSendParams.metadata)
▼
┌─────────── Remote agent (adk api_server --a2a) ┐
│ │
│ ADK converts incoming metadata to: │
│ run_config.custom_metadata['a2a_metadata'] │
│ │
│ greet_agent (Agent) │
│ └─ before_agent_callback │
│ run_config.custom_metadata[...] │
│ └─→ callback_context.state["user_name"] │
│ │
│ instruction "Hello {user_name}! ..." │
│ resolves to "Hello Alice! ..." │
│ │
└────────────────────────────────────────────────┘
The remote greet_agent depends on user_name in its session state (used
here as an instruction template placeholder {user_name}). No
A2A-specific code is required beyond the one before_agent_callback that
copies incoming metadata into session state.
Both sides of the sample rely on public ADK APIs:
- Client:
RemoteA2aAgent,A2aRemoteAgentConfig,RequestInterceptor,ParametersConfig(fromgoogle.adk.a2a.agent.config). - Server:
callback_context.run_config.custom_metadata['a2a_metadata'](populated automatically by ADK's A2A request converter).
No monkey-patching, no private attribute access.
-
Start the remote greet_agent A2A server:
adk api_server --a2a --port 8001 contributing/samples/a2a_state_forwarding/remote_a2a
-
Run the main agent (in a separate terminal):
adk web contributing/samples/
User: Please greet me.
Bot: Hello Alice! How are you doing today?
The root agent seeds user_name = "Alice" into its own session state, the
RequestInterceptor forwards it through the A2A request metadata, and the
remote greet_agent resolves its {user_name} template from that value.
- One-way only. This sample covers client → server state forwarding. The
reverse direction (server → client) is not supported by the plain
to_a2a()/adk api_server --a2apath because it does not expose anafter_agentexecute-interceptor hook. Applications that need bidirectional state sync must build a customA2aAgentExecutorwith anExecuteInterceptor. - Keep the whitelist tight. The whitelist is a deliberate design choice,
not a nicety. Do not replace
ALLOWED_FORWARD_KEYSwithdict(ctx.session.state)in production — doing so will leak whatever the caller happens to have in their session to every remote agent they call.