{"record":{"id":"dcaa735b216993fc","repo":"alibaba/spring-ai-alibaba","slug":"failed-to-execute-agent-tool-s-parentthreadid-dcaa73","errorCode":null,"errorMessage":"Failed to execute agent tool '%s' (parentThreadId=%s, input=%s): sub-agent returned no messages","messagePattern":"Failed to execute agent tool '(.+?)' \\(parentThreadId=(.+?), input=(.+?)\\): sub-agent returned no messages","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/AgentTool.java","lineNumber":250,"sourceCode":"\t\t\tcatch (RuntimeException e) {\n\t\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\t\"sub-agent invocation failed: \" + e.getMessage(), e);\n\t\t\t}\n\n\t\t\tOptional<List> messages = resultState.flatMap(overAllState -> overAllState.value(\"messages\", List.class));\n\t\t\tif (messages.isPresent()) {\n\t\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\t\tList<Message> messageList = (List<Message>) messages.get();\n\t\t\t\tif (!messageList.isEmpty() && messageList.get(messageList.size() - 1) instanceof AssistantMessage assistantMessage) {\n\t\t\t\t\treturn assistantMessage;\n\t\t\t\t}\n\t\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\t\"sub-agent returned no assistant message. Last message type: \"\n\t\t\t\t\t\t\t\t+ (messageList.isEmpty() ? \"<empty>\" : messageList.get(messageList.size() - 1).getMessageType()),\n\t\t\t\t\t\tnull);\n\t\t\t}\n\t\t\t\n\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\"sub-agent returned no messages\", null);\n\t\t}\n\n\t\tprivate RuntimeException buildExecutionException(String actualInput, RunnableConfig parentConfig, String detail,\n\t\t\t\tThrowable cause) {\n\t\t\tString threadId = parentConfig != null ? parentConfig.threadId().orElse(\"<no-thread-id>\") : \"<standalone>\";\n\t\t\tString message = String.format(\"Failed to execute agent tool '%s' (parentThreadId=%s, input=%s): %s\",\n\t\t\t\t\tagent.name(), threadId, actualInput, detail);\n\t\t\treturn cause == null ? new RuntimeException(message) : new RuntimeException(message, cause);\n\t\t}\n\n\t\t/**\n\t\t * Extract the actual input value from the wrapped JSON structure.\n\t\t * The input is expected to be in the format: {\"input\": \"actual_value\"}\n\t\t * If the input is not a valid JSON object or doesn't contain \"input\" field,\n\t\t * the original input string is returned as-is.\n\t\t * \n\t\t * @param input the wrapped input JSON string","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/AgentTool.java#L232-L268","documentation":"Thrown by AgentTool.executeAgent when the sub-agent's returned OverAllState has no 'messages' value at all (Optional.empty from state.value(\"messages\", List.class)). The tool cannot extract any conversation output, so it throws 'sub-agent returned no messages'. This indicates the sub-agent ran (no exception) but its final state lacks the messages key entirely.","triggerScenarios":"Sub-agent graph's output key strategy does not write to 'messages'; the state was reset/overwritten by cloneState/updateState without the messages key strategy; sub-agent graph ends at a node that emits a different state key (e.g. a custom output key); messages value is null rather than an empty list.","commonSituations":"Custom sub-agent using a different state key name (e.g. 'output') instead of the framework-standard 'messages'; keyStrategies misconfigured so merge drops messages; sub-agent built via builder without the default state factory; resumed/persisted state missing messages after checkpoint restore.","solutions":["Ensure the sub-agent's state factory registers a key strategy for 'messages' (appending list strategy) so all nodes write conversation state there.","Have node handlers return Map.of(\"messages\", List.of(assistantMessage)) instead of custom keys, or map the custom key to 'messages' in the final node.","If using persistence, verify the checkpointed state actually contains 'messages' before resume; clear stale checkpoints for the derived threadId.","Log the sub-agent's final OverAllState keys (state.value(\"messages\")) standalone to confirm where output is being stored."],"exampleFix":"// before: node writes a custom key\nreturn Map.of(\"output\", assistantMessage);\n// after\nreturn Map.of(\"messages\", List.of(assistantMessage));","handlingStrategy":"validation","validationCode":"OverAllState out = reactAgent.invoke(Map.of(\"messages\", List.of(new UserMessage(\"ping\")))).orElse(null);\nif (out == null || out.value(\"messages\", List.class).isEmpty()) {\n    throw new IllegalStateException(\"Sub-agent state does not expose 'messages'; fix key strategies before tool registration\");\n}","typeGuard":"static boolean hasMessages(OverAllState state) {\n    return state != null && state.value(\"messages\", List.class).isPresent();\n}","tryCatchPattern":"try {\n    AssistantMessage reply = agentToolExecutor.executeAgent(input, toolContext);\n}\ncatch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"returned no messages\")) {\n        throw new IllegalStateException(\"Sub-agent \" + agentName + \" state missing 'messages' key — check key strategies\");\n    }\n    throw e;\n}","preventionTips":["Always use the standard 'messages' key with an append strategy in sub-agent state factories.","Map custom output keys to 'messages' in the terminal node before exposing the agent as a tool.","Smoke-test that the sub-agent's final state contains 'messages' during development.","Purge stale checkpoints for derived threadIds when changing state schemas."],"tags":["agent-framework","tool-execution","state-management","graph-state"],"backgroundTag":"empty-required-field","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}