Welcome to the TaskOutputTool project!
In this tutorial series, we will explore how an AI agent retrieves the results of long-running background tasks.
Imagine you order a custom pizza. You get a receipt with an Order ID. You don't stand at the counter staring at the oven; you go sit down. Later, you use that Order ID to ask, "Is my pizza done?" or "What toppings were on that?"
The TaskOutputTool is the interface that allows the AI to do exactly that: take a Task ID and retrieve the "pizza" (the logs, errors, or final results).
When an AI agent runs a command (like a shell script or a sub-agent), it often happens in the background. The agent needs a specific tool to "check back in" on that process.
The TaskOutputTool acts as the API endpoint for task data. It defines:
Scenario: An agent starts a script called data_crunch.sh which runs for 30 seconds. The system gives it a Task ID: task-123.
Problem: The agent needs to see the final output of data_crunch.sh to know if it succeeded.
Solution: The agent calls TaskOutputTool with task_id="task-123".
Let's break down the definition of this tool into two simple parts: what goes in and what comes out.
To get information, the agent must provide specific details.
// From TaskOutputTool.tsx
const inputSchema = lazySchema(() => z.strictObject({
task_id: z.string().describe('The task ID to get output from'),
block: z.boolean().default(true).describe('Wait for completion?'),
timeout: z.number().default(30000).describe('Max wait time in ms')
}));
task_id: The "receipt number" for the background job.block:true (default): "Wait until it's done, then tell me."false: "Tell me what is happening right now, even if it's not finished."timeout: How long (in milliseconds) the agent is willing to wait.The tool returns a standardized object so the agent knows exactly where to look for errors or logs.
type TaskOutputToolOutput = {
// Did we get the data, or did we time out waiting?
retrieval_status: 'success' | 'timeout' | 'not_ready';
// The actual details of the job
task: TaskOutput | null;
};
The task object contains the juicy details: output (the text logs), status (e.g., "completed", "failed"), and exitCode.
Here is how an agent interacts with this definition in a real scenario.
Agent Input (JSON):
{
"task_id": "task-55a",
"block": true,
"timeout": 5000
}
What happens:
The tool receives this input. Because block is true, it pauses until task-55a finishes. Once finished, it returns the result.
Tool Output (Simplified):
{
"retrieval_status": "success",
"task": {
"task_id": "task-55a",
"status": "completed",
"output": "Data processing complete.\nRows affected: 50",
"exitCode": 0
}
}
How does the tool actually work under the hood? Let's look at the lifecycle of a request.
Let's look at the internal logic in TaskOutputTool.tsx.
First, we ensure the Task ID provided actually points to a real task in memory.
// Inside validateInput
const appState = getAppState();
const task = appState.tasks?.[task_id];
if (!task) {
return {
result: false,
message: `No task found with ID: ${task_id}`,
errorCode: 2
};
}
appState) and look up the task. If it's missing, we stop immediately.This is the core decision point. Do we return immediately, or do we wait?
// Inside call()
if (!block) {
// If we aren't waiting, return whatever we have right now
const isRunning = task.status === 'running';
return {
data: {
retrieval_status: isRunning ? 'not_ready' : 'success',
task: await getTaskOutputData(task)
}
};
}
block is false, we don't wait. We grab the data immediately using getTaskOutputData (which we will cover in detail in Unified Task Data Normalization).
If block is true, we enter the polling phase.
// Inside call() - Blocking mode
const completedTask = await waitForTaskCompletion(
task_id,
toolUseContext.getAppState,
timeout
);
Finally, we package the data.
if (!completedTask) {
return { data: { retrieval_status: 'timeout', task: null } };
}
return {
data: {
retrieval_status: 'success',
task: await getTaskOutputData(completedTask)
}
};
We have successfully defined the TaskOutputTool. It serves as a bridge between the agent and the background processes, accepting a "ticket" (Task ID) and returning the "order" (Output Logs).
However, you might be wondering: How exactly does the tool wait for the task without freezing the entire application?
We'll answer that in the next chapter.
Next Chapter: Task Completion Polling
Generated by Code IQ