conductor-oss/conductor · error · IllegalArgumentException

Composite termination (${isAnd ? "and" : "or"}) must have at

Error message

Composite termination (${isAnd ? "and" : "or"}) must have at least one sub-condition

What it means

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).

Source

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

                        + "  return {should_continue: !exceeded, reason: reason};");
    }

    /**
     * Build a composite (AND/OR) script by inlining each sub-condition's check.
     *
     * <p>For AND: all sub-conditions must signal termination (should_continue == false) for the
     * composite to terminate.
     *
     * <p>For OR: any sub-condition signaling termination causes the composite to terminate.
     *
     * @param config the composite termination config
     * @param isAnd true for AND semantics, false for OR
     * @return a JavaScript IIFE string
     */
    private static String buildCompositeScript(TerminationConfig config, boolean isAnd) {
        List<TerminationConfig> conditions = config.getConditions();
        if (conditions == null || conditions.isEmpty()) {
            throw new IllegalArgumentException(
                    "Composite termination ("
                            + (isAnd ? "and" : "or")
                            + ") must have at least one sub-condition");
        }

        StringBuilder body = new StringBuilder();
        body.append("  var results = [];");

        for (int i = 0; i < conditions.size(); i++) {
            TerminationConfig sub = conditions.get(i);
            String subBody = buildSubConditionBody(sub, i);
            body.append(subBody);
            body.append("  results.push(r").append(i).append(");");
        }

        if (isAnd) {
            // AND: all must signal termination (should_continue == false) for composite to
            // terminate

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Populate the conditions list with at least one TerminationConfig sub-condition.
  2. Each sub-condition must itself have a valid type (text_mention, stop_message, max_message, token_usage, or nested and/or).
  3. If you only need a single condition, use that condition directly instead of wrapping it in a composite.

Example fix

// before
TerminationConfig.builder()
    .type("and")
    .conditions(List.of())  // empty!
    .build();
// after
TerminationConfig.builder()
    .type("and")
    .conditions(List.of(
        TerminationConfig.builder().type("max_message").maxMessages(20).build(),
        TerminationConfig.builder().type("text_mention").text("DONE").build()))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

void validateComposite(TerminationConfig config) {
    if ("and".equals(config.getType()) || "or".equals(config.getType())) {
        if (config.getConditions() == null || config.getConditions().isEmpty()) {
            throw new IllegalArgumentException(
                "Composite termination (" + config.getType() + ") needs >= 1 sub-condition");
        }
    }
}

Type guard

static boolean isValidComposite(TerminationConfig config) {
    if (!Set.of("and", "or").contains(config.getType())) return true;
    return config.getConditions() != null && !config.getConditions().isEmpty();
}

Try / catch

try {
    String script = TerminationCompiler.buildTerminationScript(config);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("must have at least one sub-condition")) {
        // add sub-conditions to the composite's conditions list
    }
    throw e;
}

Prevention

When it happens

Trigger: A TerminationConfig with type='and' or type='or' where config.getConditions() is null or returns an empty list.

Common situations: 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.

Related errors


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