From eb91ae049666bb800a57598949a4142f079b6f3f Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Tue, 22 Sep 2026 14:51:06 -0500 Subject: [PATCH] feat(wipe): a mission wipe now clears the memory of those missions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz --- scripts/wipe-missions.sh | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 scripts/wipe-missions.sh diff --git a/scripts/wipe-missions.sh b/scripts/wipe-missions.sh new file mode 100755 index 0000000..e4a24aa --- /dev/null +++ b/scripts/wipe-missions.sh @@ -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.sobh@gmail.com}" +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: /'