Files
clawmates/scripts/wipe-missions.sh
T
Omar SobhandClaude Opus 5 eb91ae0496
deploy / test (push) Successful in 4m58s
deploy / build (push) Successful in 1m10s
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
2026-09-22 14:51:06 -05:00

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: /'