# Scheduled Tasks

> Schedule recurring tasks and heartbeat monitoring using Claude Code's cron system (CronCreate/CronList/CronDelete). Implements the Agentic Heartbeat Pattern for ecosystem health monitoring.

- Skill: `oimiragieo/scheduled-tasks` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add oimiragieo/scheduled-tasks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/oimiragieo/scheduled-tasks/raw
- Safety review: pending (external: skill-scanner PASS, skillspector WARNING)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: oimiragieo (https://skillmd.com/u/oimiragieo)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/oimiragieo/scheduled-tasks

---


# Scheduled Tasks & Heartbeat Skill

**Official docs:** <https://code.claude.com/docs/en/scheduled-tasks>

## Primary Interface: /loop Slash Command

The primary user-facing way to invoke scheduled tasks is via the `/loop` command. This is the
recommended interface for most users.

### Syntax

```
/loop <interval> <prompt>         # interval-based
/loop every 5m <prompt>           # explicit "every" syntax
/loop 2h <prompt>                 # shorthand hours
/loop at 8:00am <prompt>          # time-based (daily at local time)
```

### Common Intervals

```
2m, 5m, 10m, 15m, 30m            # minutes
1h, 2h, 4h, 6h, 12h              # hours
at 8:00am, at 3:00pm             # daily time triggers (local timezone)
```

### Examples

```
/loop 5m Check for new Telegram messages and reply
/loop 2h Distill recent session learnings into .claude/context/memory/learnings.md
/loop at 8:00am Read issues.md and CHANGELOG.md and give morning briefing
/loop every 4h Run pnpm search:code "indexing" to refresh codebase index
/loop 30m Heartbeat: read agent-health.json, check memory sizes, TaskList for stuck tasks
```

### Chaining: Run a Slash Command on a Schedule

```
/loop 20m /review-pr 1234         # run a slash command on a schedule
/loop 2h /heartbeat-start         # restart heartbeat ecosystem every 2 hours
```

### Key Behaviors

| Behavior               | Detail                                                                           |
| ---------------------- | -------------------------------------------------------------------------------- |
| **Jitter**             | Tasks fire up to 10% of their period late (max 15 min) — avoids stampedes        |
| **No catch-up**        | Missed fires are NOT replayed after sleep/hibernation                            |
| **Session-scoped**     | Loops stop automatically when the Claude Code session ends                       |
| **Local timezone**     | `at HH:MMam/pm` triggers use local time, not UTC                                 |
| **Max 50 loops**       | Hard cap of 50 concurrent scheduled tasks per session                            |
| **3-day expiry**       | All loops silently self-delete 3 days after creation (reschedule before day 2.5) |
| **Fire-between-turns** | Tasks fire only when Claude is idle, not mid-response                            |

---

## Heartbeat OS Pattern

Use `/loop` to build a self-improving agent ecosystem that runs continuously in the background:

### Continuous Reflection (every 2h)

```
/loop 2h Read .claude/context/memory/issues.md and recent session context. Extract patterns, workarounds, and decisions. Append to learnings.md and decisions.md.
```

### Autonomous Evolution (daily at 3am)

```
/loop at 3:00am Review learnings.md and test failures. Use agent-updater skill to improve agent .md files. Run pnpm validate:full before finalizing.
```

### Morning Briefing (weekdays at 8am)

```
/loop at 8:00am Read CHANGELOG.md and issues.md. Summarize technical debt, flaky tests, and top 2 tasks for today.
```

### Context Drain (every 15m)

```
/loop 15m Check if all tasks are done. If TaskList shows zero in_progress tasks, summarize session to memory and surface readiness for /clear to user.
```

### Codebase Indexing (every 4h)

```
/loop 4h Run pnpm code:index:reindex to refresh the BM25 and semantic search index.
```

### Start the Full Heartbeat Ecosystem

Use `/heartbeat-start` to launch all 7 loops in one command. See `.claude/commands/heartbeat-start.md`.

---

## Overview

Provides patterns for scheduling recurring tasks using Claude Code's built-in cron system (`CronCreate`/`CronList`/`CronDelete`). Also implements the Agentic Heartbeat Pattern for keeping the agent ecosystem healthy.

**Core distinction:**

- **Cron tasks**: "Run this SPECIFIC task at 3am Tuesday" — deterministic, time-anchored maintenance
- **Heartbeat**: "Check in periodically; act IF something needs attention" — awareness-based, conditional liveness

## Decision Framework: When to Use Cron

### Quick Decision Checklist (run through before calling CronCreate)

1. **Is the task recurring?** If NO → use `TaskCreate` instead (cron is for recurring work only).
2. **Can it tolerate up to 15-minute jitter?** If NO → use OS cron or GitHub Actions (CronCreate has built-in jitter).
3. **Is it acceptable to lose it on terminal close?** If NO → use OS cron (`crontab -e`) or GitHub Actions for persistence.
4. **Will it complete within the session?** If the session may end before the task fires → prefer OS-level scheduling.
5. **Is it for monitoring/health checks?** If YES → CronCreate is the right tool (this is its primary use case).
6. **Are there 10+ other cron tasks already scheduled?** If YES → reconsider; stay under 10 concurrent tasks (50-cap headroom).

### Decision Tree

```
Need a recurring task?
├── NO → Use TaskCreate (one-shot) or Task()
└── YES
    ├── Must survive terminal close?
    │   ├── YES → Use OS cron (crontab -e) or GitHub Actions
    │   └── NO (session-scoped is fine)
    │       ├── Time-critical (< 15 min precision required)?
    │       │   ├── YES → Use OS cron or GitHub Actions
    │       │   └── NO → CronCreate is appropriate ✓
    │       └── Is it monitoring/heartbeat?
    │           └── YES → CronCreate is ideal ✓
```

### When to Use CronCreate

- Session-scoped heartbeat monitoring (agent health, memory sizes, stuck tasks)
- Periodic index rebuilds during an active work session
- Scheduled memory rotation checks while actively developing
- Auto-reschedule tasks that keep other cron tasks alive
- Morning briefings and context-drain checks during sessions

### When to Use OS Cron / GitHub Actions Instead

- Tasks that must run even when Claude Code is not open
- Production scheduled jobs (backups, deployments, data pipelines)
- Tasks requiring sub-minute precision or hard time guarantees
- Any task where silent expiry (3-day limit) is unacceptable

### When NOT to Use CronCreate

- One-time tasks (use `Task` or `TaskCreate`)
- Tasks triggered by events rather than time (use hooks or event listeners)
- Tasks requiring external system persistence across Claude restarts
- Time-critical alerts where 15-minute jitter is unacceptable

### Session-Scope Context Note

> **CRITICAL — Session-Scoped Loops:** All `CronCreate` loops are registered to the current Claude Code session and are permanently lost when the session ends (terminal close, `/clear`, or session restart). After any session restart, all loops must be re-registered manually.
>
> **heartbeat-orchestrator handles this automatically.** When the heartbeat ecosystem is running, `heartbeat-orchestrator` is responsible for detecting missing loops after session restarts and re-registering them. If you are not using `heartbeat-orchestrator`, you must re-register all loops yourself at the start of each new session.
>
> To check which loops are active: `CronList()`. If loops are missing after a restart, re-run the setup commands or invoke `/heartbeat-start`.

## Quick Reference

```javascript
// Schedule a recurring task
CronCreate({ schedule: '*/30 * * * *', task: 'heartbeat check prompt' });

// List active cron tasks
CronList();

// Remove a scheduled task
CronDelete({ id: 'abc12345' });
```

## Constraints (CRITICAL — Read Before Using)

| Constraint             | Detail                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| **Session-scoped**     | All cron tasks die when the terminal closes — no persistence across restarts                |
| **50-task cap**        | Maximum 50 concurrent scheduled tasks per session                                           |
| **3-day auto-expiry**  | Recurring tasks self-delete 3 days after creation (SILENT — must reschedule before day 2.5) |
| **Fire-between-turns** | Tasks fire only when Claude is idle, not mid-response                                       |
| **No catch-up**        | Missed fires are NOT replayed — task fires once at next idle opportunity                    |
| **Jitter**             | Tasks fire up to 10% of their period late (max 15 min) to avoid API stampedes               |
| **Timezone**           | All cron expressions use local system timezone, NOT UTC                                     |
| **No extended syntax** | `L`, `W`, `?`, name aliases like `MON` are NOT supported                                    |

## Cron Expression Reference

Standard 5-field syntax: `minute hour day-of-month month day-of-week`

| Expression     | Meaning                  |
| -------------- | ------------------------ |
| `*/30 * * * *` | Every 30 minutes         |
| `*/5 * * * *`  | Every 5 minutes          |
| `0 * * * *`    | Every hour on the hour   |
| `0 2 * * *`    | Every day at 2am local   |
| `0 3 * * 0`    | Every Sunday at 3am      |
| `0 6 * * 1-5`  | Weekdays at 6am          |
| `0 0 */2 * *`  | Every 2 days at midnight |

## Heartbeat Pattern

### Design Philosophy (OpenClaw-inspired)

Cheap file reads FIRST, LLM only when needed. Achieve 60-80% cost reduction for healthy-state ticks.

**Execution model:**

1. Heartbeat fires at scheduled interval
2. Read `HEARTBEAT.md` checklist (cheap file read — no LLM call)
3. If blank/headers-only → emit `HEARTBEAT_OK` immediately, no LLM invocation
4. Read cheap signals (file sizes, `agent-health.json`, task list) — still no LLM
5. If ALL signals green → emit `HEARTBEAT_OK`
6. Only if signals indicate issues → invoke LLM with full HEARTBEAT.md context

### Heartbeat Checklist

Check these signals on each heartbeat tick (in order — cheapest first):

1. **Agent health**: Read `agent-health.json` — any agents `status: degraded`?
2. **Memory sizes**: Is `learnings.md` > 40KB? Is `decisions.md` > 80KB?
3. **Stuck tasks**: `TaskList()` — any tasks in `in_progress` for > 2 hours?
4. **Hook errors**: Any hooks with `errorRate > 0.05` in `agent-health.json`?
5. **Reflection queue**: Does `reflection-reminder.txt` exist and is stalled?
6. **BM25 freshness**: Is index mtime > 24 hours?

If ALL healthy → return `HEARTBEAT_OK` (no further LLM invocation needed)
If ANY unhealthy → spawn appropriate agent to fix, then report

### HEARTBEAT.md Template

Create `HEARTBEAT.md` in the project root as standing instructions for the heartbeat:

```markdown
# Agent Studio Heartbeat Checklist

## Every Tick (30 minutes)

### Memory Health

- [ ] learnings.md > 40KB? → trigger rotation via node .claude/lib/memory/memory-rotator.cjs
- [ ] decisions.md > 80KB? → warn user
- [ ] issues.md has unresolved P0 items? → surface to user

### Agent Registry Health

- [ ] agent-health.json has any status: degraded? → alert
- [ ] Any hooks have errorRate > 5%? → alert with hook name

### Task Health

- [ ] Any tasks stuck in in_progress for > 2 hours? → surface task IDs

### Reflection Queue

- [ ] reflection-reminder.txt exists? → alert (reflection queue stalled)

## Self-Maintenance

- [ ] This heartbeat task older than 2.5 days? → reschedule before 3-day expiry
```

## Scheduled Maintenance Patterns

### Index Rebuild (nightly)

```javascript
CronCreate({
  schedule: '0 2 * * *',
  task: 'Rebuild BM25 search index for code discovery. Run: cd /project && pnpm code:index:reindex',
});
```

### Memory Rotation Check (weekly)

```javascript
CronCreate({
  schedule: '0 3 * * 0',
  task: 'Check memory file sizes and rotate if needed. Run node .claude/lib/memory/memory-rotator.cjs and report results.',
});
```

### Agent Registry Refresh (daily)

```javascript
CronCreate({
  schedule: '0 6 * * *',
  task: 'Regenerate agent registry: pnpm agents:registry and verify 72 agents are registered.',
});
```

### Heartbeat (every 30 minutes)

```javascript
CronCreate({
  schedule: '*/30 * * * *',
  task: 'Run heartbeat check: Read HEARTBEAT.md checklist, check agent-health.json, memory file sizes, and stuck tasks. Reply HEARTBEAT_OK if all healthy, otherwise describe issues found.',
});
```

### Auto-Reschedule (every 2 days — prevents 3-day silent expiry)

```javascript
CronCreate({
  schedule: '0 0 */2 * *',
  task: 'Re-register all scheduled tasks to prevent 3-day auto-expiry. Call CronList() to check what tasks exist, then recreate any that are missing or older than 2.5 days.',
});
```

### Reschedule a Specific Task (correct order — no scheduling gap)

When replacing a running cron task with updated settings, ALWAYS use CronCreate FIRST, then CronDelete.
This prevents a scheduling gap where no cron task exists between delete and create.

```javascript
// CORRECT ORDER: Create new before deleting old to prevent scheduling gap

// Step 1: Create the new task FIRST (new task is now running)
CronCreate({
  schedule: '*/30 * * * *',
  task: 'Updated heartbeat prompt with new checks.',
});

// Step 2: THEN delete the old task (old ID obtained from CronList())
// There is never a window where no heartbeat cron exists
CronDelete({ id: 'old-task-id-from-cronlist' });

// WRONG ORDER (creates a scheduling gap — do NOT do this):
// CronDelete({ id: "old-id" });   // <-- gap begins here
// CronCreate({ ... });             // <-- gap ends here (tasks missed during gap)
```

### Weekly Validation (every Sunday)

```javascript
CronCreate({
  schedule: '0 3 * * 0',
  task: 'Run full framework validation: pnpm validate:full and report any errors found.',
});
```

## Integration Points

| Infrastructure       | Integration Method                                      | Heartbeat Action                     |
| -------------------- | ------------------------------------------------------- | ------------------------------------ |
| `agent-health.json`  | `Read` tool → JSON parse                                | Check `status`, `errorRate` per hook |
| `learnings.md`       | `Bash: wc -c` or Read + check size                      | Alert if > 40KB threshold            |
| `decisions.md`       | `Bash: wc -c` or Read + check size                      | Alert if > 80KB threshold            |
| Task system          | `TaskList()`                                            | Surface stuck in_progress tasks      |
| Memory rotator       | `Bash: node .claude/lib/memory/memory-rotator.cjs`      | Trigger when threshold exceeded      |
| BM25 index           | Check index mtime                                       | Rebuild if mtime > 24h               |
| `pnpm validate:full` | `Bash`                                                  | Weekly validation, surface errors    |
| Reflection queue     | `Read: .claude/context/runtime/reflection-reminder.txt` | Alert if reflection queue stalled    |

## Cost & Performance Controls

| Control                   | Purpose                             | Recommendation            |
| ------------------------- | ----------------------------------- | ------------------------- |
| Cheap checks first        | Read files before invoking LLM      | Always check files first  |
| `HEARTBEAT_OK` early exit | No LLM when all healthy             | Saves 60-80% API costs    |
| haiku model               | Cheaper model for status-only runs  | Use haiku for heartbeats  |
| `lightContext`            | Only HEARTBEAT.md, not full session | Reduces context per tick  |
| 30m default interval      | OpenClaw proven default             | Don't go below 15 minutes |

## Anti-Patterns

- Do NOT use cron for one-time tasks (use `Task`/`TaskCreate` instead)
- Do NOT schedule more than 10 concurrent cron tasks (leave room for user tasks in the 50-cap)
- Do NOT mix heartbeat and maintenance in the same cron task — keep them separate
- ALWAYS include auto-reschedule task to prevent silent 3-day expiry
- Do NOT rely on heartbeat for time-critical alerts — jitter means up to 15 min delay
- Do NOT set interval below 5 minutes — creates API stampede risk
- NEVER use extended cron syntax (`L`, `W`, `?`, named days) — not supported

## Usage Examples

### Start full heartbeat monitoring

```javascript
Skill({ skill: 'scheduled-tasks' });

// 1. Heartbeat every 30 minutes
CronCreate({
  schedule: '*/30 * * * *',
  task: 'Heartbeat: Read .claude/context/memory/agent-health.json, check learnings.md size, run TaskList() for stuck tasks. Reply HEARTBEAT_OK if all healthy.',
});

// 2. Auto-reschedule every 2 days (prevents 3-day expiry)
CronCreate({
  schedule: '0 0 */2 * *',
  task: 'Self-maintenance: CronList() to check active tasks, recreate any missing scheduled tasks.',
});

// 3. Nightly index rebuild
CronCreate({
  schedule: '0 2 * * *',
  task: 'Rebuild search index: pnpm code:index:reindex',
});
```

### Check scheduled task health

```javascript
CronList(); // Verify all expected tasks are registered
// If missing, re-register (3-day expiry may have cleared them silently)
```

### Cancel a specific task

```javascript
CronList(); // Get task IDs
CronDelete({ id: 'abc12345' }); // Cancel by ID
```

## Durable Alternatives (When Session-Scope Is Not Enough)

For tasks that must survive terminal close:

- **Desktop scheduled tasks** (OS-level): survives terminal close, graphical setup
- **GitHub Actions `schedule` trigger**: fully unattended, cloud-run, no terminal required
- **OS cron (`crontab -e`)**: persistent, runs independently of Claude Code

Use Claude Code's `CronCreate` for session-scoped monitoring only. Use OS-level scheduling for critical production tasks.

## Iron Laws

1. **ALWAYS** include an auto-reschedule cron task when setting up heartbeats — the 3-day silent expiry will kill the heartbeat without warning
2. **NEVER** use cron for tasks requiring time-critical precision — jitter up to 10% of period (max 15 min) makes timing non-deterministic
3. **ALWAYS** use cheap file reads before LLM invocation in heartbeat prompts — 60-80% cost reduction for healthy systems
4. **NEVER** exceed 10 concurrent scheduled tasks — leave headroom in the 50-task session cap for user-initiated tasks
5. **ALWAYS** document that cron tasks are session-scoped and will not survive terminal close — this surprises users who expect persistence

## Anti-Patterns

| Anti-Pattern                   | Why It Fails                          | Correct Approach                                |
| ------------------------------ | ------------------------------------- | ----------------------------------------------- |
| No auto-reschedule task        | 3-day expiry silently kills heartbeat | Add `0 0 */2 * *` reschedule cron               |
| Heartbeat interval < 5 minutes | API stampede risk, burns token budget | Use 30 min default; minimum 5 min               |
| LLM invocation on every tick   | 100% API cost with no savings         | Cheap checks first, LLM only when needed        |
| Mixing heartbeat + maintenance | One failure breaks both               | Separate cron tasks per concern                 |
| Extended cron syntax           | Not supported — silent failure        | Use standard 5-field syntax only                |
| Assuming session persistence   | Tasks die on terminal close           | Document limitation; use OS cron for durability |

## Memory Protocol (MANDATORY)

**Before starting:**
Read `.claude/context/memory/learnings.md`

**After completing:**

- New scheduling pattern → `.claude/context/memory/learnings.md`
- Issue found → `.claude/context/memory/issues.md`
- Decision made → `.claude/context/memory/decisions.md`

> ASSUME INTERRUPTION: If it's not in memory, it didn't happen.

