---
name: loop
description: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
---

# /loop — run a prompt on a recurring cadence

Parse the input as `[interval] <prompt…>` and schedule it. sema has TWO loop mechanisms — pick by whether an interval was given:

- **Interval given** → fixed cadence: schedule with `CronCreate` (fires as a scheduled task via the sema scheduler).
- **No interval** → dynamic mode: YOU self-pace the loop with `ScheduleWakeup`, choosing each delay from what you are actually waiting for.

## Parsing (in priority order)

1. **Leading token**: if the first whitespace-delimited token matches `^\d+[smhd]$` (e.g. `5m`, `2h`), that's the interval; the rest is the prompt.
2. **Trailing "every" clause**: if the input ends with `every <N><unit>` or `every <N> <unit-word>` (e.g. `every 20m`, `every 5 minutes`), extract it as the interval and strip it from the prompt. Only match when what follows "every" is a time expression — `check every PR` has no interval.
3. **Otherwise**: no interval → dynamic mode; the entire input is the prompt.

If the resulting prompt is empty AND no interval was given, show usage `/loop [interval] <prompt>` and stop. An empty prompt WITH intent to run autonomously is the autonomous-loop case (see sentinels below).

## Fixed cadence: CronCreate

Convert the interval to a 5-field local-time cron expression (`Nm` → `*/N * * * *`; `Nh` → `0 */N * * *`; `Nd` → `0 0 */N * *`; `Ns` → round up to whole minutes — 1 minute is the floor). If the interval doesn't divide its unit cleanly (e.g. `7m`, `90m`), pick the nearest clean interval and tell the user what you rounded to.

Call `CronCreate` with:

- `prompt`: the parsed prompt, verbatim (slash commands pass through unchanged). The task fires UNATTENDED and will not see this conversation, so if the prompt relies on conversational context, rewrite it self-contained first.
- `schedule`: `{ "kind": "cron", "expr": "<the expression>" }`
- `label`: a short name for the loop (also the dedup key — re-creating with the same schedule+label upserts instead of duplicating).

Then briefly confirm what's scheduled (id, human cadence, how to cancel with `CronDelete`), and **immediately execute the parsed prompt once now** — don't wait for the first fire.

## Dynamic mode: ScheduleWakeup

With no interval, run one iteration of the task now, then call `ScheduleWakeup` to schedule when you resume:

- `delaySeconds`: how long to sleep (runtime clamps to [60, 3600]).
- `reason`: one short, specific sentence ("watching CI run #4521" beats "waiting") — shown to the user and telemetry.
- `prompt`: the same /loop input verbatim, so the next firing re-enters this skill and continues the loop. For an autonomous loop with no user prompt, pass the literal sentinel `<<autonomous-loop-dynamic>>` (never the CronCreate-mode `<<autonomous-loop>>` sentinel — ScheduleWakeup always uses the `-dynamic` variant).
- To END the loop: call `ScheduleWakeup` with `stop: true` and omit every other field — the loop ends immediately and no further wakeups fire.

### Picking delaySeconds

Do NOT schedule short wakeups to poll harness-tracked background work — when tracked work finishes you are re-invoked automatically, so polling is wasted; schedule a long fallback (1200s+) so the loop survives if the work hangs. Only poll external state the harness cannot track (a CI run, a deploy, a remote queue), and pick the delay from how fast that state actually changes.

Wake-up cost is driven by the provider prompt cache: anthropic-messages routes default to a ~5-minute cache TTL (waking under ~300s stays warm; prefer 270s over 300s when actively polling, and commit to 1200s+ rather than repeated ~300s waits). Routes with hours-long cache retention (e.g. DeepSeek) re-read your context cached at any delay in the clamp range — there, pick purely from the signal you're waiting for. Never schedule extra wakeups just to keep the cache warm. For idle ticks with no specific signal, default to 1200–1800s.

## Which mechanism backs this

Both tools are engine tools mounted only when the runtime exposes a scheduler capability; in the sema TOC shell that backend is the shell scheduler daemon, which persists tasks and fires them on time. Dynamic wakeups resume THIS session (`mode: session-wakeup`, label `loop-wakeup`); CronCreate tasks run unattended. If `CronCreate`/`ScheduleWakeup` are not in your tool list, the scheduler capability is not wired in this session — say so instead of simulating a loop with sleeps.
