{"record":{"id":"7b8d868f8007f920","repo":"conductor-oss/conductor","slug":"plan-execute-harness-config-getname-has-gua","errorCode":null,"errorMessage":"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}","messagePattern":"PLAN_EXECUTE harness '(.+?)' 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: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":500,"severity":"error","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java","lineNumber":2579,"sourceCode":"        // collapse to TERMINATE on the dynamic plan SUB_WORKFLOW; without a\n        // configured fallback, the whole pipeline just fails — the user\n        // probably intended adaptive recovery (which the fallback agent\n        // provides). Log-only — don't block compile, since \"fail loud on\n        // guardrail trip\" is also a valid choice.\n        if (fallbackConfig == null) {\n            List<String> offenders = new ArrayList<>();\n            for (ToolConfig t : parentTools) {\n                if (t.getGuardrails() == null) continue;\n                for (GuardrailConfig g : t.getGuardrails()) {\n                    String onFail = g.getOnFail();\n                    if (onFail != null && !\"raise\".equalsIgnoreCase(onFail)) {\n                        offenders.add(\n                                t.getName() + \":\" + g.getName() + \" (on_fail=\" + onFail + \")\");\n                    }\n                }\n            }\n            if (!offenders.isEmpty()) {\n                throw new IllegalStateException(\n                        \"PLAN_EXECUTE harness '\"\n                                + config.getName()\n                                + \"' has guardrails with on_fail=retry|fix|human but no fallback \"\n                                + \"agent. In plan mode these collapse to TERMINATE — the user-intended \"\n                                + \"retry-with-feedback semantics do not apply. Either configure a \"\n                                + \"``fallback=<Agent>`` on the harness, or set ``on_fail=raise`` on \"\n                                + \"these guardrails to acknowledge fail-closed semantics. Offenders: \"\n                                + String.join(\", \", offenders));\n            }\n        }\n        List<String> knownToolNames = new ArrayList<>();\n        for (ToolConfig t : parentTools) {\n            if (t.getName() != null && !t.getName().isEmpty()) {\n                knownToolNames.add(t.getName());\n            }\n        }\n        // Serialise the full ToolConfig list to Maps so PAC can deserialise\n        // them server-side and reach guardrail metadata at SUB_WORKFLOW","sourceCodeStart":2561,"sourceCodeEnd":2597,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java#L2561-L2597","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: guardrail defaults to on_fail=retry, no fallback\nAgentConfig.builder()\n    .strategy(Strategy.PLAN_EXECUTE)\n    .planner(planner)\n    .tools(List.of(ToolConfig.builder()\n        .name(\"search\")\n        .guardrails(List.of(GuardrailConfig.builder()\n            .name(\"safety_check\").build()))  // on_fail defaults to \"retry\"\n        .build()))\n    .build();\n// after: add fallback agent\nAgentConfig.builder()\n    .strategy(Strategy.PLAN_EXECUTE)\n    .planner(planner)\n    .fallback(fallbackAgent)\n    .tools(...)  // same tools\n    .build();\n// OR: set on_fail=raise to accept TERMINATE\nGuardrailConfig.builder().name(\"safety_check\").onFail(\"raise\").build();","handlingStrategy":"validation","validationCode":"void validatePlanExecuteGuardrails(AgentConfig config) {\n    if (config.getStrategy() != AgentConfig.Strategy.PLAN_EXECUTE) return;\n    boolean hasFallback = config.getFallback() != null;\n    List<String> offenders = new ArrayList<>();\n    if (config.getTools() != null) {\n        for (ToolConfig t : config.getTools()) {\n            if (t.getGuardrails() == null) continue;\n            for (GuardrailConfig g : t.getGuardrails()) {\n                String onFail = g.getOnFail() != null ? g.getOnFail() : \"retry\";\n                if (!hasFallback && !\"raise\".equalsIgnoreCase(onFail)) {\n                    offenders.add(t.getName() + \":\" + g.getName());\n                }\n            }\n        }\n    }\n    if (!offenders.isEmpty()) {\n        throw new IllegalStateException(\n            \"PLAN_EXECUTE guardrails need either fallback or on_fail=raise: \" + offenders);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    compiler.compile(agentConfig);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"on_fail=retry|fix|human but no fallback\")) {\n        // either add fallback=<Agent> or set on_fail=raise on listed guardrails\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["plan-execute","guardrails","fallback","config-validation","agentspan"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}