PingPongRealWorldRL / scripts /stop_h2_policy_eval.sh
KurisiTOMA's picture
chore: publish H2 real-world RL system
d8290eb verified
Raw History Blame Contribute Delete
5.89 kB
#!/usr/bin/env bash
# Graceful, project-owned evaluation stop. Never sends R1 or motor commands.
set -euo pipefail
PROJECT_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
RUN_DIR=
ACK=
ABORTED=0
usage() {
echo 'Usage: bash scripts/stop_h2_policy_eval.sh [--run-dir PATH] --ack-r1 H2-R1-CONFIRMED'
echo ' or: --ack-hardware-off H2-HARDWARE-POWER-OFF-CONFIRMED'
echo ' or: --startup-aborted (only when runner never started)'
}
fail() { echo "$*" >&2; exit 1; }
while (($#)); do
case "$1" in
--run-dir) RUN_DIR=${2:?missing run directory}; shift 2 ;;
--ack-r1|--ack-hardware-off) ACK=${2:?missing acknowledgement}; shift 2 ;;
--startup-aborted) ABORTED=1; shift ;;
-h|--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
done
if [[ -z $RUN_DIR ]]; then
[[ -f "$PROJECT_ROOT/artifacts/latest_eval_run" ]] || fail 'No evaluation run pointer in this project.'
IFS= read -r RUN_DIR < "$PROJECT_ROOT/artifacts/latest_eval_run"
fi
[[ -d $RUN_DIR ]] || fail "Evaluation run directory does not exist: $RUN_DIR"
RUN_DIR=$(cd -- "$RUN_DIR" && pwd -P)
H2_ROOT=
H2_IF=
H2_PY=
H2_SESSION=
FB_SESSION=
H2_EVAL_RUN_DIR=
if [[ -f "$RUN_DIR/eval_owner.env" ]]; then
source "$RUN_DIR/eval_owner.env"
elif [[ -f "$RUN_DIR/run.env" ]]; then
source "$RUN_DIR/run.env"
else
fail 'Missing evaluation owner metadata; nothing stopped.'
fi
[[ -n $H2_ROOT && -d $H2_ROOT ]] || fail 'Missing evaluation project owner; nothing stopped.'
[[ $(cd -- "$H2_ROOT" && pwd -P) == "$PROJECT_ROOT" ]] \
|| fail 'Evaluation run belongs to another project; nothing stopped.'
SESSION=${H2_SESSION:-$FB_SESSION}
[[ $SESSION =~ ^[A-Za-z0-9_.-]+$ ]] || fail 'Invalid evaluation session owner; nothing stopped.'
SCORES=${H2_EVAL_RUN_DIR:-$RUN_DIR/evaluation}
has_session() { tmux has-session -t "=$SESSION" 2>/dev/null; }
if has_session; then
# show-options uses target-pane syntax; ':' keeps the exact session scope.
[[ $(tmux show-options -v -t "=$SESSION:" @h2_eval_run_dir) == "$RUN_DIR" ]] \
|| fail 'Evaluation tmux owner mismatch; nothing stopped.'
elif [[ -f "$RUN_DIR/eval.stopped" ]]; then
echo "Evaluation already stopped. Scores: $SCORES"
exit 0
fi
[[ -f "$RUN_DIR/panes.tsv" ]] || fail 'Missing stable evaluation pane ownership; nothing stopped.'
pane_info() {
tmux display-message -p -t "$1" '#{session_name} #{pane_id} #{pane_dead}' 2>/dev/null || true
}
check_pane() {
local pane=$1 info
[[ $pane =~ ^%[0-9]+$ ]] || fail 'Invalid evaluation pane id; nothing stopped.'
info=$(pane_info "$pane")
[[ -z $info || $info == "$SESSION $pane 0" || $info == "$SESSION $pane 1" ]] \
|| fail "Pane $pane belongs to another session; nothing stopped."
}
while IFS=$'\t' read -r role pane; do
[[ -n $role ]] || continue
case "$role" in
control|runner|predictor|planner|adapter|monitor|bridge) check_pane "$pane" ;;
*) fail "Unknown evaluation pane role: $role; nothing stopped." ;;
esac
done < "$RUN_DIR/panes.tsv"
if [[ $ABORTED == 1 ]]; then
[[ ! -e "$RUN_DIR/runner.started" ]] || fail 'Runner started; use confirmed R1 or power-off before stop.'
elif [[ $ACK == H2-R1-CONFIRMED ]]; then
[[ -n $H2_PY ]] || fail 'Missing evaluation Python; nothing stopped.'
(
cd -- "$PROJECT_ROOT"
"$H2_PY" - <<'PY'
from runtime.h2_shm_bridge import H2SharedMemoryBridge
bridge = H2SharedMemoryBridge(claim_writer=False, independent_reader=True)
try:
state = bridge.read_state()
if not (state.stop_requested and state.emergency_stop):
raise SystemExit("R1 stop/fault is not latched; no process was stopped")
print("Verified stop=1 fault=1; read-only, no policy model loaded")
finally:
bridge.close()
PY
)
elif [[ $ACK != H2-HARDWARE-POWER-OFF-CONFIRMED ]]; then
usage >&2
fail 'Operator R1 or confirmed physical power-off is required; nothing stopped.'
fi
# Serialize only the launch decision, never worker exit or journal flush.
(
flock -x 9
touch "$RUN_DIR/eval.stop"
if [[ $ABORTED == 1 && -e "$RUN_DIR/runner.started" ]]; then
fail 'Runner started while cancelling startup; session preserved. Confirm R1 or power-off, then retry.'
fi
) 9>"$RUN_DIR/eval.lifecycle.lock"
role_pane() { awk -F '\t' -v role="$1" '$1 == role {print $2; exit}' "$RUN_DIR/panes.tsv"; }
control=$(role_pane control)
if [[ -n $control ]]; then
check_pane "$control"
if [[ $(pane_info "$control") == "$SESSION $control 0" ]]; then
tmux send-keys -t "$control" C-c 2>/dev/null || true
fi
fi
# A launcher already entering RUNNER must not be mistaken for an aborted start.
if [[ $ABORTED == 1 && -e "$RUN_DIR/runner.started" ]]; then
fail 'Runner started while cancelling startup; session preserved. Confirm R1 or power-off, then retry.'
fi
stop_role() {
local role=$1 pane info
pane=$(role_pane "$role")
[[ -n $pane ]] || return 0
check_pane "$pane"
info=$(pane_info "$pane")
[[ $info == "$SESSION $pane 0" ]] || return 0
tmux send-keys -t "$pane" C-c 2>/dev/null || true
for _ in {1..60}; do
info=$(pane_info "$pane")
[[ -z $info || $info == "$SESSION $pane 1" ]] && return 0
[[ $info == "$SESSION $pane 0" ]] || fail "Pane $pane ownership changed; session preserved."
sleep 0.5
done
fail "$role did not exit within 30 seconds; session preserved. Wait for journal flush and retry; no force kill."
}
for role in runner predictor planner adapter monitor bridge; do
stop_role "$role"
done
if has_session; then
tmux kill-session -t "=$SESSION" || {
! has_session || fail 'Owned session could not be closed; it was not force-killed.'
}
fi
touch "$RUN_DIR/eval.stopped"
if [[ -e /dev/shm/h2_pingpong_bridge ]]; then
echo 'WARNING: /dev/shm/h2_pingpong_bridge still exists; retained, not deleted.' >&2
fi
echo "Stopped only evaluation session $SESSION. Scores: $SCORES"
echo 'Shared capture, other projects and network configuration were not changed.'