{"record":{"id":"57779cdeb919b9a8","repo":"conductor-oss/conductor","slug":"swarm-handoff-type-must-be-on-tool-result-on-text","errorCode":null,"errorMessage":"SWARM handoff type must be on_tool_result, on_text_mention, or on_condition","messagePattern":"SWARM handoff type must be on_tool_result, on_text_mention, or on_condition","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java","lineNumber":1489,"sourceCode":"                targets.put(tool.getName(), tool.getName().substring(index + marker.length()));\n        }\n        return targets;\n    }\n\n    /** Validate the declarative SWARM contract without changing its wire representation. */\n    private static void validateSwarmHandoffs(AgentConfig config) {\n        if (config.getHandoffs() == null) return;\n        Set<String> targets = new HashSet<>();\n        targets.add(config.getName());\n        if (config.getAgents() != null) {\n            for (AgentConfig agent : config.getAgents()) targets.add(agent.getName());\n        }\n        for (HandoffConfig handoff : config.getHandoffs()) {\n            if (handoff == null\n                    || handoff.getType() == null\n                    || !Set.of(\"on_tool_result\", \"on_text_mention\", \"on_condition\")\n                            .contains(handoff.getType())) {\n                throw new IllegalArgumentException(\n                        \"SWARM handoff type must be on_tool_result, on_text_mention, or on_condition\");\n            }\n            if (handoff.getTarget() == null || !targets.contains(handoff.getTarget())) {\n                throw new IllegalArgumentException(\n                        \"SWARM handoff target must name a swarm agent: \" + handoff.getTarget());\n            }\n            switch (handoff.getType()) {\n                case \"on_tool_result\" -> {\n                    if (isBlank(handoff.getToolName()) || isBlank(handoff.getResultContains())) {\n                        throw new IllegalArgumentException(\n                                \"on_tool_result requires toolName and resultContains\");\n                    }\n                }\n                case \"on_text_mention\" -> {\n                    if (isBlank(handoff.getText())) {\n                        throw new IllegalArgumentException(\"on_text_mention requires text\");\n                    }\n                }","sourceCodeStart":1471,"sourceCodeEnd":1507,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java#L1471-L1507","documentation":"Thrown during agent compilation when a SWARM-strategy agent's handoff entry has a 'type' that is not one of the three supported trigger types: 'on_tool_result', 'on_text_mention', or 'on_condition'. The validator iterates every HandoffConfig in config.getHandoffs() and also fires if the handoff object itself is null. This is a compile-time gate so misconfigured handoffs surface at deploy, not at runtime when the coordinator silently ignores them.","triggerScenarios":"An AgentConfig with strategy=SWARM whose handoffs list contains an entry whose type field is null, misspelled (e.g. 'on_tool_results', 'On_Tool_Result'), or uses an unsupported value. Also triggers when a null entry exists in the handoffs list.","commonSituations":"Typo in the JSON/Python config (plural 'on_tool_results' instead of singular), copy-pasting handoff type strings from OpenAI Agents SDK docs that use slightly different names, or omitting the type field entirely from a handoff definition. Common when handoffs are constructed programmatically and the type constant is wrong.","solutions":["Set the handoff's type to exactly 'on_tool_result', 'on_text_mention', or 'on_condition' (lowercase, exact match).","Ensure every entry in the handoffs list is non-null and has a non-null type field.","Remove any null or placeholder entries from the handoffs array."],"exampleFix":"// before\n{\"type\": \"on_tool_results\", \"target\": \"researcher\"}\n// after\n{\"type\": \"on_tool_result\", \"target\": \"researcher\", \"toolName\": \"search\", \"resultContains\": \"complete\"}","handlingStrategy":"validation","validationCode":"private static final Set<String> VALID_HANDOFF_TYPES =\n        Set.of(\"on_tool_result\", \"on_text_mention\", \"on_condition\");\n\nvoid validateHandoffTypes(List<HandoffConfig> handoffs) {\n    if (handoffs == null) return;\n    for (int i = 0; i < handoffs.size(); i++) {\n        HandoffConfig h = handoffs.get(i);\n        if (h == null || !VALID_HANDOFF_TYPES.contains(h.getType())) {\n            throw new IllegalArgumentException(\n                \"handoffs[\" + i + \"] has invalid type; expected one of \" + VALID_HANDOFF_TYPES);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    compiler.compile(agentConfig);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"handoff type must be\")) {\n        // fix the handoff type in config and recompile\n    }\n    throw e;\n}","preventionTips":["Define handoff type strings as constants or an enum instead of string literals.","Validate handoff configs in a unit test before deploying."],"tags":["swarm","handoff","config-validation","agentspan"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}