AI Agent Scheduling Patterns: Cron, Webhooks, and Event Chains
Mentiko Team
Agent chains need triggers. Something has to tell the chain "start now." The three fundamental trigger types -- cron schedules, webhooks, and event chains -- cover every scheduling pattern you'll encounter. The question isn't which one to use. It's which combination.
Cron says "run at this interval regardless of what's happening." Webhooks say "run when something happens externally." Event chains say "run when another chain finishes." Understanding when to use each one is the difference between automation that runs smoothly and automation that fires wrong, misses events, or loops forever.
Cron: periodic work on a schedule
Cron is for regular intervals. Daily reports, weekly audits, hourly data syncs. The work isn't triggered by an external event -- it's triggered by the clock.
In Mentiko, a cron schedule attaches directly to a chain:
{
"name": "daily-metrics-report",
"schedule": "0 9 * * 1-5",
"agents": [
{
"name": "data-collector",
"prompt": "Pull yesterday's metrics from the analytics API.",
"triggers": ["chain:start"],
"emits": ["data:collected"]
},
{
"name": "report-writer",
"prompt": "Write a summary report from the collected metrics. Highlight anomalies.",
"triggers": ["data:collected"],
"emits": ["report:ready"]
},
{
"name": "distributor",
"prompt": "Send the report to the #metrics Slack channel.",
"triggers": ["report:ready"],
"emits": ["chain:complete"]
}
]
}
0 9 * * 1-5 fires at 9 AM Monday through Friday. The chain runs, produces a report, distributes it. No human intervention needed.
When cron works well: The work is independent of external events. You want a report every morning whether or not anything happened. Cron is right when the trigger is "time passed" rather than "something happened."
When cron is wrong: Work that depends on unpredictable external state. Polling for new PRs every 5 minutes is wasteful. Use a webhook.
Cron gotchas: Overlapping runs. If your chain takes 45 minutes and cron fires every 30, you get overlaps. Set concurrency: 1 and the scheduler skips the next trigger if the previous run is still active.
Webhooks: reactive work on external events
Webhooks trigger work in response to external events. A PR opens, a payment lands, an alert fires. The external system sends an HTTP request to your chain's webhook URL, and the chain starts.
{
"name": "pr-review-pipeline",
"webhook": {
"path": "/hooks/pr-opened",
"secret": "${WEBHOOK_SECRET}",
"filter": "payload.action == 'opened'"
},
"agents": [
{
"name": "code-analyzer",
"prompt": "Analyze the PR diff for security issues, logic errors, and style violations.",
"triggers": ["chain:start"],
"emits": ["analysis:complete"]
},
{
"name": "review-poster",
"prompt": "Post the analysis as a PR review comment.",
"triggers": ["analysis:complete"],
"emits": ["chain:complete"]
}
]
}
The secret field validates that requests come from the expected source (GitHub signs payloads with the shared secret). filter narrows which payloads trigger the chain -- only opened events start it, not every PR event.
When webhooks work well: The work is a direct response to an external event. PR opened -> review it. Payment received -> provision access. Alert fired -> run diagnostics. Latency is minimal because the trigger is the event itself, not a poll.
When webhooks are wrong: The external system doesn't support webhooks. Some APIs only offer polling -- use a cron chain that polls and conditionally proceeds. Also wrong when you need guaranteed ordering, since webhooks can arrive out of order on retries.
Webhook gotchas: Deduplication. External systems retry delivery if they don't get a timely 200. Mentiko deduplicates by tracking X-Delivery-Id headers -- duplicates are silently dropped.
Event chains: cascading work across chains
Event chains trigger when another chain finishes. Chain A emits an event, Chain B triggers on it. This is how you compose larger workflows from independent chains.
{
"name": "content-distributor",
"agents": [
{
"name": "format-twitter",
"prompt": "Convert the blog post into a Twitter thread (max 5 tweets).",
"triggers": ["blog:published"],
"emits": ["twitter:ready"]
},
{
"name": "format-linkedin",
"prompt": "Convert the blog post into a LinkedIn article summary.",
"triggers": ["blog:published"],
"emits": ["linkedin:ready"]
},
{
"name": "scheduler",
"prompt": "Schedule the Twitter thread for 10 AM and the LinkedIn post for 2 PM.",
"triggers": ["twitter:ready", "linkedin:ready"],
"collect": 2,
"emits": ["chain:complete"]
}
]
}
No cron, no webhook. This chain triggers when the blog publishing pipeline emits blog:published. Two independent chains composing through events.
When event chains work well: Modular chains that compose into larger workflows. The blog pipeline doesn't need to know about social distribution. The distribution chain doesn't need to know how the blog was written. They connect through a single event.
When event chains are wrong: The downstream chain needs to start regardless of whether the upstream ran. If you want social distribution at 10 AM even without a new blog post, use cron. Event chains create a dependency -- if upstream never fires, downstream never runs.
Hybrid patterns
Real-world scheduling rarely uses a single trigger type. The best patterns combine them.
Cron starts, events handle branching. A daily chain collects data and emits different events based on findings. Downstream chains trigger on those events. Cron provides cadence, events provide routing.
[Cron: 0 9 * * *] -> data-collector
-> emits anomaly:detected -> alert-chain
-> emits metrics:normal -> report-chain
Webhook starts, cron retries. A webhook triggers the primary processing chain. A cron job runs hourly, checks for failed runs, and re-triggers them. The webhook handles the happy path. The cron handles recovery.
Event chains with timeout fallback. Chain B waits on Chain A's completion. If Chain A hangs, a cron job checks every 15 minutes for stalled runs and force-completes them with a timeout event. Chain B handles both normal completion and timeout.
Timezone handling
Cron schedules run in UTC by default. 0 9 * * * means 9 AM UTC -- 1 AM Pacific, 4 AM Eastern. If your report should arrive at local 9 AM, you need the offset.
Mentiko supports a timezone field:
{
"schedule": "0 9 * * 1-5",
"timezone": "America/Los_Angeles"
}
This fires at 9 AM Pacific, adjusting automatically for daylight saving time. Always use IANA timezone names (America/Los_Angeles, not PST) because abbreviations don't handle DST transitions correctly. PST is always UTC-8, but Los Angeles is UTC-7 during summer. The IANA name handles both.
Rate limiting
Webhook-triggered chains can fire rapidly during event bursts. A busy repo might send dozens of PR events during a merge train.
Set max_concurrent and rate_limit at the chain level:
{
"webhook": { "path": "/hooks/pr-opened" },
"max_concurrent": 3,
"rate_limit": "10/hour"
}
Excess triggers are queued, not dropped. When a running instance completes, the next queued trigger starts. This prevents resource exhaustion while ensuring every event eventually gets processed.
Failure recovery for scheduled chains
Scheduled chains fail. The API is down, the model returns garbage, the downstream service times out. What matters is what happens next.
For cron-triggered chains, the simplest recovery is the next scheduled run. If your daily report fails on Tuesday, Wednesday's run will succeed. This is fine for chains where missing one run isn't critical.
For chains where every run matters, configure retry:
{
"schedule": "0 9 * * 1-5",
"retry": {
"attempts": 3,
"backoff": "exponential",
"max_delay": "15m"
}
}
Three attempts with exponential backoff. If all fail, the run is marked failed and surfaces in monitoring.
For webhook-triggered chains, retry is even more important -- the external event won't repeat itself. A failed payment webhook doesn't resend. Configure retries aggressively and set up alerting for exhausted attempts.
Event-chain failures are trickiest because the downstream chain might wait for an event that never arrives. Use the timeout fallback pattern: a cron job monitoring pending chains and handling stalls.
Choosing your pattern
Periodic work -> cron. Reactive work -> webhook. Dependent work -> event trigger. Start there.
Most production chains end up with a primary trigger and a secondary recovery mechanism. Get the primary trigger working first. Add recovery after your first production failure -- you'll know exactly what kind of recovery you need once you've seen how the chain actually fails.
Ready to schedule your first chain? Start with a 5-minute walkthrough or explore chain design patterns for more complex workflows.
Get new posts in your inbox
No spam. Unsubscribe anytime.