conductor-oss/conductor · error · IllegalArgumentException
plan_source.tool '${toolName}' is not registered as a harnes
Error message
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). What it means
Thrown when a PLAN_EXECUTE agent's planSource.tool names a tool that is not registered at the harness (parent agent) level. The plan_reader task is emitted in the harness's task namespace, so the tool must be declared in the harness's own tools=[...] list — declaring it on a sub-agent does not work because the task runs in the parent's scope.
Source
Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/compiler/MultiAgentCompiler.java:2755
// 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();
planReaderTask.setName(toolName);
planReaderTask.setTaskReferenceName(planReaderRef);
planReaderTask.setType("SIMPLE");
View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Move the tool declaration to the harness-level tools=[...] list on the parent AgentConfig.
- Verify the tool name in planSource.tool exactly matches the name field of a tool in config.getTools().
- The tool does NOT need to be on the planner or any sub-agent — it must be on the harness.
Example fix
// before: tool declared on sub-agent, not harness
AgentConfig.builder()
.strategy(Strategy.PLAN_EXECUTE)
.planner(plannerAgent)
.planSource(Map.of("tool", "read_skill_file"))
// read_skill_file is only on plannerAgent.tools
.tools(List.of()) // harness has no tools!
.build();
// after: tool declared on harness
AgentConfig.builder()
.strategy(Strategy.PLAN_EXECUTE)
.planner(plannerAgent)
.planSource(Map.of("tool", "read_skill_file"))
.tools(List.of(ToolConfig.builder().name("read_skill_file").build()))
.build(); Defensive patterns
Strategy: validation
Validate before calling
void validatePlanSourceTool(AgentConfig config) {
Map<String, Object> ps = config.getPlanSource();
if (ps == null) return;
String toolName = (String) ps.get("tool");
if (toolName == null || toolName.isBlank()) return; // handled by [30]
Set<String> harnessTools = new HashSet<>();
if (config.getTools() != null) {
for (ToolConfig t : config.getTools()) harnessTools.add(t.getName());
}
if (!harnessTools.contains(toolName)) {
throw new IllegalArgumentException(
"plan_source.tool '" + toolName + "' not in harness tools: " + harnessTools);
}
} Try / catch
try {
compiler.compile(agentConfig);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("not registered as a harness-level tool")) {
// move the tool declaration from sub-agent to harness tools=[...]
}
throw e;
} Prevention
- plan_source.tool must be declared in the harness's own tools=[...] — not on any sub-agent.
- The plan_reader task runs in the harness namespace; sub-agent tools are not visible.
When it happens
Trigger: An AgentConfig with strategy=PLAN_EXECUTE where planSource.tool is a non-blank string that does not match any tool name in config.getTools() (the harness-level tools). The tool may exist on a sub-agent but that doesn't count.
Common situations: Declaring the plan-reading tool on a sub-agent (e.g., the planner) instead of on the harness itself, or using a tool name that exists in a different agent definition. The plan_reader task runs in the harness namespace, so only harness-level tools are visible.
Related errors
- plan_source must include a non-empty 'tool' field
- 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/dcafaf53657d5824.
Report an issue: GitHub.