Welcome back! In Chapter 1: Tool Definition Wrapper, we built the outer shell of our "Skill Cartridge"βthe TaskUpdateTool. We defined how the AI selects the tool and what inputs it needs (like taskId and status).
Now, we need to program the logic inside that cartridge. We can't just let the AI do whatever it wants; we need rules. This set of rules is called the Task Lifecycle Workflow.
Think of a task not as a digital text file, but as a physical product on a manufacturing assembly line.
You cannot teleport an item. You must follow the process. You can't ship an item (completed) that doesn't exist. You also can't work on an item that has been incinerated (deleted).
Before we move an item on the assembly line, we must check if it's actually there. If the AI tries to update Task #99, but Task #99 doesn't exist, our tool must stop immediately.
Here is how we handle this logic inside the call function:
// Inside the call() function
const existingTask = await getTask(taskListId, taskId)
// Stop if the task is missing
if (!existingTask) {
return {
data: {
success: false,
error: 'Task not found',
},
}
}
Explanation:
We use getTask to look for the item. If it returns null (nothing found), we return an error message. This prevents the code from crashing later.
In our workflow, deleted is unique. It isn't just a status label; it is an action. It removes the item from the universe entirely.
Because this is a destructive action, we check for it before doing normal updates.
if (status === 'deleted') {
// Permanently remove the task
const deleted = await deleteTask(taskListId, taskId)
return {
data: {
success: deleted,
taskId,
updatedFields: ['deleted'], // Tell AI it's gone
}
}
}
Explanation:
If the AI sends status: 'deleted', we don't update the task fileβwe delete it using deleteTask. We then return immediately because there is nothing left to update.
If we aren't deleting the task, we are likely moving it along the assembly line (e.g., from pending to in_progress).
We prepare an update package. We only change fields that the AI explicitly asked to change.
const updates = {} // Empty container for changes
// If AI provided a status, and it's different from current
if (status !== undefined && status !== existingTask.status) {
updates.status = status
}
// If AI provided a new owner (the worker)
if (owner !== undefined && owner !== existingTask.owner) {
updates.owner = owner
}
Explanation:
We create an updates object. We compare the new value against the existing value. If they are the same (e.g., changing a task to in_progress when it is already in_progress), we do nothing. This keeps our database operations efficient.
Once we have calculated all the changes (status, owner, title, etc.), we apply them to the database.
// Only call the database if there are actual changes
if (Object.keys(updates).length > 0) {
await updateTask(taskListId, taskId, updates)
}
return {
data: {
success: true,
updatedFields: Object.keys(updates)
}
}
Explanation:
updateTask is the function that actually writes to the file system. We then return a success message telling the AI exactly which fields were modified.
Let's visualize the decision-making process the TaskUpdateTool performs every time it runs.
In a factory, when an item reaches the "Shipping Crate" (completed), it might trigger other eventsβlike printing a shipping label or notifying the customer.
In our code, we have similar "Side Effects."
If a task is moved to in_progress but nobody claims it, the system automatically assigns it to the AI running the tool.
// If working, but no owner set...
if (status === 'in_progress' && !owner && !existingTask.owner) {
// ...assign it to the current agent
updates.owner = getAgentName()
}
When a task is completed, we might need to notify other agents who were waiting for it. This logic is handled by "Hooks," which we will cover in detail in Chapter 5: Agent Collaboration & Hooks.
In this chapter, we learned:
pending -> in_progress -> completed.
However, simply passing a string like "completed" isn't enough safety. What if the AI sends a status of "finished" (which isn't valid)? Or sends a number instead of a string?
In the next chapter, we will learn how to strictly enforce these data types using schemas.
Next Chapter: Lazy Schema Validation
Generated by Code IQ