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_WORKFLOWView on GitHub (pinned to cf7c3e4a8a)
Solutions
- Add a fallback=<Agent> to the harness config so failed guardrails can route to the fallback agent.
- Alternatively, set on_fail='raise' on every guardrail in the harness tools to acknowledge fail-closed (TERMINATE) semantics.
- 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
- For PLAN_EXECUTE harnesses, either always provide a fallback agent or explicitly set on_fail=raise on all guardrails.
- Remember GuardrailConfig.onFail defaults to 'retry' — it is not null.
- Audit guardrail configs when copying them between strategies.
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
- PLAN_EXECUTE strategy requires ``planner=<Agent>`` on the pa
- plan_source must include a non-empty 'tool' field
- plan_source.tool '${toolName}' is not registered as a harnes
- SWARM handoff type must be on_tool_result, on_text_mention,
- SWARM handoff target must name a swarm agent: ${handoff.getT
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/7b8d868f8007f920.
Report an issue: GitHub.