ruvnet/ruflo · error

task step requires config.agentId or…

Error message

task step ${step.stepId} requires config.agentId or workflow.variables.defaultAgentId

What it means

During workflow_run, every step of type 'task' resolves its executor as step.config.agentId || workflow.variables.defaultAgentId. If both are falsy the step throws before executeAgentTask is called, marking the step failed with this message. It is a workflow-definition validation error: the workflow JSON lacks the agent binding for that step.

Solutions

  1. Add config.agentId to the failing step (stepId is named in the message), pointing at an existing agent id.
  2. Or set workflow variables.defaultAgentId when creating/starting the run so all agent-less task steps inherit it.
  3. Validate the workflow definition before execution: every type:'task' step must have a non-empty config.agentId or the run must receive defaultAgentId.
  4. Check exact key names and casing — only 'agentId' (camelCase) under step.config is read.

Example fix

// before
{ steps: [{ stepId: 's1', type: 'task', name: 'summarize', config: { prompt: 'Summarize {{input}}' } }] }
// -> throws: task step s1 requires config.agentId or workflow.variables.defaultAgentId

// after
{ steps: [{ stepId: 's1', type: 'task', name: 'summarize', config: { agentId: 'summarizer-1', prompt: 'Summarize {{input}}' } }] }
Defensive patterns

Strategy: validation

Validate before calling

interface WorkflowStep { stepId: string; type: string; config?: Record<string, unknown> }
function validateTaskSteps(steps: WorkflowStep[], variables: Record<string, unknown>): string[] {
  const hasDefault = typeof variables.defaultAgentId === 'string' && variables.defaultAgentId;
  return steps
    .filter(s => s.type === 'task')
    .filter(s => !s.config?.agentId && !hasDefault)
    .map(s => `task step ${s.stepId} requires config.agentId or workflow.variables.defaultAgentId`);
}
const problems = validateTaskSteps(workflow.steps, variables);
if (problems.length) throw new Error(problems.join('; ')); // fail BEFORE workflow_run

Type guard

function taskStepIsBound(step: { type: string; config?: Record<string, unknown> }, variables: Record<string, unknown>): boolean {
  if (step.type !== 'task') return true;
  return Boolean(step.config?.agentId) || Boolean(variables.defaultAgentId);
}

Try / catch

try {
  return await callTool('workflow_run', { workflowId, variables });
} catch (e) {
  const m = /task step (\S+) requires config\.agentId/.exec(String((e as Error).message));
  if (m) return configError(`Workflow step '${m[1]}' has no agent binding — set config.agentId or pass defaultAgentId.`);
  throw e;
}

Prevention

When it happens

Trigger: Running a workflow whose task step has config without agentId while the run variables also lack defaultAgentId; passing variables: {} at start; a typo like config.agentid (case-sensitive) or nesting agentId one level too deep in the step config.

Common situations: Hand-written workflow JSON/YAML templates that omit per-step agent bindings; templates designed around defaultAgentId run without setting that variable; LLM-generated workflow definitions missing required fields; renaming config keys during migration from an older workflow schema.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/07b611ef28c44780. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/workflow-tools.ts:355

          workflow.status = 'failed';
          saveWorkflowStore(store);
          break;
        }

        const step = workflow.steps[i];
        step.status = 'running';
        step.startedAt = new Date().toISOString();
        const stepStart = Date.now();
        saveWorkflowStore(store);

        let stepEntry: typeof stepResults[number] = { stepId: step.stepId, type: step.type, status: 'running' };

        try {
          if (step.type === 'task') {
            const cfg = step.config as Record<string, unknown>;
            const agentId = (cfg.agentId as string) || (workflow.variables.defaultAgentId as string);
            const promptTpl = (cfg.prompt as string) || step.name;
            if (!agentId) throw new Error(`task step ${step.stepId} requires config.agentId or workflow.variables.defaultAgentId`);
            const prompt = interp(promptTpl);
            const result = await executeAgentTask({
              agentId,
              prompt,
              systemPrompt: cfg.systemPrompt ? interp(String(cfg.systemPrompt)) : undefined,
              maxTokens: cfg.maxTokens as number | undefined,
              temperature: cfg.temperature as number | undefined,
              timeoutMs: cfg.timeoutMs as number | undefined,
            });
            if (!result.success) throw new Error(result.error || 'agent_execute failed');
            step.result = result;
            workflow.variables[`${step.stepId}.output`] = result.output;
            workflow.variables.lastStepOutput = result.output;
            stepEntry = { stepId: step.stepId, type: 'task', status: 'completed', durationMs: result.durationMs, output: result.output };
          } else if (step.type === 'wait') {
            const cfg = step.config as Record<string, unknown>;
            const ms = Math.min(Math.max(0, (cfg.ms as number) || 0), 60000);
            await new Promise(r => setTimeout(r, ms));

View on GitHub (pinned to fa13ee4ad6)