You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
RemoteA2AAgent builds the outbound io.a2a.spec.Message via prepareMessage() / newA2AMessage(),
but neither method ever calls Message.Builder#metadata(...). This means there is currently no way
for a Java ADK application to pass any custom data - session.state(), a user id, a tenant id, an
auth-scoped identifier the remote agent's tools need - to a remote A2A agent. The remote agent's tools
receive the conversation content only; anything the calling agent knows about the current user/session
is silently unavailable on the other side of the A2A boundary.
This is a real limitation for a common pattern: a tool on the remote agent needs to resolve a resource
(e.g. an OAuth access token) that is looked up by a caller-supplied identifier (e.g. user_id) stored
in session.state(). In-process sub-agent calls get this for free (session.state() is shared); A2A
calls get nothing.
Note this is not the same as two issues I filed previously, and I want to make the distinction
explicit so it's easy to keep this one scoped:
A2A communication drops state context by invoking 4-argument runner.runAsync #1240 (fixed in 410ff810) is about the receiving side: AgentExecutor used to silently drop
incoming MessageSendParams.metadata() instead of routing it into RunConfig.customMetadata().
That's fixed - a receiving agent can now read a2a_metadata from RunConfig.customMetadata() and,
via a native beforeAgentCallback, copy whatever it needs into session.state().
This issue is about a third, independent gap: even with both of the above fixed, the Message object itself - the 1st argument to sendMessage, built by prepareMessage() - never
gets .metadata(...) attached at all. There is no code path, and no builder hook, to put anything
there. Fixing RemoteA2AAgent hardcodes ClientCallContext to null, preventing authentication and header propagation #1258 alone would not address this: ClientCallContext and Message are separate
parameters built independently.
Describe the solution you'd like
adk-python's RemoteA2aAgent already solves exactly this with an opt-in callback:
and, inside prepareMessage(), call it and attach the result via .metadata(...) on the Message.Builder.
This is deliberately opt-in and lets the caller pick exactly what crosses the A2A boundary - it does not
ask for session.state() to be forwarded automatically or in full, which I understand is intentionally
avoided elsewhere in ADK (per the #1240 resolution comment).
The provider is a plain callback with no fixed/whitelisted set of keys baked into the API - ADK would
simply attach whatever Map<String, Object> the caller's implementation returns. It's entirely up to the
application to decide what to include: a single identifier, several selected keys, or (if it chooses to)
all of session.state(). This mirrors the full flexibility of a2a_request_meta_provider in adk-python -
the library imposes no restriction on which keys or how many can be returned, it only wires the callback
through to Message.metadata().
Minimal reproducible example (runnable today, no external services needed)
Drop this test method into a2a/src/test/java/com/google/adk/a2a/agent/RemoteA2AAgentTest.java (it uses only fixtures/imports
already present in that file) and run:
mvn -pl a2a test -Dtest=RemoteA2AAgentTest#runAsync_doesNotPropagateSessionStateToOutboundMessage
The test passes today, which is the bug: it proves session.state() - set up exactly the way an
application would populate it via stateDelta before running the agent - never reaches the Message
sent to the remote peer, even though mockClient.sendMessage(...) is the exact call site prepareMessage() feeds.
@Test@SuppressWarnings("unchecked") // cast for MockitopublicvoidrunAsync_doesNotPropagateSessionStateToOutboundMessage() {
RemoteA2AAgentagent = createAgent();
// Simulates an application that populated session.state() via stateDelta before this run -// e.g. a user id a remote tool would need to resolve an OAuth token, exactly as it would for// an in-process sub-agent call.SessionsessionWithState =
Session.builder("session-state-repro")
.appName("demo")
.userId("user")
.state(ImmutableMap.of("user_id", "user-42", "tenant_id", "tenant-7"))
.events(
ImmutableList.of(
Event.builder()
.id("e1")
.author("user")
.content(
Content.builder()
.role("user")
.parts(ImmutableList.of(Part.builder().text("hello").build()))
.build())
.build()))
.build();
InvocationContextcontext =
InvocationContext.builder()
.sessionService(newInMemorySessionService())
.artifactService(newInMemoryArtifactService())
.pluginManager(newPluginManager())
.invocationId("invocation-state-repro")
.agent(newTestAgent())
.session(sessionWithState)
.runConfig(RunConfig.builder().build())
.build();
mockStreamResponse(consumer -> consumer.accept(createFinalEvent("ok"), agentCard));
varunused = agent.runAsync(context).toList().blockingGet();
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mockClient)
.sendMessage(messageCaptor.capture(), any(List.class), any(Consumer.class), any());
MessagesentMessage = messageCaptor.getValue();
// BUG: session.state() (user_id, tenant_id) is fully known to invocationContext at this point,// but prepareMessage()/newA2AMessage() never call .metadata(...), so it never reaches the// outbound Message. A remote agent's tools have no way to see it - even though the exact same// data would be visible via session.state() for an in-process sub-agent call.assertThat(sentMessage.getMetadata()).isAnyOf(null, ImmutableMap.of());
}
Environment
google-adk-a2a: 1.8.0 (current main, confirmed present at RemoteA2AAgent.java:196-213
(newA2AMessage / prepareMessage) and RemoteA2AAgent.java:240 (the sendMessage call site))
Is your feature request related to a problem? Please describe.
RemoteA2AAgentbuilds the outboundio.a2a.spec.MessageviaprepareMessage()/newA2AMessage(),but neither method ever calls
Message.Builder#metadata(...). This means there is currently no wayfor a Java ADK application to pass any custom data -
session.state(), a user id, a tenant id, anauth-scoped identifier the remote agent's tools need - to a remote A2A agent. The remote agent's tools
receive the conversation content only; anything the calling agent knows about the current user/session
is silently unavailable on the other side of the A2A boundary.
This is a real limitation for a common pattern: a tool on the remote agent needs to resolve a resource
(e.g. an OAuth access token) that is looked up by a caller-supplied identifier (e.g.
user_id) storedin
session.state(). In-process sub-agent calls get this for free (session.state()is shared); A2Acalls get nothing.
Note this is not the same as two issues I filed previously, and I want to make the distinction
explicit so it's easy to keep this one scoped:
410ff810) is about the receiving side:AgentExecutorused to silently dropincoming
MessageSendParams.metadata()instead of routing it intoRunConfig.customMetadata().That's fixed - a receiving agent can now read
a2a_metadatafromRunConfig.customMetadata()and,via a native
beforeAgentCallback, copy whatever it needs intosession.state().RemoteA2AAgenthardcoding the 4th argument (ClientCallContext) ofa2aClient.sendMessage(...)tonull, which breaks transport-level concerns (HTTP headerresolution, credential services used to authenticate the A2A call itself).
Messageobject itself - the 1st argument tosendMessage, built byprepareMessage()- nevergets
.metadata(...)attached at all. There is no code path, and no builder hook, to put anythingthere. Fixing RemoteA2AAgent hardcodes ClientCallContext to null, preventing authentication and header propagation #1258 alone would not address this:
ClientCallContextandMessageare separateparameters built independently.
Describe the solution you'd like
adk-python'sRemoteA2aAgentalready solves exactly this with an opt-in callback:A caller can implement this to explicitly select what to forward, e.g.:
I'd like
RemoteA2AAgent(Java) to expose the equivalent extension point, e.g.:and, inside
prepareMessage(), call it and attach the result via.metadata(...)on theMessage.Builder.This is deliberately opt-in and lets the caller pick exactly what crosses the A2A boundary - it does not
ask for
session.state()to be forwarded automatically or in full, which I understand is intentionallyavoided elsewhere in ADK (per the #1240 resolution comment).
The provider is a plain callback with no fixed/whitelisted set of keys baked into the API - ADK would
simply attach whatever
Map<String, Object>the caller's implementation returns. It's entirely up to theapplication to decide what to include: a single identifier, several selected keys, or (if it chooses to)
all of
session.state(). This mirrors the full flexibility ofa2a_request_meta_providerin adk-python -the library imposes no restriction on which keys or how many can be returned, it only wires the callback
through to
Message.metadata().Minimal reproducible example (runnable today, no external services needed)
Drop this test method into
a2a/src/test/java/com/google/adk/a2a/agent/RemoteA2AAgentTest.java(it uses only fixtures/importsalready present in that file) and run:
The test passes today, which is the bug: it proves
session.state()- set up exactly the way anapplication would populate it via
stateDeltabefore running the agent - never reaches theMessagesent to the remote peer, even though
mockClient.sendMessage(...)is the exact call siteprepareMessage()feeds.Environment
google-adk-a2a: 1.8.0 (currentmain, confirmed present atRemoteA2AAgent.java:196-213(
newA2AMessage/prepareMessage) andRemoteA2AAgent.java:240(thesendMessagecall site))