feat(wipe): a mission wipe now clears the memory of those missions
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
This commit is contained in:
co-authored by
Claude Opus 5
parent
162506cd53
commit
eb91ae0496
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/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: /'
|
||||
Reference in New Issue
Block a user