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 /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).From its SKILL.md
npx -y skills add restarter/lets-workflow --skill loop-testAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.9 KB, as published. Nobody here has run it
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.
- User runs
/loop <prompt>(no interval). - Model does the work.
- Before yielding, model calls
ScheduleWakeup(delaySeconds, prompt, reason):delaySecondsclamped to[60, 3600]promptmust be exactly the same string as the original/loopinputreasonis one sentence shown to user + sent to telemetry — be specific ("polling CI run"beats"waiting")
- Harness re-invokes the model at the scheduled time. Loop continues.
- 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 wait300sis 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 to60s, actual scheduled65s(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
/loopwith 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 modetengu_loop_dynamic_wakeup_scheduled— fired per ScheduleWakeuptengu_loop_dynamic_wakeup_aged_out— fired when 7-day cap hits
Designing a useful /loop for LETS
Three rules that fit this project:
- 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 readyare safe;/lets:commit,/lets:doneare not. - 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.
- Pick the delay for the actual signal, not a round number. Polling a CI run that takes ~8min?
delaySeconds=480is better thandelaySeconds=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 or command>. If still running: report "running, T+Xmin" + estimated remaining. If failed: surface the error and stop. If succeeded: confirm and stop. Reschedule 180s while running.
Use case: triggered a deploy or migration, want hands-off progress reporting.
Combining /loop with /goal (open research)
Interesting question: nest /loop inside /goal?
/goal Repo passes /lets:check three times in a row over the next hour.
/loop 5m /lets:check
The loop schedules the work; the goal defines the termination condition. Untested — see goal-test for the /goal half and try.
Concrete experiment
/loop check git status; if no uncommitted files and `git rev-list main..HEAD --count` is 0, report "clean" and stop. Otherwise report the dirty state and reschedule 90s.
Watch in findings:
- Does
ScheduleWakeupfire after each tick? - Is
delaySeconds=90honored, or clamped to 60? - How does the UI display loop ticks between user messages?
- Does the model self-terminate when the stop-condition is met (no ScheduleWakeup call), or does it loop indefinitely?
Open questions
- How does the cron-mode (
/loop 5m ...) persist? Just file on disk, or also stored in session state? Does it surviveclauderestart? - Can a dynamic loop call another dynamic loop? (Probably idempotent on prompt-string — second
/loopcancels first.) - Does
/loopinteract with worktrees? Per-worktree state files, or global to the user? - What's the practical max number of concurrent loops? (Both cron and dynamic.)
- How does
/loopinteract with/goal? Does a Stop hook block a/looptick from yielding?
Findings (fill in during experiments)
Track in sibling findings.md. One entry per experiment:
### <date> · <prompt>
- Mode: (interval / dynamic)
- delaySeconds: requested → actual (clamped?)
- Ticks observed: N
- Self-terminate?: yes/no (did it skip ScheduleWakeup when condition met?)
- UI experience: (intrusive? unobtrusive? useful?)
- Promote to plugin? yes/no + rationale
Promote criteria
Skill graduates to a real /lets:watch (or similar) command in plugins/lets/commands/ only if:
- At least one template (A/B/C) produces useful repeat-value across 3+ different real situations
- The "loop only on read-only polls" boundary is enforceable as a command-level guardrail
- We have a clear answer for: "how does the user stop the loop without losing the context?"
Otherwise: keep notes in findings.md, close lets-53buy with a "investigated, parking the pattern" comment.
See also
- Sibling skill
goal-test— for convergent autonomous work (Stop hook condition)./goalis the right tool forplan → review → check → execute → review → checkchaining;/loopis not. - LETS Rules
AUTO MODEsection in.claude/rules/lets-rules.md— gating rules every loop must respect. - Binary symbols (claude v2.1.150):
tengu_kairos_loop_dynamic,tengu_loop_dynamic_wakeup_scheduled,recurringMaxAgeMs,ScheduleWakeuptool.