Welcome to the ScheduleCronTool project!
In this first chapter, we are going to explore the core "skills" that allow an AI agent to handle time.
By default, an AI agent is reactive. It sits and waits for you to type a message. If you don't type anything, the AI does nothing.
But what if you want the AI to be proactive?
We call this the Cron Tool Suite.
Think of the AI agent as a smart assistant with a utility belt. By default, the belt is empty. We are going to add three gadgets (Tools) to this belt:
Let's look at how the Agent uses these tools to solve our use case.
CronCreateToolThis is the most important tool. When you tell the agent "Remind me to drink water every hour," the agent's brain decides to call this tool.
To use a tool, the agent must provide specific Input. We define this input using a "Schema" (a set of rules).
The agent must provide two main things:
0 * * * * means "at minute 0 of every hour").Here is a simplified look at the code defining these rules:
// CronCreateTool.ts
const inputSchema = lazySchema(() =>
z.strictObject({
// The time code (e.g. "*/5 * * * *")
cron: z.string().describe('Standard 5-field cron expression...'),
// What to do when the timer fires
prompt: z.string().describe('The prompt to enqueue at each fire time.'),
}),
)
Explanation: We use a library called zod (z) to strictly define that the input must contain a cron string and a prompt string.
When the agent calls this tool, we execute specific logic to save the task.
// CronCreateTool.ts
async call({ cron, prompt, recurring = true, durable = false }) {
// 1. Add the task to our scheduling system
const id = await addCronTask(cron, prompt, recurring, durable)
// 2. Turn on the scheduler loop so it starts checking time
setScheduledTasksEnabled(true)
// 3. Return the ID so the agent knows it worked
return { data: { id, humanSchedule: cronToHuman(cron) } }
}
Explanation: The tool takes the inputs, saves them via addCronTask (which writes to a list), and ensures the system is "awake" (setScheduledTasksEnabled).
CronListToolSometimes the user asks, "What reminders do I have set?" The agent needs a way to check its notebook.
This tool is very simple. It requires no input and simply returns the list of active jobs.
// CronListTool.ts
async call() {
// 1. Fetch all tasks from storage
const allTasks = await listAllCronTasks()
// 2. Filter tasks (agents only see their own tasks)
const ctx = getTeammateContext()
const tasks = ctx ? allTasks.filter(t => t.agentId === ctx.agentId) : allTasks
// 3. Return the list
return { data: { jobs: tasks } }
}
Explanation: We act as a gatekeeper. If the agent is a "teammate" (a sub-agent), they only see their own reminders. The main agent sees everything.
CronDeleteToolFinally, if the user says "Stop reminding me about water," the agent needs to remove the task.
The agent must provide the specific id of the job to delete.
// CronDeleteTool.ts
const inputSchema = lazySchema(() =>
z.strictObject({
id: z.string().describe('Job ID returned by CronCreate.'),
}),
)
Before deleting, we must check if the ID actually exists. We don't want the agent hallucinating IDs!
// CronDeleteTool.ts - inside validateInput
async validateInput(input): Promise<ValidationResult> {
const tasks = await listAllCronTasks()
const task = tasks.find(t => t.id === input.id)
if (!task) {
// If ID is not found, tell the agent "Error"
return { result: false, message: `No scheduled job with id '${input.id}'` }
}
return { result: true }
}
Explanation: We explicitly check if the task exists before trying to delete it. This prevents errors down the line.
Here is what happens under the hood when you ask the agent to schedule a task.
You might notice the code snippet isKairosCronEnabled() in the files. This is a "Feature Gate."
// CronCreateTool.ts
isEnabled() {
return isKairosCronEnabled()
}
This ensures that if we want to disable the scheduling capabilities globally (perhaps for maintenance or security), we can switch them off in one place, and the tools vanish from the agent's utility belt. We will discuss configuration in Feature Gating & Configuration.
In this chapter, we built the foundation of our time-based agent. We learned:
cron strings).
However, creating the tool is only half the battle. Once the timer fires, the agent needs to know what to say. The prompt stored in CronCreateTool isn't just static textβit can be dynamic.
In the next chapter, we will learn how the system constructs the message that wakes the agent up.
Next Chapter: Dynamic Prompt Construction
Generated by Code IQ