Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Orchestrating Sequential Agent Tasks

Introduction

In our previous lesson, we built the dynamic routing mechanism for our orchestrator, enabling it to dispatch subtasks to the correct specialist agents using an agent registry. However, our execution logic was simplified: it processed each task sequentially, ignoring the crucial dependencies field in our plan. This means it couldn't handle workflows where one task, like summarize_report, must wait for another, like fetch_report, to complete first.

This lesson directly addresses that gap. We will explore how to manage and execute tasks in a specific sequence based on their dependencies. You will learn two primary patterns for creating these "chains" of operations and then integrate this logic into our orchestrator's execution engine. By the end of this lesson, our orchestrator will be able to reliably execute a plan with sequential dependencies, a fundamental capability for any serious agentic system.

From Single-Step to Multi-Step Execution

A core concept to grasp is that by default, an LLM call that results in a tool invocation is a single, complete step. The model identifies a tool to call, generates the arguments, and the generation process concludes. To build a sequence, we need a way to take the result of that tool call and feed it back to the model for another round of reasoning, creating a loop.

This is essential for tasks that require a chain of thought and action, such as:

  1. Listing the files in a directory.
  2. Reading the content of a specific file from that list.
  3. Summarizing the content of that file.

Each step depends on the successful completion of the one before it. The following video from Vercel provides an excellent demonstration of this problem and the solution within the Vercel AI SDK.

Multi-Step (Agentic) Generation with the AI SDK

This video, "Multi-Step (Agentic) Generation with the AI SDK," clearly illustrates the need for multi-step execution.

First, watch the section that demonstrates the problem without multi-step logic. Notice how the agent calls the weather tool but fails to generate a final text response. Next, watch how the maxSteps parameter (now superseded by maxToolRoundtrips in the latest SDK, but conceptually identical) solves this by enabling a sequential chain. The SDK automatically pipes the tool's result back to the model, allowing it to complete the user's request.

Pattern 1: Automated Chaining within a Single Agent

As the video demonstrates, the Vercel AI SDK provides a powerful, automated way to handle simple sequential tool calls within a single agent. By setting the maxToolRoundtrips option in generateText or streamText, you instruct the SDK to automatically loop, feeding tool results back to the model up to a specified number of times.

This is ideal for self-contained tasks where one agent needs to use several of its tools in sequence to arrive at an answer. For example, a user asks, "What is the weather in San Francisco and what should I do there today?"

The agent could be equipped with two tools:

  • getWeather(location: string)
  • getAttractions(location: string, weather: string)

With maxToolRoundtrips enabled, the AI SDK would manage this flow automatically:

  1. Step 1: The model calls getWeather({ location: 'San Francisco' }).
  2. Loop: The SDK executes the tool, gets the weather data, and sends it back to the model along with the original prompt.
  3. Step 2: The model now has the weather and calls getAttractions({ location: 'San Francisco', weather: 'Sunny' }).
  4. Loop: The SDK executes this second tool and sends the result back.
  5. Step 3: The model, now having all the necessary information, generates the final text response for the user.

The image below from Vercel's documentation visualizes a similar multi-step flow where the model first calls a weather tool and then pipes the result into a convertTemperature tool before generating the final response.

A visual representation of a multi-step agent workflow. The flow `weather (x2) -> convertTemperature (x2) -> Response` clearly shows how the output of one tool call becomes the input for the next, demonstrating a sequential, dependent task chain.

This automated approach is incredibly convenient for building capable, tool-using agents. The Vercel Ship workshop video below offers another perspective on this, discussing a slightly older but conceptually similar mechanism (stopWhen) to achieve the same kind of looping behavior.

Vercel Ship 2025 workshop: Building agents with the AI SDK (Nico Albanese)

This workshop, "Building agents with the AI SDK," dives deep into agent construction. The concepts discussed here are foundational to understanding controlled execution loops.

Watch the segment explaining why a single step is insufficient and how a looping mechanism is required. The speaker introduces stopWhen, which, like maxToolRoundtrips, provides a declarative way to manage the execution loop. Then, see how this enables a list files -> read file sequence, a classic example of a dependent task chain.

Pattern 2: Manual Orchestration of Sequential Tasks

While maxToolRoundtrips is excellent for intra-agent sequences, our orchestrator needs to manage a workflow across multiple, distinct agents. For this, a more explicit, manual approach is better suited. Here, we implement the sequence by chaining async calls in our own code, giving us full control over the state and logic between steps.

This pattern is the bedrock of reliable, complex workflows. The official Vercel AI SDK documentation provides a clear example.

Workflow Patterns - Agents

This documentation page from the Vercel AI SDK team outlines several key agentic patterns. We will focus on the most fundamental one.

Read the section on Sequential Processing. Analyze the code example for generating marketing copy. Notice how it uses await to ensure each generateText call completes before the next one begins, passing the output of one step as the input to the next. This manual chaining gives the developer precise control over the workflow.

This manual pattern is powerful because it's explicit. You can inspect intermediate results, add conditional logic, implement custom error handling, and log each step of the process. Given your experience with backend development, this pattern will feel very natural, as it mirrors standard asynchronous programming practices.

The article "AI Agent Workflow Patterns" further elaborates on when and why to use this foundational pattern.

AI Agent Workflow Patterns: Building Reliable Multi-Step AI Systems

This article from Acceli provides production-level insights into building agent workflows.

Focus on the sections When to Use Sequential Workflows, the Implementation Pattern for customer feedback, and the short discussion on Error Handling. These sections reinforce the value of explicit, sequential execution for creating reliable and debuggable systems.

Implementing the Orchestrator's Execution Engine

Now, let's apply the manual orchestration pattern to our executePlan function. Our goal is to process the list of subtasks generated in the previous lesson, respecting the dependencies array of each task.

We will implement a loop that continues until all tasks are executed. In each pass, it attempts to run any tasks whose dependencies have already been met. This approach is a simplified form of a topological sort, which is a standard algorithm for processing directed acyclic graphs (like our task plan).

Here is the updated logic for our executePlan function:

// Assume 'plan', 'agentRegistry', and 'Subtask' types are defined as before.

async function executePlan(plan: { plan: Subtask[] }) {
  const taskResults = new Map<string, any>();
  const tasksToRun = [...plan.plan];
  let executedTaskCount = 0;
  const maxLoops = tasksToRun.length; // Failsafe to prevent infinite loops

  for (let loop = 0; loop < maxLoops && executedTaskCount < tasksToRun.length; loop++) {
    let tasksExecutedInThisLoop = 0;

    for (const task of tasksToRun) {
      // Skip if already executed
      if (taskResults.has(task.id)) {
        continue;
      }

      // Check if all dependencies for the current task have been met
      const dependenciesMet = task.dependencies.every(depId => taskResults.has(depId));

      if (dependenciesMet) {
        console.log(`Dependencies met for task ${task.id}. Executing...`);

        // Gather the results of the dependency tasks to pass as context
        const context = task.dependencies.reduce((acc, depId) => {
          acc[depId] = taskResults.get(depId);
          return acc;
        }, {} as Record<string, any>);

        // Route to the correct agent and execute the task
        const agentFunction = agentRegistry.get(task.agent);
        if (!agentFunction) {
          throw new Error(`Agent "${task.agent}" for task "${task.id}" not found in registry.`);
        }

        const result = await agentFunction(task.task, context);
        
        // Store the result
        taskResults.set(task.id, result);
        
        executedTaskCount++;
        tasksExecutedInThisLoop++;
        console.log(`Task ${task.id} executed successfully.`);
      }
    }

    // If a full loop completes with no tasks executed, there must be a cyclic dependency
    if (tasksExecutedInThisLoop === 0) {
      const remainingTasks = tasksToRun.filter(t => !taskResults.has(t.id));
      console.error("Deadlock detected. Remaining tasks:", remainingTasks);
      throw new Error("Could not execute all tasks. Check for cyclic or missing dependencies.");
    }
  }

  return Object.fromEntries(taskResults);
}

This implementation brings our orchestrator to life. It can now take a structured plan and execute it step-by-step, ensuring that information flows correctly from one task to the next.

Conclusion

In this lesson, you've mastered the critical skill of managing sequential, dependent tasks within an agentic system. You now have a robust execution engine for your orchestrator.

Key Takeaways:

  • Sequential Execution is Essential: Many complex tasks are composed of smaller, dependent steps that must be executed in a specific order.
  • Two Core Patterns:
    1. Automated Chaining (maxToolRoundtrips): Best for simple, linear tool sequences within a single agent. The Vercel AI SDK handles the looping automatically.
    2. Manual Orchestration (async/await): The preferred pattern for an orchestrator managing tasks across multiple agents. It offers full control, explicit logic, and better debugging.
  • Orchestrator Logic: By implementing a dependency-aware execution loop, our orchestrator can now correctly process a task plan, passing the output of one agent's work as the input to another.

Our orchestrator can now handle sequences. But what about tasks that don't depend on each other? Running them one by one is inefficient. In our next lesson, we will upgrade our execution engine to implement parallel execution of independent agent tasks, significantly improving the performance and throughput of our system.

Can't find a good explanation? Sign up and we'll make it for you

Sign up