Module 9: Scheduling and Background Work
The shift from tool to autonomous assistant happens the moment your agent starts doing things without being asked.
This is Module 9 of a 12-part curriculum: Build Software Products with AI — From First Principles to Production Pipeline.
Everything we’ve covered so far has been reactive: a user sends a message, an agent responds. This is the chatbot model. It’s useful, but it’s the floor — not the ceiling.
The most powerful version of an agent is one that operates proactively. It checks your inbox before you ask. It processes meeting transcripts when they arrive. It backs up your work at midnight. It reminds you about the meeting in 20 minutes.
That shift — from reactive to proactive — is what this module is about.
Push vs Pull
There are two fundamental models for when an agent takes action.
Pull (reactive). The agent waits for input. User sends a message → agent processes it → agent responds. The user is always the initiator.
Push (proactive). The agent initiates action based on time, events, or conditions. A schedule fires → agent checks the inbox. A file appears in a directory → agent processes it. A threshold is crossed → agent sends an alert.
Most agent setups start with pull. The leap to push is what makes an agent feel like an assistant rather than a tool.
PULL (reactive) PUSH (proactive) ───────────────── ───────────────── human asks → agent responds schedule fires → agent acts no input → no work file appears → agent processes threshold hit → agent alertsexample: /ask command example: cron job at 09:00 runs once on request runs daily, no prompt needed heartbeat poll every 30 min event watcher on drop folder
feels like: a tool 🔧 feels like: an assistant 🤖
The architectural shift: pull agents wait. Push agents act.
Cron Jobs
Cron is the simplest form of scheduled execution. At a defined time or interval, run a task.
# Runs every weekday at 8am London time
0 8 * * 1-5 → run morning briefing agent
# Runs every day at 11pm
0 23 * * * → run action items cleanup
# Runs every Sunday at 8pm
0 20 * * 0 → run weekly content planning session
Cron jobs are suited for:
- Tasks with exact timing requirements (8:00 AM sharp, not “sometime in the morning”)
- Tasks that run independently of the main conversation
- Tasks that produce output delivered to a channel without main session involvement
- Long-running background work
Each cron job runs in its own isolated session. It has its own context, its own model selection, and its own output routing. It doesn’t inherit the state of your main session.
Design principle: cron jobs should be self-contained. A cron job that depends on state set in a previous conversation is fragile. It should read what it needs from files, do its work, and write its output.
Heartbeats
A heartbeat is a periodic check that runs within the main session. Rather than an isolated cron job, it’s a regular nudge: “while things are quiet, go check if there’s anything worth doing.”
Heartbeats are suited for:
- Batching multiple lightweight checks together (inbox + calendar + notifications in one turn)
- Tasks where you want the main session’s context available
- Checks where timing can drift slightly (every 30 minutes is fine, not exact)
- Proactive work the assistant can do during quiet periods
A HEARTBEAT.md file defines what to check on each heartbeat:
# HEARTBEAT.md
## Check for new meeting transcripts
Scan the drop folder for unprocessed transcript files.
If found, run the meeting-scribe pipeline.
## Triage shared inbox
Check Shared Inbox.md for unprocessed items.
Add brief descriptions. Note project relevance.
Mark processed items with ✅.
## Sync task backlog
Run sync-context-files.js. Report any status changes.
## Check backlog for ready items
If any backlog items in "ready" column, alert.
The agent reads this on each heartbeat and works through it. Small nudges — a few checks, a few updates — that compound into a system that feels alive and attentive.
One-Shot Reminders
Beyond scheduled jobs and heartbeats, there’s a simpler pattern: set a reminder for a specific moment.
“Remind me in 20 minutes about the call.” “Alert me at 3pm if the CI hasn’t passed yet.” “Check in tomorrow morning about whether I’ve sent that email.”
These are one-shot cron entries — they fire once and expire. In practice, they’re short-lived timer tasks that the main agent creates and monitors.
Implementation: write a cron entry to fire at the specified time, with a message to deliver to the user. The cron job fires, delivers the message, and is removed.
Event-Driven Triggers
Beyond time-based scheduling, agents can react to events: something happened, therefore act.
Common event patterns:
File system watchers. A new transcript file appears in the drop folder → process it. A new video lands in the render queue → start transcoding. A config file changes → reload and apply.
Webhook receivers. A GitHub webhook fires when a PR is opened → run automated review. A Stripe webhook fires when a payment succeeds → update user tier. A calendar event starts → send a prep brief.
Database triggers. A record is created → kick off a workflow. A status changes → notify the relevant agent.
Message patterns. A message contains a specific keyword or hashtag → route it. A message arrives in a specific channel → auto-process.
Event-driven triggers are more responsive than polling. Instead of checking “is there a new file?” every 5 minutes, you’re notified the moment a file arrives. For time-sensitive workflows, this matters.
State Tracking for Background Work
Background agents need to track their own state. Without it, they lose track of what they’ve processed, what they’ve skipped, and where they left off.
For heartbeat-driven checks, maintain a heartbeat-state.json:
{
"lastChecks": {
"email": 1748423400,
"calendar": 1748423400,
"transcripts": 1748420000
},
"processedFiles": [
"2026-05-09-standup.md",
"2026-05-08-roadmap-review.md"
],
"backlog": {
"inProgress": ["TASK-042", "TASK-047"]
}
}
The agent reads this state at the start of each check, uses it to decide what’s new, and writes back after processing. This prevents the same email being flagged twice, the same transcript being processed twice, or the same backlog item being picked up twice.
For cron jobs: maintain a processed log. A simple text file or JSON array of IDs that have been handled. On each run, check the log before processing anything.
The Overnight Agent
One of the most practical applications of background scheduling: agents that do work while you sleep.
My nightly schedule:
- 23:00 — Clean up resolved action items from the Obsidian vault
- 23:30 — Back up the workspace to GitHub
- 01:00 — Generate tomorrow’s daily note and priorities
By 8:00 AM when I open my laptop, these things are already done. The daily note is waiting. The workspace is backed up. Resolved tasks are cleaned up.
None of this requires intelligence — it requires reliable scheduling and clear task definitions. The value isn’t the individual tasks. It’s the accumulation of automated housekeeping that would otherwise drain small amounts of attention every day.
Proactive Communication
A proactive agent doesn’t just do tasks silently — it communicates when something warrants attention.
Rules for when to surface a proactive notification:
- An urgent email arrived that requires action
- A calendar event is within 2 hours
- A task moved to “blocked” and needs unblocking
- A CI build failed on a branch you care about
- Something genuinely new and relevant happened
Rules for when NOT to:
- It’s outside working hours (unless truly urgent)
- Nothing new has happened since the last check
- The check ran less than 30 minutes ago
- It’s a routine update that can wait until you check in
The goal is being attentive without being annoying. An assistant that pings you for every minor status update is worse than no assistant at all. Be selective. Surface the signal, suppress the noise.
Designing for Reliability
Background jobs fail. Network is unavailable. Files are malformed. APIs return errors. Build your background work with failure in mind.
Idempotency. Running the same job twice should produce the same result as running it once. If a transcript was already processed, processing it again should be a no-op, not a duplicate.
Error logging. When a background job fails, log it. Don’t fail silently. Write the error to a log file so you can diagnose it later.
Graceful degradation. If one check fails (e.g., email API is down), continue with the other checks. Don’t let one failure abort the whole heartbeat.
Alerting on repeated failures. If the same job fails three times in a row, surface it. Silent repeated failures are the worst kind.
What’s Next
Your agent can now act proactively — on a schedule, in response to events, and during quiet periods. In Module 10, we make it production-ready: failure handling, retry logic, fallback models, approval gates, and the security and safety architecture that makes a system you can trust.
Further Reading
- [Tool] Crontab Guru — Cron Expression Editor — The fastest way to write and verify cron expressions. Bookmark this.
- [Docs] Node-cron Documentation — If you’re building scheduling in a Node.js environment, node-cron is the simplest path.
- [Blog] Event-Driven Architecture — Martin Fowler — The clearest explanation of event-driven patterns and when to use them vs polling.
- [Docs] Chokidar — File Watching for Node.js — The standard library for file system event watching in Node. Used under the hood in many frameworks.
Referenced from @nikovijay
“This is my Mission Control: A Squad of 10 autonomous @openclaw agents… They create work on their own.” — @pbteja1998
Subscribe to get notified when new modules and courses drop. No drip — just updates when there's something worth reading.
Subscribe on Substack →