{"record":{"id":"93c4f288c168f553","repo":"conductor-oss/conductor","slug":"composite-termination-isand-and-or-mu","errorCode":null,"errorMessage":"Composite termination (${isAnd ? \"and\" : \"or\"}) must have at least one sub-condition","messagePattern":"Composite termination \\((.+?)\\) must have at least one sub-condition","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/TerminationCompiler.java","lineNumber":259,"sourceCode":"                        + \"  return {should_continue: !exceeded, reason: reason};\");\n    }\n\n    /**\n     * Build a composite (AND/OR) script by inlining each sub-condition's check.\n     *\n     * <p>For AND: all sub-conditions must signal termination (should_continue == false) for the\n     * composite to terminate.\n     *\n     * <p>For OR: any sub-condition signaling termination causes the composite to terminate.\n     *\n     * @param config the composite termination config\n     * @param isAnd true for AND semantics, false for OR\n     * @return a JavaScript IIFE string\n     */\n    private static String buildCompositeScript(TerminationConfig config, boolean isAnd) {\n        List<TerminationConfig> conditions = config.getConditions();\n        if (conditions == null || conditions.isEmpty()) {\n            throw new IllegalArgumentException(\n                    \"Composite termination (\"\n                            + (isAnd ? \"and\" : \"or\")\n                            + \") must have at least one sub-condition\");\n        }\n\n        StringBuilder body = new StringBuilder();\n        body.append(\"  var results = [];\");\n\n        for (int i = 0; i < conditions.size(); i++) {\n            TerminationConfig sub = conditions.get(i);\n            String subBody = buildSubConditionBody(sub, i);\n            body.append(subBody);\n            body.append(\"  results.push(r\").append(i).append(\");\");\n        }\n\n        if (isAnd) {\n            // AND: all must signal termination (should_continue == false) for composite to\n            // terminate","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/TerminationCompiler.java#L241-L277","documentation":"Thrown by buildCompositeScript() when an 'and' or 'or' composite termination config has a null or empty 'conditions' list. A composite termination must have at least one sub-condition to evaluate — without any, the AND/OR semantics are undefined (AND of nothing is vacuously true, OR of nothing is false, both meaningless for termination).","triggerScenarios":"A TerminationConfig with type='and' or type='or' where config.getConditions() is null or returns an empty list.","commonSituations":"Setting up a composite termination but forgetting to populate the conditions list, or clearing it during config manipulation. Also happens when conditions are loaded from a list that was expected to be non-empty but came back empty.","solutions":["Populate the conditions list with at least one TerminationConfig sub-condition.","Each sub-condition must itself have a valid type (text_mention, stop_message, max_message, token_usage, or nested and/or).","If you only need a single condition, use that condition directly instead of wrapping it in a composite."],"exampleFix":"// before\nTerminationConfig.builder()\n    .type(\"and\")\n    .conditions(List.of())  // empty!\n    .build();\n// after\nTerminationConfig.builder()\n    .type(\"and\")\n    .conditions(List.of(\n        TerminationConfig.builder().type(\"max_message\").maxMessages(20).build(),\n        TerminationConfig.builder().type(\"text_mention\").text(\"DONE\").build()))\n    .build();","handlingStrategy":"validation","validationCode":"void validateComposite(TerminationConfig config) {\n    if (\"and\".equals(config.getType()) || \"or\".equals(config.getType())) {\n        if (config.getConditions() == null || config.getConditions().isEmpty()) {\n            throw new IllegalArgumentException(\n                \"Composite termination (\" + config.getType() + \") needs >= 1 sub-condition\");\n        }\n    }\n}","typeGuard":"static boolean isValidComposite(TerminationConfig config) {\n    if (!Set.of(\"and\", \"or\").contains(config.getType())) return true;\n    return config.getConditions() != null && !config.getConditions().isEmpty();\n}","tryCatchPattern":"try {\n    String script = TerminationCompiler.buildTerminationScript(config);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"must have at least one sub-condition\")) {\n        // add sub-conditions to the composite's conditions list\n    }\n    throw e;\n}","preventionTips":["Always populate conditions when type is 'and' or 'or'.","For single conditions, don't wrap in a composite.","Validate composite termination configs in tests."],"tags":["termination","composite","config-validation","agentspan"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}