[Discussion] Current handling of A2A task interruptions
Background
The current handling of A2A task interruptions (input-required / auth-required) was introduced by #6038 . Before that, A2ATaskInterruptedException (added by #5924 in 1.19.0) only completed the invocation future exceptionally, carrying taskId, state, and reason.
However, this implementation has several practical problems.
Problems
1. The related A2A integration tests do not pass, and they are not run by default
When the A2A integration tests are enabled against a2a-echo-server, tests such as A2AAgentIT#a2a_client_agent_should_propagate_contextId_and_taskId_on_message_envelope and A2AAgentIT#a2a_multi_turn_sequence_should_propagate_contextId_and_taskId_through_scope fail. The symptom is that the first turn completes, and then an exception is thrown instead of the flow continuing to the next turn.
2. Without a scope, an interrupted task cannot be resumed
Outside an agentic system, the caller only receives the taskId and contextId on the exception. The documented way to resume is to invoke the same method again and pass those IDs through @A2AContextId / @A2ATaskId parameters (see A2ARepeatedInvocationTest#interruptedTask_isResumedWithItsOwnTaskId). Consequently:
- if the agent interface does not declare those two parameters, the IDs carried by the exception cannot be sent anywhere and the task is lost;
continuationMessage(...)is package-private and there is no publicresume(input)-style API.
With a scope, by contrast, resumption works without knowing the interface signature, because the continuation message is rebuilt from the stored metadata. The capability is inversely related to whether a scope exists, which is counter-intuitive.
3. A caller such as a supervisor / LLM agent cannot supply the missing input itself
When the A2A client is invoked as a subagent, AgentInvoker#internalInvoke puts the scope into LangChain4jManaged, so the client always takes the suspend() branch. The chain is:
suspend() → AgenticSystemSuspendedException → rethrown by AgentInvoker → caught by AgentExecutor, which calls planner.onSubagentSuspended() and returns null → PlannerBasedInvocationHandler throws AgenticSystemSuspendedException again.
The whole system is suspended, and the supervisor's planner LLM never learns what the remote agent asked for (the input-required content only lives in the scope metadata). A supervisor that is itself capable of answering has no opportunity to do so.
4. Different exceptions depending on whether a scope exists
In DefaultA2AClientBuilder#invokeAgent:
if (response.interruption != null) {
if (scope != null) {
suspend(scope, agentId, response.interruption); // throws AgenticSystemSuspendedException
}
throw response.interruption; // throws A2ATaskInterruptedException
}The same interruption surfaces as AgenticSystemSuspendedException inside an agentic system and as A2ATaskInterruptedException when the agent is invoked directly. The former has no cause, so the original interruption details only exist in the scope metadata and can be recovered only via A2ATaskInterruptedException.from(...). There is no single type to handle.
Is it realistic to consider rewriting the entire interrupt processing flow?
Source: langchain4j/langchain4j