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

  1. Add a 'tool' key to the planSource map with a non-blank value naming the tool that reads the plan.
  2. Ensure the tool name matches a tool registered at the harness level (see error 31).
  3. 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

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


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