conductor-oss/conductor · error · IllegalArgumentException

Composite termination must have at least one sub-condition

Error message

Composite termination must have at least one sub-condition

What it means

Thrown by buildInlineComposite() when a nested composite (an 'and'/'or' inside another composite) has a null or empty conditions list. This is the same validation as error 34 but for the inline/nested compilation path. The compiler recurses into nested composites, and each level must have at least one sub-condition.

Source

Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/TerminationCompiler.java:413

                + ") { exceeded = true; reason = 'Prompt token limit exceeded'; }"
                + "    if ("
                + maxCompletion
                + " > 0 && (tokenUsed.completion_tokens || 0) > "
                + maxCompletion
                + ") { exceeded = true; reason = 'Completion token limit exceeded'; }"
                + "    return {should_continue: !exceeded, reason: reason};"
                + "  })();";
    }

    /**
     * Build an inline composite sub-condition. Recursively resolves nested composites. Uses a
     * unique prefix to avoid variable name collisions in deeply nested composites.
     */
    private static String buildInlineComposite(
            TerminationConfig config, String varName, boolean isAnd, int parentIndex) {
        List<TerminationConfig> conditions = config.getConditions();
        if (conditions == null || conditions.isEmpty()) {
            throw new IllegalArgumentException(
                    "Composite termination must have at least one sub-condition");
        }

        StringBuilder sb = new StringBuilder();
        sb.append("  var ").append(varName).append(" = (function() {");
        sb.append("    var results = [];");

        for (int i = 0; i < conditions.size(); i++) {
            String nestedVar = "s" + parentIndex + "_" + i;
            TerminationConfig sub = conditions.get(i);
            String nestedBody = buildNestedSubConditionBody(sub, nestedVar, parentIndex, i);
            sb.append(nestedBody);
            sb.append("    results.push(").append(nestedVar).append(");");
        }

        if (isAnd) {
            sb.append("    var allTerminate = true;");
            sb.append("    var reasons = [];");

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Find the nested composite (and/or) inside the conditions list that has no sub-conditions.
  2. Populate that nested composite's conditions with at least one TerminationConfig.
  3. If the nested composite is unnecessary, remove it from the parent's conditions list.

Example fix

// before: nested "or" has empty conditions
TerminationConfig.builder()
    .type("and")
    .conditions(List.of(
        TerminationConfig.builder().type("max_message").maxMessages(10).build(),
        TerminationConfig.builder().type("or").conditions(List.of()).build()))  // empty nested!
    .build();
// after
TerminationConfig.builder()
    .type("and")
    .conditions(List.of(
        TerminationConfig.builder().type("max_message").maxMessages(10).build(),
        TerminationConfig.builder().type("or").conditions(List.of(
            TerminationConfig.builder().type("text_mention").text("STOP").build(),
            TerminationConfig.builder().type("token_usage").maxTotalTokens(8000).build())).build()))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

void validateNestedComposites(TerminationConfig root) {
    validateComposite(root);
    if (root.getConditions() != null) {
        for (TerminationConfig sub : root.getConditions()) {
            if (Set.of("and", "or").contains(sub.getType())) {
                validateNestedComposites(sub);  // recurse
            }
        }
    }
}

Type guard

static boolean allNestedCompositesValid(TerminationConfig config) {
    if (Set.of("and", "or").contains(config.getType())) {
        if (config.getConditions() == null || config.getConditions().isEmpty()) return false;
        return config.getConditions().stream().allMatch(NestedValidator::allNestedCompositesValid);
    }
    return true;
}

Try / catch

try {
    String script = TerminationCompiler.buildTerminationScript(rootConfig);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Composite termination must have at least one")) {
        // find the empty nested composite and populate it or remove it
    }
    throw e;
}

Prevention

When it happens

Trigger: A composite TerminationConfig that contains a nested composite (type='and' or 'or') whose own conditions list is null or empty. For example, an 'and' containing an 'or' that has no conditions.

Common situations: Building deeply nested termination conditions and forgetting to populate an inner composite's conditions, or a nested composite whose conditions were removed during config refactoring but the composite entry was left behind.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/616815530fd46f0b. Report an issue: GitHub.