conductor-oss/conductor · error · IllegalArgumentException
plan_source must include a non-empty 'tool' field
Error message
plan_source must include a non-empty 'tool' field
What it means
Thrown when a PLAN_EXECUTE agent's planSource config (config.getPlanSource()) exists but its 'tool' field is null or blank. The planSource defines a fallback mechanism: if the planner's text output fails plan extraction, a SIMPLE task calls the named tool to read the plan from an external source (e.g., contextbook). Without a valid tool name, the plan_reader task can't be emitted.
Source
Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java:2751
String plannerResult = AgentCompiler.coercedRef(plannerCoerceRef);
// ── 2b. Optional plan_source: deterministic tool call to read plan ──
// If planSource is configured, call the specified tool (e.g. contextbook_read)
// to retrieve the plan from an external source. This provides a deterministic
// fallback: even if the planner's text output fails extraction, the plan can
// be read directly from where the explorer wrote it.
//
// Validate at compile time that ``planSource.tool`` is a real tool registered
// somewhere in the harness — a typo is silently swallowed if we wait until
// runtime (the ``optional:true`` task simply doesn't run, extraction falls
// through to the no_plan branch). Reject the harness here so the misconfig
// surfaces at deploy.
String planReaderRef = null;
if (config.getPlanSource() != null) {
Map<String, Object> planSource = config.getPlanSource();
String toolName = (String) planSource.get("tool");
if (toolName == null || toolName.isBlank()) {
throw new IllegalArgumentException(
"plan_source must include a non-empty 'tool' field");
}
if (!isToolRegisteredInHarness(config, toolName)) {
throw new IllegalArgumentException(
"plan_source.tool '"
+ toolName
+ "' is not registered as a harness-level tool on '"
+ config.getName()
+ "'. The plan_reader task is emitted in the harness's task "
+ "namespace, so the tool must be declared in tools=[...] on the harness itself "
+ "(declaring it on a sub-agent does not work).");
}
@SuppressWarnings("unchecked")
Map<String, Object> toolArgs =
(Map<String, Object>) planSource.getOrDefault("args", Map.of());
planReaderRef = prefix + "_plan_reader";
WorkflowTask planReaderTask = new WorkflowTask();View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Add a 'tool' key to the planSource map with a non-blank value naming the tool that reads the plan.
- Ensure the tool name matches a tool registered at the harness level (see error 31).
- If you don't need a plan source, remove the planSource config entirely (null planSource is valid).
Example fix
// before
planSource = {"args": {"doc": "plan.md"}}
// after
planSource = {"tool": "read_skill_file", "args": {"doc": "plan.md"}} Defensive patterns
Strategy: validation
Validate before calling
void validatePlanSource(AgentConfig config) {
Map<String, Object> ps = config.getPlanSource();
if (ps != null) {
String tool = (String) ps.get("tool");
if (tool == null || tool.isBlank()) {
throw new IllegalArgumentException("plan_source must include a non-empty 'tool' field");
}
}
} Try / catch
try {
compiler.compile(agentConfig);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("plan_source must include")) {
// add "tool": "<toolName>" to the planSource map
}
throw e;
} Prevention
- If planSource is set, always include the 'tool' key.
- If you don't need a deterministic plan source, omit planSource entirely.
When it happens
Trigger: An AgentConfig with strategy=PLAN_EXECUTE where planSource is a non-null Map but either lacks a 'tool' key or the value of 'tool' is null/blank. For example planSource={"args": {...}} without a "tool" entry.
Common situations: Configuring planSource with only 'args' but forgetting the 'tool' key, or setting tool to an empty string. Also happens when planSource is constructed from a template that has a placeholder for the tool name that was never filled in.
Related errors
- plan_source.tool '${toolName}' is not registered as a harnes
- PLAN_EXECUTE strategy requires ``planner=<Agent>`` on the pa
- PLAN_EXECUTE harness '${config.getName()}' has guardrails wi
- 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/c76b541945075b07.
Report an issue: GitHub.