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

  1. Move the tool declaration to the harness-level tools=[...] list on the parent AgentConfig.
  2. Verify the tool name in planSource.tool exactly matches the name field of a tool in config.getTools().
  3. 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

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


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/dcafaf53657d5824. Report an issue: GitHub.