Skip to content
Development
Skill

/loop-test

Local sandbox for experimenting with Claude Code's /loop command (interval + dynamic/self-paced scheduling) applied to LETS workflows. Use when the user says "loop test", "loop-test", "test the loop skill", "цикл LETS", "ScheduleWakeup experiment", or wants to design a periodic

From plugin
lets-workflow
1718 skills15 agents23 commands
Install
$ npx -y skills add restarter/lets-workflow --skill loop-test --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/loop-test

Context preview

The summary Claude sees to decide when to auto-load this skill.

Local sandbox for experimenting with Claude Code's /loop command (interval + dynamic/self-paced scheduling) applied to LETS workflows. Use when the user says "loop test", "loop-test", "test the loop skill", "цикл LETS", "ScheduleWakeup experiment", or wants to design a periodic

SKILL.md

loop-test.SKILL.md
name: loop-test
description: Local sandbox for experimenting with Claude Code's /loop command (interval + dynamic/self-paced scheduling) applied to LETS workflows. Use when the user says "loop test", "loop-test", "test the loop skill", "цикл LETS", "ScheduleWakeup experiment", or wants to design a periodic /loop that polls state or babysits a long-running job. Local-only — not part of the lets plugin payload. For convergent autonomous work toward an end state, use sibling skill `goal-test` (which covers /goal).

loop-test (local experiment)

`/loop` schedules **periodic re-invocations of the same prompt**. Two modes:

| Mode | Syntax | Mechanic | |---|---|---| | **Interval** | `/loop 5m /foo` · `/loop /standup 1h` · `/loop check the deploy every 20m` | Cron entry in `.claude/scheduled_tasks.json`. Default `10m` when only `[interval]` shorthand. Survives session restart (durable). | | **Dynamic / self-paced** | `/loop check the deploy` (no interval anywhere) | Model calls `ScheduleWakeup(delaySeconds, prompt, reason)` at the end of each turn. Skip → loop ends silently. |

**`/loop` is the wrong tool for sequential workflow phases.** It repeats the *same prompt* each tick — to chain `plan → review → check`, the prompt itself has to be phase-aware (read state, advance, write back). For state-convergent autonomy, use `/goal` instead (see `goal-test` skill).

**Right tool for `/loop`:**

  • Polling external state (CI runs, deploys, PR comments)
  • Periodic checks (every 20m: any new bd-ready tasks?)
  • Babysitting a long-running job (check progress, surface failures)
  • Continuous monitoring during a long focused session

How `/loop` actually works

Reverse-engineered from `claude` binary v2.1.150.

Dynamic-mode contract

This is the mode you care about most — interval is just cron.

1. User runs `/loop <prompt>` (no interval). 2. Model does the work. 3. Before yielding, model calls `ScheduleWakeup(delaySeconds, prompt, reason)`:

  • `delaySeconds` clamped to `[60, 3600]`
  • `prompt` must be **exactly the same string** as the original `/loop` input
  • `reason` is one sentence shown to user + sent to telemetry — be specific (`"polling CI run"` beats `"waiting"`)

4. Harness re-invokes the model at the scheduled time. Loop continues. 5. Skip `ScheduleWakeup` → loop ends silently. No error.

Cache TTL gotcha

Anthropic prompt cache TTL is 5 minutes. Picking `delaySeconds`:

  • `< 270s` → cache stays warm, cheap re-invocation
  • `>= 1200s` → commit to the miss, one cache-miss amortizes over a long wait
  • **`300s` is worst-of-both** — you pay the miss without amortizing. Avoid exactly 300.

For idle ticks with no specific signal to watch, default `1200-1800s` (20-30 min). User can always interrupt if they need you sooner.

Minute-boundary rounding gotcha (empirical, Experiment 1)

`delaySeconds` is NOT honored exactly. The actual wakeup time is **rounded UP to the next clean minute boundary**. Binary cron expression is `MM HH * * *` (minute-only granularity).

Examples from Experiment 1:

  • Requested `30s`, clamped to `60s`, actual scheduled `65s` (we were at HH:MM:55)
  • Same request next tick, actual scheduled `101s` (we were at HH:MM:19)

**Implication:** for any tick under ~1min you get 60-119s of real delay. Designing precise polling cadences <2min is futile — pick `60s` (floor) and accept jitter, or accept >120s with predictable bucketing. Also note dispatch latency adds ~10-25s on top.

Cancellation / collisions

  • Re-issuing `/loop` with the **same prompt** cancels the previous pending wakeup for that prompt (`ZP5(_)` in binary). One prompt = one live loop.
  • User interrupt cancels **all** pending loop wakeups (`b76()` returns count cancelled).
  • Recurring max age: `7 days` (`recurringMaxAgeMs = 604800000`). After that the loop auto-ages-out.

Sentinels (fully autonomous)

For unattended runs where there's no user prompt sitting in the channel:

  • `<<autonomous-loop>>` — cron-mode autonomous (CronCreate-backed)
  • `<<autonomous-loop-dynamic>>` — ScheduleWakeup-mode autonomous

Useful for `/lets:schedule`-style routines but probably YAGNI for LETS right now.

Telemetry

  • `tengu_kairos_loop_dynamic` — feature flag for dynamic mode
  • `tengu_loop_dynamic_wakeup_scheduled` — fired per ScheduleWakeup
  • `tengu_loop_dynamic_wakeup_aged_out` — fired when 7-day cap hits

Designing a useful `/loop` for LETS

Three rules that fit this project:

1. **Loop only on read-only state polls.** Anything that mutates (commit, push, bd close) needs explicit user approval per AUTO MODE rule — auto-loop violates it. `/lets:status`, `/lets:check`, `bd ready` are safe; `/lets:commit`, `/lets:done` are not. 2. **One sentence of new information per tick, or nothing.** If state hasn't changed, the tick should yield "no change" and reschedule. Don't re-report the same status over and over — the user sees ticks pile up. 3. **Pick the delay for the actual signal, not a round number.** Polling a CI run that takes ~8min? `delaySeconds=480` is better than `delaySeconds=600`. Avoid 300 (cache trap).

Concrete `/loop` templates for LETS

Template A — poll for PR feedback

/loop check `gh pr view <N> --json comments,reviewDecision` for new activity since last check. If nothing new: report "no change" and reschedule (1200s). If new comments or review state changed: report what's new and stop the loop so user can act.

Use case: opened a PR, want to know within 20min when reviewer pings, without keeping the window in focus.

Template B — backlog watcher

/loop run `bd ready --json | jq length`. If count increased since last tick: report which new tasks appeared (bd show each new id, one-line summary). If unchanged: report "no change". Reschedule 1800s.

Use case: long deep-work session on a feature, want to surface if a higher-priority bug landed in the backlog without breaking focus.

Template C — long-running job babysit

/loop check the deploy status at <URL
Read more
Ships withlets-workflow

A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.

Get the whole plugin

Other skills on lets-workflow.