Scheduled Tasks Example
Build agents that run on a schedule to automate recurring tasks.
What We're Building
A set of scheduled agents that:
- Generate daily reports at 9 AM
- Monitor systems every 15 minutes
- Send weekly summaries on Friday afternoons
Use Cases
Scheduled agents are perfect for:
- Reporting: Daily/weekly summaries
- Monitoring: System health checks
- Data Processing: ETL pipelines
- Notifications: Digest emails
- Maintenance: Cleanup tasks
Basic Scheduled Agent
The simplest scheduled agent:
import { Agent, Model, Secret, Rate, SlackOutcome } from "@shuttl-io/core";
export const heartbeatAgent = new Agent({
name: "Heartbeat",
systemPrompt: "Report that the system is healthy.",
model: Model.openAI("gpt-4o-mini", Secret.fromEnv("OPENAI_API_KEY")),
tools: [],
triggers: [Rate.hours(1)],
outcomes: [new SlackOutcome("#monitoring")],
});
This agent runs every hour and posts a simple health check to Slack.
Daily Report Agent
A more sophisticated agent that generates daily reports:
Project Structure
daily-reporter/
├── src/
│ ├── agents/
│ │ └── reporter.ts
│ ├── tools/
│ │ ├── metrics.ts
│ │ └── database.ts
│ └── main.ts
├── shuttl.json
└── package.json
Metrics Tool
Create src/tools/metrics.ts:
import { Schema } from "@shuttl-io/core";
export const fetchMetricsTool = {
name: "fetch_metrics",
description: "Fetch business metrics for a date range",
schema: Schema.objectValue({
startDate: Schema.stringValue("Start date (YYYY-MM-DD)").isRequired(),
endDate: Schema.stringValue("End date (YYYY-MM-DD)").isRequired(),
metrics: Schema.stringValue("Comma-separated metric names"),
}),
execute: async ({ startDate, endDate, metrics }) => {
// In production, fetch from your data warehouse
// This is sample data for demonstration
const metricList = metrics?.split(",").map(m => m.trim()) || [
"revenue",
"users",
"orders",
"churn",
];
const results: Record<string, any> = {};
for (const metric of metricList) {
switch (metric) {
case "revenue":
results.revenue = {
value: 45230.50,
previousPeriod: 42100.00,
change: "+7.4%",
};
break;
case "users":
results.users = {
active: 1523,
new: 127,
churned: 23,
};
break;
case "orders":
results.orders = {
total: 342,
avgValue: 132.25,
fulfillmentRate: "98.5%",
};
break;
case "churn":
results.churn = {
rate: "1.5%",
atRisk: 45,
};
break;
}
}
return {
period: { startDate, endDate },
metrics: results,
};
},
};
export const fetchTopProductsTool = {
name: "fetch_top_products",
description: "Get the top-selling products",
schema: Schema.objectValue({
limit: Schema.numberValue("Number of products").defaultTo(5),
period: Schema.enumValue("Time period", ["day", "week", "month"]).defaultTo("day"),
}),
execute: async ({ limit, period }) => {
// Sample data
return {
period,
products: [
{ name: "Pro Plan Subscription", units: 45, revenue: 1305.00 },
{ name: "Enterprise Add-on", units: 12, revenue: 2388.00 },
{ name: "API Credits Pack", units: 89, revenue: 890.00 },
{ name: "Support Upgrade", units: 23, revenue: 575.00 },
{ name: "Training Session", units: 8, revenue: 1200.00 },
].slice(0, limit),
};
},
};
Reporter Agent
Create src/agents/reporter.ts:
import { Agent, Model, Secret, Rate, SlackOutcome } from "@shuttl-io/core";
import { fetchMetricsTool, fetchTopProductsTool } from "../tools/metrics";
const REPORT_PROMPT = `You are a business analyst generating daily reports.
Your task:
1. Fetch yesterday's metrics using fetch_metrics
2. Get top products using fetch_top_products
3. Generate a concise, insightful report
Report format:
📊 **Daily Report - [Date]**
**Key Metrics**
• Revenue: $X (↑/↓ Y% vs previous)
• Active Users: X (New: Y, Churned: Z)
• Orders: X (Avg: $Y)
**Top Products**
1. Product A - X units ($Y)
2. Product B - X units ($Y)
...
**Insights**
- Notable trends or anomalies
- Brief recommendations
Keep it concise but actionable.`;
function getYesterdayDate(): string {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday.toISOString().split("T")[0];
}
export const dailyReportAgent = new Agent({
name: "DailyReporter",
systemPrompt: REPORT_PROMPT,
model: Model.openAI("gpt-4", Secret.fromEnv("OPENAI_API_KEY")),
tools: [fetchMetricsTool, fetchTopProductsTool],
triggers: [
Rate.cron("0 9 * * MON-FRI", "America/New_York")
.withOnTrigger({
onTrigger: async () => {
const yesterday = getYesterdayDate();
return [{
typeName: "text",
text: `Generate the daily report for ${yesterday}.`,
}];
},
})
.bindOutcome(new SlackOutcome("#daily-reports")),
],
});
Monitoring Agent
An agent that checks system health regularly:
import { Agent, Model, Secret, Rate, SlackOutcome, Schema } from "@shuttl-io/core";
const checkHealthTool = {
name: "check_system_health",
description: "Check health of various system components",
schema: Schema.objectValue({
components: Schema.stringValue("Comma-separated component names"),
}),
execute: async ({ components }) => {
const componentList = components?.split(",").map(c => c.trim()) || [
"api",
"database",
"cache",
"queue",
];
const results: Record<string, any> = {};
for (const component of componentList) {
// In production, actually check each component
const isHealthy = Math.random() > 0.1; // 90% healthy for demo
results[component] = {
status: isHealthy ? "healthy" : "degraded",
latency: Math.floor(Math.random() * 100) + 20,
lastCheck: new Date().toISOString(),
};
}
const unhealthy = Object.entries(results)
.filter(([_, v]) => v.status !== "healthy")
.map(([k, _]) => k);
return {
overallStatus: unhealthy.length === 0 ? "healthy" : "degraded",
components: results,
issues: unhealthy,
};
},
};
const alertTool = {
name: "create_alert",
description: "Create an alert for unhealthy components",
schema: Schema.objectValue({
component: Schema.stringValue("Component name").isRequired(),
severity: Schema.enumValue("Alert severity", ["warning", "critical"]).isRequired(),
message: Schema.stringValue("Alert message").isRequired(),
}),
execute: async ({ component, severity, message }) => {
// In production, create a PagerDuty/OpsGenie alert
console.log(`ALERT [${severity}]: ${component} - ${message}`);
return { alertId: `ALT-${Date.now()}`, status: "created" };
},
};
export const monitoringAgent = new Agent({
name: "SystemMonitor",
systemPrompt: `You monitor system health.
Every check:
1. Check all component health
2. If any component is unhealthy, create an alert
3. Report status summary
Only report issues - if everything is healthy, just confirm briefly.`,
model: Model.openAI("gpt-4o-mini", Secret.fromEnv("OPENAI_API_KEY")),
tools: [checkHealthTool, alertTool],
triggers: [
Rate.minutes(15)
.withOnTrigger({
onTrigger: async () => [{
typeName: "text",
text: "Run a health check on all system components.",
}],
})
.bindOutcome(new SlackOutcome("#ops-monitoring")),
],
});
Weekly Summary Agent
Generate comprehensive weekly summaries:
import { Agent, Model, Secret, Rate, SlackOutcome, CombinationOutcome } from "@shuttl-io/core";
export const weeklySummaryAgent = new Agent({
name: "WeeklySummary",
systemPrompt: `Generate a comprehensive weekly business summary.
Include:
1. Week-over-week metric comparisons
2. Top achievements
3. Areas needing attention
4. Recommendations for next week
Make it suitable for executive review - concise but insightful.`,
model: Model.openAI("gpt-4", Secret.fromEnv("OPENAI_API_KEY")),
tools: [fetchMetricsTool, fetchTopProductsTool, fetchWeeklyGoalsTool],
triggers: [
Rate.cron("0 16 * * FRI", "America/New_York") // 4 PM on Fridays
.withOnTrigger({
onTrigger: async () => {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
return [{
typeName: "text",
text: `Generate the weekly summary for ${startDate.toISOString().split("T")[0]} to ${endDate.toISOString().split("T")[0]}.`,
}];
},
})
.bindOutcome(new CombinationOutcome([
new SlackOutcome("#weekly-updates"),
new SlackOutcome("#leadership"),
])),
],
});
Combining Multiple Schedules
One agent, multiple schedules with different behaviors:
export const flexibleAgent = new Agent({
name: "FlexBot",
systemPrompt: `You handle various scheduled tasks.
Respond based on the input you receive.`,
model: Model.openAI("gpt-4o-mini", Secret.fromEnv("OPENAI_API_KEY")),
tools: [fetchMetricsTool, checkHealthTool],
triggers: [
// Quick health check every 5 minutes
Rate.minutes(5)
.withOnTrigger({
onTrigger: async () => [{
typeName: "text",
text: "Quick health check - only report if there are issues.",
}],
})
.bindOutcome(new SlackOutcome("#alerts")),
// Detailed report every hour
Rate.hours(1)
.withOnTrigger({
onTrigger: async () => [{
typeName: "text",
text: "Generate hourly status report with key metrics.",
}],
})
.bindOutcome(new SlackOutcome("#hourly-status")),
// Still respond to API calls
new ApiTrigger().bindOutcome(new StreamingOutcome()),
],
});
Testing Scheduled Agents
Local Testing
You can't easily wait for cron triggers, so test the agent logic directly:
// test/reporter.test.ts
import { dailyReportAgent } from "../src/agents/reporter";
describe("DailyReportAgent", () => {
it("generates a report with metrics", async () => {
const toolCalls: string[] = [];
const mockStreamer = {
async receive(_, content) {
if (content.data?.typeName === "tool_call") {
toolCalls.push(content.data.toolCall.name);
}
},
};
await dailyReportAgent.invoke(
"Generate the daily report for 2025-01-02.",
undefined,
mockStreamer
);
expect(toolCalls).toContain("fetch_metrics");
});
});
Manual Trigger
Test using the TUI:
shuttl dev
- Select
DailyReporterfrom the Agent Select screen - Navigate to the Chat screen
- Type:
Generate the daily report for 2025-01-02.
Watch the Agent Debug screen to see the tool calls and report generation in real-time.
Best Practices
1. Use Appropriate Intervals
// ❌ Too frequent for expensive operations
Rate.seconds(30) // Don't run GPT-4 every 30 seconds!
// ✅ Reasonable intervals
Rate.minutes(15) // For monitoring
Rate.hours(1) // For metrics
Rate.days(1) // For reports
2. Handle Failures Gracefully
const robustTool = {
name: "fetch_data",
execute: async (args) => {
try {
return await fetchFromApi(args);
} catch (error) {
// Return error info rather than throwing
return {
error: true,
message: error.message,
fallback: "Using cached data from last successful run.",
};
}
},
};
3. Include Context in Triggers
Rate.hours(1).withOnTrigger({
onTrigger: async () => {
const currentHour = new Date().getHours();
const isBusinessHours = currentHour >= 9 && currentHour <= 17;
return [{
typeName: "text",
text: isBusinessHours
? "Generate detailed report - business hours."
: "Generate brief summary - after hours.",
}];
},
})
4. Use Different Outcomes for Different Urgencies
triggers: [
// Routine: post to general channel
Rate.hours(1).bindOutcome(new SlackOutcome("#monitoring")),
// Critical: alert on-call + create ticket
// (handled in tool logic based on severity)
]
Next Steps
- ☀️ Weather Agent - Conversational agent example
- ⚡ Triggers Guide - All trigger options
- 📤 Outcomes Guide - Routing responses