/loopify β Set up an agent loop
Wizard for going from "this task should run periodically" to a working loop with the right pacing, idempotency, and bail-out. Reference: ScheduleWakeup (dynamic pacing), CronCreate (fixed schedule), and the built-in /loop (dynamic self-paced re-entry).
Step 0 β Confirm what you're looping
Ask if not obvious from context: "What task should this loop do each iteration?"
Then get the essentials:
| Question | Why it matters |
|---|---|
| How often? | Determines cron vs dynamic vs one-shot |
| When to stop? | Bail-out condition β loops must have one |
| What's the loop body doing? | Determines idempotency requirements |
| Where does output go? | File / notification / commit / nothing |
| What's the failure mode if it runs twice? | Idempotency validation |
Step 1 β Pick the pattern
Three primary patterns. Route by the answer to "how often":
Pattern A β Cron (fixed schedule)
Use when: task runs at predictable intervals β daily at 8am, weekly on Fridays, hourly on the hour.
Tool: CronCreate β schedules a recurring task with a cron expression.
CronCreate({
schedule: "0 8 * * *", // daily at 8am local
prompt: "<loop body prompt>",
timezone: "America/Los_Angeles"
})Common cron patterns:
0 8 * * *β daily at 8am0 9 * * 1β Mondays at 9am0 9 * * 5β Fridays at 9am0 */2 * * *β every 2 hours*/15 * * * *β every 15 minutes
Trade-offs:
- β Predictable, human-readable, easy to reason about
- β Best for time-of-day-dependent tasks (morning brief, EOD summary)
- β Runs at the scheduled time even if the last run isn't done β need idempotent body
- β No self-pacing β over-schedules if the task duration varies wildly
Pattern B β Dynamic pacing (self-scheduled)
Use when: task should react to state, not the clock. Monitor-until-condition-met patterns. Waiting on an external event.
Tool: ScheduleWakeup β the current run schedules its own next wake-up.
ScheduleWakeup({
delaySeconds: 270, // stay in cache window (< 5min)
reason: "checking build status; sleeping under 5min to stay cache-warm",
prompt: "<same task, re-entered>"
})Critical delay rules (from ScheduleWakeup docs β internalized in the wizard):
| Delay range | Use for | Cache impact |
|---|---|---|
| 60sβ270s | Active work β polling build, waiting for state that's about to change | Stays in 5-min prompt cache β fast + cheap |
| 300s β | DON'T USE THIS | Worst of both worlds β pay cache miss without amortizing |
| 300sβ3600s | Waiting on something that takes minutes to change | Pay cache miss but justified |
| 1200sβ1800s (20β30 min) | Idle ticks with no specific signal | Default for autonomous loops |
Never pick 300s literally β either drop to 270 (cache stays warm) or commit to 1200+ (cache miss buys longer wait).
Trade-offs:
- β Adaptive β sleeps longer when idle, shorter when active
- β Cache-optimal when tuned right
- β Requires the loop body to know when to schedule next (extra logic)
- β Harder to reason about when it'll run
Pattern C β One-shot loop (until-condition)
Use when: task runs until a condition is met, then stops. No recurrence after that.
Tool: /loop (built-in) with an exit condition in the prompt itself.
/loop
Check if the deploy is healthy. If yes β stop. If no β wait 5 min and check again.
Max 10 iterations. If still failing after 10, alert and stop.Trade-offs:
- β Simplest for check-until-condition
- β Bounded β always eventually terminates
- β Not for indefinite recurrence β that's Pattern A or B
Step 2 β Design the loop body for idempotency
Idempotent = running the loop twice produces the same result as running it once. Non-negotiable for cron and dynamic patterns because they'll fire while the previous iteration is still running or partially complete.
Idempotency patterns:
- Use "already done" markers: e.g., commit a state file
<vault>/.loopify/<name>-last-run.txtwith the timestamp of last successful run. Loop body checks the timestamp before doing work. - Use dedupe keys: if the loop writes to a DB or file, key by content-hash or timestamp so re-runs are no-ops.
- Use transactions: DB writes in the loop body should be atomic β either all commit or all roll back.
- Query before mutate: check current state before applying the change. If already applied, skip.
Show the user the loop body draft, highlighting the idempotency check. If none exists, add one.
Step 3 β Bail-out condition
Every loop needs one. Options:
| Bail-out | When to use |
|---|---|
| Max iterations (e.g., stop after 100 runs) | Cron loops β prevents runaway |
| State-based (e.g., stop when metric X drops below Y) | Monitoring loops |
| Time-based (e.g., stop after 24 hours) | Bounded monitoring |
| Error-based (e.g., stop on 3 consecutive failures) | All loops β catches degradation |
If the loop is truly indefinite (e.g., a weekly cron with no end), still add a manual bail-out via CronDelete. Document it in the SKILL/loop notes so the user knows how to stop it.
Step 4 β Set the schedule
Based on the pattern from Step 1:
Cron (Pattern A):
CronCreate({
schedule: "<expression>",
timezone: "<tz>",
prompt: "<loop body>",
})Report the cron_id returned so the user can CronDelete later.
Dynamic (Pattern B):
Wrap the loop body prompt so it ends with a ScheduleWakeup call:
<do the work>
Then: ScheduleWakeup({delaySeconds: <tuned per Step 1>, prompt: "<same body>", reason: "<why this cadence>"})One-shot (Pattern C):
Just run /loop <prompt with exit condition>.
Step 5 β Verify the first run
Wait for the first iteration (or trigger it manually via /loop with the same prompt for a dry-run). Confirm:
- Output landed where expected
- Idempotency check works (run twice β second should be a no-op)
- Bail-out condition would fire correctly if triggered
- Log/notification appears if configured
Step 6 β Report + follow-ups
Report:
- Pattern picked (A/B/C) + why
- Cron ID or wakeup pattern registered
- Bail-out condition set
- Idempotency mechanism in place
- How to stop the loop (
CronDelete <cron_id>or "just don't call the wakeup" for dynamic)
Offer:
- "Save this loop configuration as a skill via
skillify from-chat?" - "Want to also register a
weekly-reviewordaily-startuploop while we're here?" - "Should the loop write to
second-brainoutputs when it runs?"
Common loop recipes
Templates for frequent loop types (fill in as they're used):
references/daily-brief.mdβ morning routine loop (calendar + priorities + overnight)references/weekly-review.mdβ Friday portfolio pulsereferences/upstream-check.mdβ periodic check for changes to an adapted skill's upstreamreferences/vault-compile.mdβ periodic raw/ β wiki/ compilationreferences/metric-monitor.mdβ poll a metric until it crosses a threshold, then alert
Composes with
skillifyβ sibling in-ifytrifecta. Useskillifyto author a new SKILL.md β useloopifywhen the goal is a scheduled task, not a skill.toolifyβ sibling. Usetoolifyfor adding an integration β useloopifywhen the goal is running something on top of an already-integrated tool on a schedule.second-brainβ many loops write to the vault (raw/ or outputs/). The vault auto-commit pattern applies.pmβ daily-brief and weekly-review loops often read from pm before generating output.
Notes on quality
- Never pick 300s for
delaySeconds. Worst of both worlds. Drop to 270 or commit to 1200+. - Every loop needs a bail-out. Even indefinite ones need a documented manual stop.
- Idempotency is non-negotiable for cron + dynamic. Assume the loop will fire twice while a previous iteration is running.
- Prefer dynamic pacing over over-frequent cron. Cron every 15 min wastes tokens if the work isn't ready; dynamic pacing scales down when idle.
- Document the cron_id. Otherwise the loop is orphaned and hard to stop.
- Log every iteration briefly β even a single line ("2026-06-30 08:00 daily-brief: ran, 3 items") makes debugging drift trivial.
- Loops that touch external APIs need rate-limit respect. If the vendor has a 100/day limit, don't schedule 500/day.
- Bounded > unbounded when uncertain. If unsure whether to run for a week or a month, start with a week β extend after seeing it work.