conductor-oss/conductor · error · IllegalArgumentException
PLAN_EXECUTE strategy requires ``planner=<Agent>`` on the pa
Error message
PLAN_EXECUTE strategy requires ``planner=<Agent>`` on the parent agent. The legacy ``agents=[planner, fallback]`` positional shape is no longer accepted — set the named slots ``planner=`` (required) and ``fallback=`` (optional) instead.
What it means
Thrown when a PLAN_EXECUTE-strategy agent is compiled without a 'planner' sub-agent configured. The compiler requires config.getPlanner() to be non-null — this replaces the old positional agents=[planner, fallback] shape that the Python SDK already rejected at construction time. The Java compiler mirrors that hard cut so both SDK and hand-crafted JSON callers get the same migration error.
Source
Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java:2544
// run fallback agent (agentic LLM, bounded turns).
//
// The JSON plan describes a DAG of operations. Each operation is either
// "static" (tool call with known args) or "generated" (LLM produces args).
// Static ops compile to SIMPLE tasks. Generated ops compile to
// LLM_CHAT_COMPLETE → INLINE(parse) → SIMPLE(apply) chains running in
// parallel within each step.
private WorkflowDef compilePlanExecute(AgentConfig config) {
// Named-slot resolution. PLAN_EXECUTE requires ``planner=``;
// ``fallback=`` is optional. The Python SDK rejects the legacy
// ``agents=[planner, fallback]`` positional shape at construction
// time (see Agent.__init__); we mirror that hard cut here so the
// Java SDK and any HTTP caller crafting JSON by hand fail with the
// same migration message instead of silently quasi-working.
AgentConfig plannerConfig = config.getPlanner();
AgentConfig fallbackConfig = config.getFallback();
if (plannerConfig == null) {
throw new IllegalArgumentException(
"PLAN_EXECUTE strategy requires ``planner=<Agent>`` on the parent agent. "
+ "The legacy ``agents=[planner, fallback]`` positional shape is no "
+ "longer accepted — set the named slots ``planner=`` (required) and "
+ "``fallback=`` (optional) instead.");
}
// Parent-level ``tools`` is the canonical plan-executable set. The
// planner is told which tools are available (so it can't hallucinate
// names), PAC validates ``op.tool`` names against this set, and PAC
// wraps each emitted SIMPLE task with the tool's input guardrails
// (if any). Empty/null degrades gracefully — no allowlist check, no
// guardrail wrapping; the recommended shape always sets tools.
List<ToolConfig> parentTools = config.getTools() != null ? config.getTools() : List.of();
// Warn when a tool's guardrail uses a non-RAISE on_fail and there's
// no fallback agent to recover. In plan mode, RETRY/FIX/HUMAN all
// collapse to TERMINATE on the dynamic plan SUB_WORKFLOW; without a
// configured fallback, the whole pipeline just fails — the userView on GitHub (pinned to cf7c3e4a8a)
Solutions
- Set the named 'planner' field on the parent AgentConfig to the agent that produces the JSON plan, e.g. planner=<AgentConfig>.
- If you also need a fallback agent, set the named 'fallback' field (optional) instead of relying on positional agents=[...].
- Remove the legacy positional agents=[planner, fallback] list and migrate to named slots.
Example fix
// before (legacy positional shape)
AgentConfig.builder()
.strategy(Strategy.PLAN_EXECUTE)
.agents(List.of(planner, fallback))
.build();
// after (named slots)
AgentConfig.builder()
.strategy(Strategy.PLAN_EXECUTE)
.planner(planner)
.fallback(fallback) // optional
.build(); Defensive patterns
Strategy: validation
Validate before calling
void validatePlanExecute(AgentConfig config) {
if (config.getStrategy() == AgentConfig.Strategy.PLAN_EXECUTE) {
if (config.getPlanner() == null) {
throw new IllegalArgumentException(
"PLAN_EXECUTE requires planner=<Agent> on the parent agent. "
+ "Set the named 'planner' field (required) and optionally 'fallback'.");
}
}
} Try / catch
try {
compiler.compile(agentConfig);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("PLAN_EXECUTE strategy requires")) {
// migrate from agents=[...] positional to named planner=/fallback= slots
}
throw e;
} Prevention
- Use named slots (planner=, fallback=) for PLAN_EXECUTE — the positional agents=[...] shape is deprecated.
- After upgrading the SDK, audit all PLAN_EXECUTE agent definitions for the planner field.
When it happens
Trigger: An AgentConfig with strategy=PLAN_EXECUTE where config.getPlanner() returns null. This happens when the planner slot was never set, or when the caller used the deprecated positional agents list instead of the named planner= field.
Common situations: Upgrading from an older API version that accepted agents=[planner, fallback] positionally, or writing PLAN_EXECUTE config by hand in JSON without knowing about the named planner= field. Also common when migrating from the Python SDK's older API.
Related errors
- PLAN_EXECUTE harness '${config.getName()}' has guardrails wi
- 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/70f27d60e25b2170.
Report an issue: GitHub.