Welcome to Chapter 5, the final chapter of our journey!
In the previous chapter, Streaming Tool Executor, we built a system that can queue up tools and run them. But simply having a queue isn't enough. We need a Strategy.
If the AI gives us a list of 10 tasks, in what order should we run them? Can we run them all at once? Or must we run them one by one?
This chapter covers Tool Orchestration, the "Traffic Controller" that organizes a chaotic list of tool requests into an orderly, efficient workflow.
Imagine a multi-lane highway.
The Problem:
If the AI sends us: [Read File A, Read File B, Delete File A, Read File C].
The Solution: We need to group them into Batches:
Throughout this chapter, we will solve this specific scenario: The AI wants to: 1. List files, 2. Search for text (Grep), 3. Delete a file.
We want the first two (safe) to run fast and simultaneously, but the third (dangerous) to wait until the others are finished.
isConcurrencySafe)Every tool definition includes a rule:
ls, grep, read_file).write_file, bash, npm install).This is the algorithm that slices the long list of tools into "Safe Batches" and "Unsafe Batches."
The process of looping through these batches and executing them using the correct method (Parallel vs. Serial).
Let's visualize how the Orchestrator sorts the traffic.
The logic for this lives in toolOrchestration.ts. It acts as a high-level manager that uses the core execution logic we learned in Chapter 1: Tool Execution Pipeline.
This is the most important part of the strategy. We iterate through the tools and bundle "safe" ones together. As soon as we hit an "unsafe" tool, we close the current bundle and start a new one.
// Inside partitionToolCalls function
function partitionToolCalls(toolMessages, context) {
return toolMessages.reduce((batches, toolUse) => {
// 1. Check if the current tool is safe (e.g., "read_file" = true)
const isSafe = checkConcurrencySafety(toolUse, context);
// 2. Look at the last batch we were building
const lastBatch = batches[batches.length - 1];
// 3. If current is safe AND last batch was safe -> Add to pile!
if (isSafe && lastBatch?.isConcurrencySafe) {
lastBatch.blocks.push(toolUse);
} else {
// 4. Otherwise, start a brand new batch
batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] });
}
return batches;
}, []);
}
Explanation:
ls (Safe). Start Batch 1 (Safe).grep (Safe). Add to Batch 1.rm (Unsafe). Stop Batch 1. Start Batch 2 (Unsafe).[{ safe: true, blocks: [ls, grep] }, { safe: false, blocks: [rm] }].Now that we have our batches, we loop through them.
// Inside runTools function
export async function* runTools(toolUseMessages, ...) {
// 1. Get the partitions (batches)
const batches = partitionToolCalls(toolUseMessages, context);
// 2. Loop through each batch
for (const batch of batches) {
if (batch.isConcurrencySafe) {
// 3. If safe, run all tools in this batch at the same time
yield* runToolsConcurrently(batch.blocks, ...);
} else {
// 4. If unsafe, run them one-by-one (serially)
yield* runToolsSerially(batch.blocks, ...);
}
// 5. Update state before moving to the next batch
// (Ensure the "rm" finished before starting next batch)
}
}
Explanation: This ensures that Batch 1 finishes completely before Batch 2 starts. This effectively creates a "Barrier" between safe and unsafe operations.
To run tools side-by-side, we use Promise.all (or a generator equivalent).
// Inside runToolsConcurrently
async function* runToolsConcurrently(tools, ...) {
// Map every tool to a running task
const tasks = tools.map(async function* (tool) {
// Run the pipeline from Chapter 1
yield* runToolUse(tool, ...);
});
// Run them all! (simplified helper)
yield* all(tasks);
}
Explanation: We fire off all the requests in the batch instantly. The system processes them in parallel, significantly speeding up "read-heavy" workloads.
For unsafe tools, we keep it simple.
// Inside runToolsSerially
async function* runToolsSerially(tools, ...) {
for (const tool of tools) {
// Run one
yield* runToolUse(tool, ...);
// Wait for it to finish loop before starting the next one
}
}
Explanation: This is a standard loop. It waits for the first tool to return a result before even looking at the second tool.
There is one advanced detail. Some tools (like cd to change directory) change the Context for future tools.
cd /tmp is running in Batch 1, Batch 2 needs to know we are now in /tmp.newContext) and passes them forward to the next batch.// Inside the runTools loop
if (update.newContext) {
// Update the master context variable
currentContext = update.newContext;
}
// Pass currentContext to the next batch...
Congratulations! You have completed the Tools Project Tutorial.
We have traveled from the basic concept of a tool call all the way to a high-performance orchestration engine.
Recap of our journey:
You now understand the nervous system of the AI application. It is designed to be Safe (Permissions), Observable (Hooks), and Fast (Orchestration).
Happy Coding!
Generated by Code IQ