--- name: stop description: Mark LOOP_CONTRACT.md completed, append DONE section, send PushNotification, let loop terminate naturally. allowed-tools: Bash, Read, Edit, AskUserQuestion, Skill argument-hint: "[reason] [--keep-forensics]" disable-model-invocation: false --- # autoloop: Stop Cleanly terminate a self-revising loop. Appends a `## DONE` section with timestamp + reason, sends a `PushNotification` summarizing final state, and stops scheduling new wake-ups. The next `/loop` firing will see the DONE marker and exit without acting. > **Self-Evolving Skill**: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues. ## Arguments - Positional (optional): reason string. Defaults to "user-requested stop". - `--keep-forensics` (flag, optional): retain the state directory after stop instead of archiving + removing it. Default behavior since Wave 2: cleanup tarball is created and `` is `rm -rf`'d. Pass this flag if you need to inspect heartbeat.json or revision-log/ after stop. ## Step 1: Locate contract ```bash CONTRACT_PATH="${CONTRACT_PATH:-./LOOP_CONTRACT.md}" if [ ! -f "$CONTRACT_PATH" ]; then echo "No contract at $CONTRACT_PATH — nothing to stop." exit 0 fi ``` If the user hasn't specified which contract, and multiple `LOOP_CONTRACT.md` files exist under the cwd, use `AskUserQuestion` to pick. ## Step 2: Confirm stop reason Use `AskUserQuestion` to pick a stop reason: - `Research saturation` — 3 consecutive null-rescue firings - `Goal achieved` — completion criterion met - `User request` — manual termination - `Blocked on external dependency` — can't proceed without intervention Record the chosen reason plus the free-text user note (if provided) into the DONE section. ## Step 3: Append DONE section Append this block to the contract (use `Edit` or a Bash `cat >>`): ```markdown --- ## DONE - **Stopped at**: - **Iteration**: - **Reason**: - **User note**: - **Final state summary**: The next /loop firing will observe this section and exit without further action. ``` ## Step 4: Send PushNotification Load `PushNotification` via `ToolSearch` if not already available, then send: ``` stopped at iter . Reason: . Final state: . ``` Keep under 200 chars. ## Step 5: Unload launchd plist Before unregistering, unload the launchd plist: ```bash # Source the launchd library PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}" source "$PLUGIN_ROOT/scripts/launchd-lib.sh" source "$PLUGIN_ROOT/scripts/state-lib.sh" # Derive loop_id and state_dir loop_id=$(derive_loop_id "$CONTRACT_PATH") state_dir=$(state_dir_path "$loop_id" "$CONTRACT_PATH") # Unload plist (idempotent; no-op on non-macOS) if ! unload_plist "$loop_id" "$state_dir" 2>/dev/null; then echo "WARNING: Failed to unload launchd plist" >&2 fi ``` ## Step 6: Unregister loop from machine registry After unloading the plist, clean up the machine registry entry: ```bash # Source the registry library PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}" source "$PLUGIN_ROOT/scripts/registry-lib.sh" # Derive loop_id from contract path loop_id=$(derive_loop_id "$CONTRACT_PATH") # Unregister from machine registry (idempotent; no error if already absent) if ! unregister_loop "$loop_id"; then echo "WARNING: Failed to unregister loop from machine registry" >&2 fi ``` ## Step 7: Clean state directory (Wave 2) After unregistering, archive and remove the loop's state directory so it doesn't accumulate as an orphan. The tarball is created next to the dir (in its parent), then the dir itself is `rm -rf`'d: ```bash # state_dir was already computed in Step 5 via state_dir_path() if [ -n "$state_dir" ]; then # Pass --keep-forensics if the user requested it (e.g. they want to inspect # heartbeat.json or revision-log/.jsonl after stop). KEEP_FLAG="" for arg in "$@"; do [ "$arg" = "--keep-forensics" ] && KEEP_FLAG="--keep-forensics" done cleanup_state_dir "$state_dir" $KEEP_FLAG fi ``` `cleanup_state_dir` is exported by `state-lib.sh`. It refuses to operate on paths outside `$HOME` (safety guard). Tarball failure is non-fatal — it warns and proceeds with the rm. ## Step 8: Update frontmatter Edit the YAML frontmatter `exit_condition` field to include `DONE` so the next firing detects it immediately without scanning the body. ## Step 9: Suggest final commit Print a suggested commit: ```bash git add "$CONTRACT_PATH" git commit -m "$(cat <<'EOF' loop(stop): complete — Final iteration: Last action: EOF )" ``` ## Anti-patterns - Do NOT `kill -9` any running process from this skill — that's out of scope. This skill only signals the loop; separate skills (like `ru:stop` or `pueue kill`) handle process termination. - Do NOT rewrite the Revision Log — append-only. Add a DONE section instead. - Do NOT send `PushNotification` if the user is actively present (watch for a recent user turn in the last 60 seconds); that would be annoying. When in doubt, skip the push. ## Troubleshooting | Symptom | Fix | | ---------------------------------- | ---------------------------------------------------------------------------------------------- | | `PushNotification` tool not loaded | `ToolSearch` with query `select:PushNotification` before step 4 | | DONE section already exists | Loop was already stopped; print confirmation and exit | | Loop keeps firing after DONE | The next `/loop` reading the contract should short-circuit — verify pointer trigger is correct | ## Post-Execution Reflection 0. **Locate yourself.** — Confirm this SKILL.md is the canonical file before any edit. 1. **What failed?** — Fix the instruction that caused it. 2. **What drifted?** — Update DONE-section template if needed. 3. **Log it.** — Evolution-log entry.