The wipe script lived only on prod, untracked, which is why this was invisible: it deleted every mission and agent and left every per-repo .brain intact. Those files are in no table and no cascade reaches them, so agents kept recalling verdicts from missions that no longer existed — measured 2026-09-22, an 18,982-byte repo brain outliving its rows, and a harness assertion that failed on the correct behaviour because it asked the database what the brain remembered. Now tracked in the repo, and it clears /data/brains/*.h5 after the agent purge so "clean slate" means what it says. CLAWMATES_KEEP_BRAINS=1 keeps the old behaviour for when accumulated project knowledge is worth more than a blank start. Also documents what deliberately SURVIVES and why: corpus_items (the research seen-set, so a fresh harvest does not re-download papers already covered), judge usage rows, skills, repos, team_templates and nodes. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
74 lines
4.1 KiB
Bash
Executable File
74 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Delete every mission and agent, and the memory they accumulated.
|
|
#
|
|
# Runs through the API rather than raw SQL, so missions go out by the same
|
|
# path the UI uses and agents go through `batch-delete`'s FK-ordered
|
|
# hard-purge (runtime → container → .brain → DB). Raw deletes leave
|
|
# containers and brain files orphaned, which is how `container-reap-drift`
|
|
# happened.
|
|
#
|
|
# WHAT SURVIVES, by FK rule and by design:
|
|
# corpus_items SET NULL — the research seen-set, so a fresh harvest does
|
|
# not re-download and re-note papers already covered
|
|
# usage_events SET NULL for judge rows; agent rows are deleted explicitly
|
|
# by `agents::hard_purge`, which overrides the FK
|
|
# skills, repos, team_templates, nodes — untouched
|
|
#
|
|
# WHAT THIS CLEARS THAT IT USED NOT TO: the per-repo `.brain` files under
|
|
# CLAWMATES_BRAIN_DIR. They are not in the database and no cascade reaches
|
|
# them, so a wipe left every agent still recalling verdicts from missions
|
|
# that no longer existed — measured 2026-09-22, an 18,982-byte repo brain
|
|
# outliving its rows. "Clean slate" now means what it says.
|
|
#
|
|
# Do NOT run this while a deploy is mid-roll: it drives the API, and a
|
|
# server recreate interrupts the calls.
|
|
set -u
|
|
HOST="${CLAWMATES_HOST:-gw-04}"
|
|
OWNER="${CLAWMATES_OWNER_EMAIL:-om[email protected]}"
|
|
API="${CLAWMATES_API:-http://100.102.112.85:8088}"
|
|
PG=clawmates_postgres_1
|
|
SERVER=clawmates_server_1
|
|
KEEP_BRAINS="${CLAWMATES_KEEP_BRAINS:-0}"
|
|
|
|
psql_() { ssh "$HOST" "docker exec $PG psql -U postgres -d clawmates -tAc \"$1\""; }
|
|
|
|
secret="wipe-$(openssl rand -hex 16)"
|
|
hash=$(printf '%s' "$secret" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
|
|
rows=$(psql_ "insert into auth_sessions (user_id, token_hash, expires_at) select id, '$hash', now() + interval '20 minutes' from users where email='$OWNER' limit 1 returning 1;" 2>/dev/null | head -1 | tr -d '[:space:]')
|
|
[ "$rows" = "1" ] || { echo "mint failed for $OWNER"; exit 1; }
|
|
|
|
echo "== missions"
|
|
for id in $(psql_ "select id from missions order by created_at" | tr -d '\r'); do
|
|
code=$(ssh "$HOST" "curl -s -o /dev/null -w '%{http_code}' -X DELETE -H 'Authorization: Bearer $secret' '$API/api/missions/$id'")
|
|
echo " $id -> $code"
|
|
done
|
|
|
|
echo "== agents (batch hard-purge, streamed)"
|
|
ids=$(psql_ "select string_agg('\"'||id||'\"', ',') from agents" | tr -d '\r')
|
|
if [ -n "$ids" ]; then
|
|
ssh "$HOST" "curl -s -N -X POST -H 'Authorization: Bearer $secret' -H 'Content-Type: application/json' \
|
|
-d '{\"ids\":[$ids]}' '$API/api/claws/batch-delete'" \
|
|
| grep -oE '"stage":"[a-z_]+"' | sort | uniq -c | sed 's/^/ /'
|
|
else
|
|
echo " (none)"
|
|
fi
|
|
|
|
# The half no cascade reaches. Per-agent brains should already be gone with
|
|
# their agents; the per-REPO ones belong to no agent and would otherwise
|
|
# survive every wipe forever.
|
|
if [ "$KEEP_BRAINS" = "1" ]; then
|
|
echo "== brains (KEPT: CLAWMATES_KEEP_BRAINS=1)"
|
|
ssh "$HOST" "docker exec $SERVER sh -c 'ls /data/brains/*.h5 2>/dev/null | wc -l'" | sed 's/^/ files left: /'
|
|
else
|
|
echo "== brains"
|
|
ssh "$HOST" "docker exec $SERVER sh -c 'ls /data/brains/*.h5 2>/dev/null | wc -l'" | sed 's/^/ before: /'
|
|
ssh "$HOST" "docker exec $SERVER sh -c 'rm -f /data/brains/*.h5 /data/brains/*.h5.onion 2>/dev/null; ls /data/brains/*.h5 2>/dev/null | wc -l'" | sed 's/^/ after: /'
|
|
fi
|
|
|
|
echo "== session"
|
|
psql_ "delete from auth_sessions where token_hash='$hash' returning 1" | head -1 | sed 's/^/ removed: /'
|
|
|
|
echo "== after"
|
|
psql_ "select 'missions='||(select count(*) from missions)||' phases='||(select count(*) from mission_phases)||' events='||(select count(*) from mission_events)||' artifacts='||(select count(*) from mission_artifacts)||' evals='||(select count(*) from mission_phase_evaluations)||' agents='||(select count(*) from agents)||' episodes='||(select count(*) from podcast_episodes)||' usage='||(select count(*) from usage_events)||' corpus_KEPT='||(select count(*) from corpus_items)||' skills_KEPT='||(select count(*) from skills)"
|
|
ssh "$HOST" "docker ps -a --format '{{.Names}}' | grep -c 'cm-runtime-mission' || true" | sed 's/^/ mission containers left: /'
|