conductor-oss/conductor · error · IllegalStateException

PLAN_EXECUTE harness '${config.getName()}' has guardrails wi

Error message

PLAN_EXECUTE harness '${config.getName()}' has guardrails with on_fail=retry|fix|human but no fallback agent. In plan mode these collapse to TERMINATE — the user-intended retry-with-feedback semantics do not apply. Either configure a ``fallback=<Agent>`` on the harness, or set ``on_fail=raise`` on these guardrails to acknowledge fail-closed semantics. Offenders: ${offenders}

What it means

Thrown as IllegalStateException when a PLAN_EXECUTE harness has tool guardrails with on_fail set to retry, fix, or human but no fallback agent configured. In plan mode these on_fail actions semantically collapse to TERMINATE because the plan-execution loop can't do retry-with-feedback the way a normal agent loop does. The compiler rejects this as a likely misconfiguration — the user probably intended retry semantics.

Source

Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java:2579

        // collapse to TERMINATE on the dynamic plan SUB_WORKFLOW; without a
        // configured fallback, the whole pipeline just fails — the user
        // probably intended adaptive recovery (which the fallback agent
        // provides). Log-only — don't block compile, since "fail loud on
        // guardrail trip" is also a valid choice.
        if (fallbackConfig == null) {
            List<String> offenders = new ArrayList<>();
            for (ToolConfig t : parentTools) {
                if (t.getGuardrails() == null) continue;
                for (GuardrailConfig g : t.getGuardrails()) {
                    String onFail = g.getOnFail();
                    if (onFail != null && !"raise".equalsIgnoreCase(onFail)) {
                        offenders.add(
                                t.getName() + ":" + g.getName() + " (on_fail=" + onFail + ")");
                    }
                }
            }
            if (!offenders.isEmpty()) {
                throw new IllegalStateException(
                        "PLAN_EXECUTE harness '"
                                + config.getName()
                                + "' has guardrails with on_fail=retry|fix|human but no fallback "
                                + "agent. In plan mode these collapse to TERMINATE — the user-intended "
                                + "retry-with-feedback semantics do not apply. Either configure a "
                                + "``fallback=<Agent>`` on the harness, or set ``on_fail=raise`` on "
                                + "these guardrails to acknowledge fail-closed semantics. Offenders: "
                                + String.join(", ", offenders));
            }
        }
        List<String> knownToolNames = new ArrayList<>();
        for (ToolConfig t : parentTools) {
            if (t.getName() != null && !t.getName().isEmpty()) {
                knownToolNames.add(t.getName());
            }
        }
        // Serialise the full ToolConfig list to Maps so PAC can deserialise
        // them server-side and reach guardrail metadata at SUB_WORKFLOW

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Add a fallback=<Agent> to the harness config so failed guardrails can route to the fallback agent.
  2. Alternatively, set on_fail='raise' on every guardrail in the harness tools to acknowledge fail-closed (TERMINATE) semantics.
  3. Review each offending tool:guardrail pair listed in the error message to decide the right fix per guardrail.

Example fix

// before: guardrail defaults to on_fail=retry, no fallback
AgentConfig.builder()
    .strategy(Strategy.PLAN_EXECUTE)
    .planner(planner)
    .tools(List.of(ToolConfig.builder()
        .name("search")
        .guardrails(List.of(GuardrailConfig.builder()
            .name("safety_check").build()))  // on_fail defaults to "retry"
        .build()))
    .build();
// after: add fallback agent
AgentConfig.builder()
    .strategy(Strategy.PLAN_EXECUTE)
    .planner(planner)
    .fallback(fallbackAgent)
    .tools(...)  // same tools
    .build();
// OR: set on_fail=raise to accept TERMINATE
GuardrailConfig.builder().name("safety_check").onFail("raise").build();
Defensive patterns

Strategy: validation

Validate before calling

void validatePlanExecuteGuardrails(AgentConfig config) {
    if (config.getStrategy() != AgentConfig.Strategy.PLAN_EXECUTE) return;
    boolean hasFallback = config.getFallback() != null;
    List<String> offenders = new ArrayList<>();
    if (config.getTools() != null) {
        for (ToolConfig t : config.getTools()) {
            if (t.getGuardrails() == null) continue;
            for (GuardrailConfig g : t.getGuardrails()) {
                String onFail = g.getOnFail() != null ? g.getOnFail() : "retry";
                if (!hasFallback && !"raise".equalsIgnoreCase(onFail)) {
                    offenders.add(t.getName() + ":" + g.getName());
                }
            }
        }
    }
    if (!offenders.isEmpty()) {
        throw new IllegalStateException(
            "PLAN_EXECUTE guardrails need either fallback or on_fail=raise: " + offenders);
    }
}

Try / catch

try {
    compiler.compile(agentConfig);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("on_fail=retry|fix|human but no fallback")) {
        // either add fallback=<Agent> or set on_fail=raise on listed guardrails
    }
    throw e;
}

Prevention

When it happens

Trigger: An AgentConfig with strategy=PLAN_EXECUTE, one or more tools in config.getTools() whose guardrails have on_fail != 'raise' (i.e., retry/fix/human), and config.getFallback() is null. The error message lists each offending tool:guardrail pair.

Common situations: Copying guardrail configuration from a HANDOFF or SEQUENTIAL agent (where on_fail=retry works) into a PLAN_EXECUTE harness without adding a fallback agent. Also happens when guardrail on_fail defaults to 'retry' (its default value in GuardrailConfig) and the user doesn't realize it's incompatible with plan mode without a fallback.

Related errors


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