Orbit Support Script Templates
Purpose: load this only when Orbit must generate or patch bootstrap.sh, recover.sh, verify.sh, or notify.sh. It keeps support-script bodies out of the main runner reference.
Contents
Bootstrap Template (bootstrap.sh)
Initializes the loop directory and creates missing artifacts from a goal description.
#!/bin/bash
# nexus-autoloop bootstrap — generated by Orbit
set -euo pipefail
LOOP_DIR="${1:-.nexus-loop}"
mkdir -p "${LOOP_DIR}"
#--- goal.md ---
if [[ ! -f "${LOOP_DIR}/goal.md" ]]; then
cat > "${LOOP_DIR}/goal.md" <<'GOAL'
# Goal
## Objective
{{OBJECTIVE}}
## Why
{{WHY}}
## Acceptance Criteria
{{ACCEPTANCE_CRITERIA}}
## Out of Scope
{{OUT_OF_SCOPE}}
## Verification Command
```
{{VERIFY_CMD}}
```
GOAL
echo "[OK] Created ${LOOP_DIR}/goal.md"
fi
#--- progress.md ---
if [[ ! -f "${LOOP_DIR}/progress.md" ]]; then
cat > "${LOOP_DIR}/progress.md" <<PROGRESS
# Progress
## Iteration 0 — Bootstrap
- Initialized loop directory
- Contract artifacts created
- Status: READY
PROGRESS
echo "[OK] Created ${LOOP_DIR}/progress.md"
fi
#--- state.env (preserve an existing checkpoint on bootstrap reruns) ---
if [[ ! -f "${LOOP_DIR}/state.env" ]]; then
cat > "${LOOP_DIR}/state.env" <<EOF
NEXT_ITERATION=1
LAST_STATUS=READY
LAST_UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
ORIGIN_BRANCH=
ITER_BRANCH=
CONTRACT_VERSION=1.2.0
EOF
echo "[OK] Created ${LOOP_DIR}/state.env"
fi
#--- verify.sh (conditional: only when VERIFY_CMD is specified) ---
VERIFY_CMD=$(cat <<'VERIFY_COMMAND'
{{VERIFY_CMD}}
VERIFY_COMMAND
)
if [[ -n "${VERIFY_CMD}" ]]; then
cat > "${LOOP_DIR}/verify.sh" <<'VERIFY'
#!/bin/bash
set -euo pipefail
PASS=0
FAIL=0
run_check() {
local name="$1"
shift
if "$@" > /dev/null 2>&1; then
echo "[PASS] ${name}"
PASS=$((PASS + 1))
else
echo "[FAIL] ${name}"
FAIL=$((FAIL + 1))
fi
}
#--- Acceptance criteria checks ---
{{VERIFY_CHECKS}}
#--- Summary ---
echo ""
TOTAL=$((PASS + FAIL))
echo "=== Verification: ${PASS}/${TOTAL} passed, ${FAIL} failed ==="
if [[ "${TOTAL}" -eq 0 || "${FAIL}" -gt 0 ]]; then
exit 1
else
exit 0
fi
VERIFY
chmod +x "${LOOP_DIR}/verify.sh"
echo "[OK] Created ${LOOP_DIR}/verify.sh"
fi
#--- run-loop.sh (main runner — always generated) ---
cat > "${LOOP_DIR}/run-loop.sh" <<'RUN_LOOP'
{{RUN_LOOP_CONTENT}}
RUN_LOOP
chmod +x "${LOOP_DIR}/run-loop.sh"
echo "[OK] Created ${LOOP_DIR}/run-loop.sh"
#--- notify.sh (iteration notification — always generated) ---
cat > "${LOOP_DIR}/notify.sh" <<'NOTIFY_SH'
{{NOTIFY_CONTENT}}
NOTIFY_SH
chmod +x "${LOOP_DIR}/notify.sh"
echo "[OK] Created ${LOOP_DIR}/notify.sh"
echo ""
echo "=== Bootstrap Complete ==="
echo "Loop directory: ${LOOP_DIR}"
echo "Artifacts: goal.md, progress.md, state.env, run-loop.sh, notify.sh${VERIFY_CMD:+, verify.sh}"
echo "Next: run 'bash ${LOOP_DIR}/run-loop.sh' to start execution"Recovery Template (recover.sh)
Re-syncs state.env from evidence when the resume state diverges from progress.md, and provides targeted recovery flags for specific failure classes.
Usage: recover.sh [--reset-circuit] [--repin-goal] [--clear-stall] [--migrate] [LOOP_DIR]
| Flag | Recovers | Class |
|---|---|---|
| (none) | rebuild state.env from progress.md evidence (durable checkpoint-replay) |
STATE_DRIFT |
--reset-circuit |
delete .circuit-state |
CIRCUIT_OPEN |
--clear-stall |
delete .action-sig.log so the stall counter resets after disambiguation |
CONVERGENCE_STALL / OSCILLATION_LOOP |
--repin-goal |
re-pin .goal.sha256 to current goal.md (only after confirming it is the intended baseline) |
GOAL_DRIFT |
--migrate |
set CONTRACT_VERSION to current and add missing optional fields with defaults |
contract migration |
#!/bin/bash
# nexus-autoloop recovery — generated by Orbit
set -euo pipefail
#--- Argument parsing: flags in any order + optional LOOP_DIR positional ---
LOOP_DIR="${LOOP_DIR:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)}"
DO_RESET_CIRCUIT=false
DO_REPIN_GOAL=false
DO_CLEAR_STALL=false
DO_MIGRATE=false
for arg in "$@"; do
case "${arg}" in
--reset-circuit) DO_RESET_CIRCUIT=true ;;
--repin-goal) DO_REPIN_GOAL=true ;;
--clear-stall) DO_CLEAR_STALL=true ;;
--migrate) DO_MIGRATE=true ;;
--*) echo "[WARN] Unknown flag: ${arg}" ;;
*) LOOP_DIR="${arg}" ;;
esac
done
echo "=== Orbit Recovery (${LOOP_DIR}) ==="
CURRENT_CONTRACT_VERSION="1.2.0"
#--- Preserve resumable fields from existing state.env (branch isolation, version, cost) ---
ORIGIN_BRANCH=""; ITER_BRANCH=""; CONTRACT_VERSION="${CURRENT_CONTRACT_VERSION}"
TOTAL_TOKENS=""; TOTAL_API_CALLS=""; ESTIMATED_COST_USD=""
# Parse checkpoint data without evaluating shell code or assigning environment keys.
load_state() {
local key value
[[ -f "${LOOP_DIR}/state.env" ]] || return 0
while IFS='=' read -r key value || [[ -n "${key}" ]]; do
case "${key}" in
NEXT_ITERATION)
[[ "${value}" =~ ^[1-9][0-9]*$ ]] || continue ;;
LAST_STATUS)
[[ "${value}" =~ ^(READY|CONTINUE|DONE|BLOCKED)$ ]] || continue ;;
TOTAL_TOKENS|TOTAL_API_CALLS|ITER_TOKENS|ITER_API_CALLS)
[[ "${value}" =~ ^[0-9]+$ ]] || continue ;;
ESTIMATED_COST_USD)
[[ "${value}" =~ ^[0-9]+([.][0-9]+)?$ ]] || continue ;;
CONTRACT_VERSION)
[[ "${value}" =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]] || continue ;;
LAST_UPDATED_AT|ORIGIN_BRANCH|ITER_BRANCH|RECOVERED_FROM|LOOP_BASE) ;;
*) continue ;;
esac
printf -v "${key}" '%s' "${value}"
done < "${LOOP_DIR}/state.env"
}
load_state
[[ "${DO_MIGRATE}" == "true" ]] && CONTRACT_VERSION="${CURRENT_CONTRACT_VERSION}"
#--- Targeted recovery flags ---
if [[ "${DO_RESET_CIRCUIT}" == "true" ]]; then
rm -f "${LOOP_DIR}/.circuit-state"
echo "[OK] Circuit breaker state cleared"
fi
if [[ "${DO_CLEAR_STALL}" == "true" ]]; then
rm -f "${LOOP_DIR}/.action-sig.log"
echo "[OK] Convergence signature log cleared — stall counter reset"
fi
if [[ "${DO_REPIN_GOAL}" == "true" ]] && [[ -f "${LOOP_DIR}/goal.md" ]]; then
shasum -a 256 "${LOOP_DIR}/goal.md" | awk '{print $1}' > "${LOOP_DIR}/.goal.sha256"
echo "[OK] goal.md re-pinned — confirm it is the intended baseline (GOAL_DRIFT recovery)"
fi
# Read only the last iteration section. Free text such as "not completed" is not
# completion evidence, and earlier DONE sections cannot finish a later iteration.
LATEST_ITER=0
RECOVERED_STATUS="CONTINUE"
if [[ -f "${LOOP_DIR}/progress.md" ]]; then
RECOVERED_EVIDENCE=$(awk '
/^## Iteration [0-9]+/ {
iter=$3; status="CONTINUE"; in_iteration=1
if ($NF ~ /^(READY|CONTINUE|DONE|BLOCKED|INTERRUPTED)$/) status=$NF
next
}
/^## / { in_iteration=0; next }
in_iteration && /^- (Status|Decision): (READY|CONTINUE|DONE|BLOCKED)$/ { status=$3 }
END { print iter+0, (status == "" ? "CONTINUE" : status) }
' "${LOOP_DIR}/progress.md")
read -r LATEST_ITER RECOVERED_STATUS <<< "${RECOVERED_EVIDENCE}"
fi
if [[ "${RECOVERED_STATUS}" == "BLOCKED" || "${RECOVERED_STATUS}" == "INTERRUPTED" ]]; then
NEXT_ITER=$((LATEST_ITER > 0 ? LATEST_ITER : 1))
[[ "${RECOVERED_STATUS}" == "INTERRUPTED" ]] && RECOVERED_STATUS="CONTINUE"
else
NEXT_ITER=$((LATEST_ITER + 1))
fi
echo "[INFO] Latest iteration: ${LATEST_ITER}"
echo "[INFO] Recovered status: ${RECOVERED_STATUS}"
echo "[INFO] Next iteration will be: ${NEXT_ITER}"
#--- Rebuild state.env (atomic write; preserve branch/version/cost fields) ---
state_tmp=$(mktemp "${LOOP_DIR}/state.env.XXXXXX")
cat > "${state_tmp}" <<EOF
NEXT_ITERATION=${NEXT_ITER}
LAST_STATUS=${RECOVERED_STATUS}
LAST_UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
ORIGIN_BRANCH=${ORIGIN_BRANCH:-}
ITER_BRANCH=${ITER_BRANCH:-}
LOOP_BASE=${LOOP_BASE:-}
CONTRACT_VERSION=${CONTRACT_VERSION:-${CURRENT_CONTRACT_VERSION}}
RECOVERED_FROM=progress_evidence
EOF
[[ -n "${TOTAL_TOKENS:-}" ]] && echo "TOTAL_TOKENS=${TOTAL_TOKENS}" >> "${state_tmp}"
[[ -n "${TOTAL_API_CALLS:-}" ]] && echo "TOTAL_API_CALLS=${TOTAL_API_CALLS}" >> "${state_tmp}"
[[ -n "${ESTIMATED_COST_USD:-}" ]] && echo "ESTIMATED_COST_USD=${ESTIMATED_COST_USD}" >> "${state_tmp}"
mv "${state_tmp}" "${LOOP_DIR}/state.env"
#--- Refresh checksum so the runner does not re-trigger recovery on resume ---
shasum -a 256 "${LOOP_DIR}/state.env" | awk '{print $1}' > "${LOOP_DIR}/state.env.sha256"
echo "[OK] Rebuilt ${LOOP_DIR}/state.env and refreshed state.env.sha256"
#--- Append recovery note to progress.md ---
{
echo ""
echo "## Recovery — $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "- Recovered from progress.md evidence"
echo "- Latest iteration found: ${LATEST_ITER}"
echo "- Recovered status: ${RECOVERED_STATUS}"
echo "- state.env rebuilt with NEXT_ITERATION=${NEXT_ITER} (branch/version/cost preserved)"
echo "- Flags: reset_circuit=${DO_RESET_CIRCUIT} clear_stall=${DO_CLEAR_STALL} repin_goal=${DO_REPIN_GOAL} migrate=${DO_MIGRATE}"
} >> "${LOOP_DIR}/progress.md"
echo "[OK] Appended recovery note to ${LOOP_DIR}/progress.md"
echo ""
echo "=== Recovery Complete ==="Verification Template (verify.sh)
Runs acceptance-criteria checks. Customize it to match goal.md.
#!/bin/bash
# nexus-autoloop verification gate — generated by Orbit
set -euo pipefail
PASS=0
FAIL=0
run_check() {
local name="$1"
shift
if "$@" > /dev/null 2>&1; then
echo "[PASS] ${name}"
PASS=$((PASS + 1))
else
echo "[FAIL] ${name}"
FAIL=$((FAIL + 1))
fi
}
#--- Acceptance criteria checks ---
# Customize: add run_check calls matching goal.md acceptance criteria
# Example:
# run_check "Build succeeds" npm run build
# run_check "Tests pass" npm test
# run_check "Lint clean" npm run lint
{{VERIFY_CHECKS}}
#--- Summary ---
echo ""
TOTAL=$((PASS + FAIL))
echo "=== Verification: ${PASS}/${TOTAL} passed, ${FAIL} failed ==="
if [[ "${TOTAL}" -eq 0 || "${FAIL}" -gt 0 ]]; then
exit 1
else
exit 0
fiNotification Template (notify.sh)
Iteration-completion notification with deterministic status narration and Cast SPEAK TTS. Notification delivery must not depend on an optional model session finishing.
Arguments: $1=ITER $2=STATUS $3=VERIFY_RESULT $4=ITER_DURATION $5=LOOP_DIR $6=COMMIT_HASH
#!/bin/bash
# nexus-autoloop iteration notifier — generated by Orbit
# Cast SPEAK engine compliant TTS notification
ITER="${1:?Usage: notify.sh ITER STATUS VERIFY_RESULT ITER_DURATION LOOP_DIR [COMMIT_HASH]}"
STATUS="${2:?}"
VERIFY_RESULT="${3:?}"
ITER_DURATION="${4:?}"
LOOP_DIR="${5:?}"
COMMIT_HASH="${6:-no-commit}"
NOTIFY_ENGINE="${NOTIFY_ENGINE:-auto}"
NOTIFY_LANG="${NOTIFY_LANG:-ja}"
NOTIFY_PERSONA_FILE="${NOTIFY_PERSONA_FILE:-}"
#--- Generate bounded, deterministic notification text ---
# agy requires a real pty and artifact/sentinel capture (_common/CLI_COMPATIBILITY.md
# §9.2); piping a prompt into an interactive session can block loop completion.
if [[ "${NOTIFY_LANG}" == "ja" ]]; then
NOTIFY_TEXT="イテレーション${ITER}完了。ステータス: ${STATUS}、検証: ${VERIFY_RESULT}、所要時間: ${ITER_DURATION}秒"
else
NOTIFY_TEXT="Iteration ${ITER} complete. Status: ${STATUS}, Verify: ${VERIFY_RESULT}, Duration: ${ITER_DURATION}s"
fi
#--- Persona override (Cast integration) ---
PERSONA_VOICE=""
if [[ -n "${NOTIFY_PERSONA_FILE}" ]] && [[ -f "${NOTIFY_PERSONA_FILE}" ]]; then
PERSONA_VOICE=$(grep -oE 'voice:\s*.*' "${NOTIFY_PERSONA_FILE}" | head -1 | sed 's/voice:[[:space:]]*//')
fi
#--- TTS playback (3-tier fallback: edge-tts -> say -> text-only) ---
TTS_PLAYED=false
# Tier 1: edge-tts (cross-platform, high quality)
if [[ "${NOTIFY_ENGINE}" == "auto" || "${NOTIFY_ENGINE}" == "edge-tts" ]]; then
if command -v edge-tts >/dev/null 2>&1; then
VOICE="${PERSONA_VOICE:-ja-JP-NanamiNeural}"
AUDIO_DIR="${LOOP_DIR}/notify-audio"
mkdir -p "${AUDIO_DIR}"
AUDIO_FILE="${AUDIO_DIR}/iter-${ITER}.mp3"
if edge-tts --voice "${VOICE}" --text "${NOTIFY_TEXT}" --write-media "${AUDIO_FILE}" 2>/dev/null; then
if command -v afplay >/dev/null 2>&1; then
afplay "${AUDIO_FILE}" 2>/dev/null &
TTS_PLAYED=true
elif command -v mpv >/dev/null 2>&1; then
mpv --no-video "${AUDIO_FILE}" 2>/dev/null &
TTS_PLAYED=true
fi
fi
fi
fi
# Tier 2: macOS say (built-in, no install required)
if [[ "${TTS_PLAYED}" != "true" ]] && [[ "${NOTIFY_ENGINE}" == "auto" || "${NOTIFY_ENGINE}" == "say" ]]; then
if command -v say >/dev/null 2>&1; then
say "${NOTIFY_TEXT}" 2>/dev/null &
TTS_PLAYED=true
fi
fi
# Tier 3: text-only (always succeeds)
if [[ "${TTS_PLAYED}" != "true" ]]; then
echo "[NOTIFY] ${NOTIFY_TEXT}"
fi
#--- Log record ---
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] iter=${ITER} status=${STATUS} verify=${VERIFY_RESULT} text=${NOTIFY_TEXT}" >> "${LOOP_DIR}/runner.log"