sweep-ace-web
Bulk-delete uploaded chat sessions from a deployed ace-web, after a report and your approval.
SKILL.mdskills/sweep-ace-web/SKILL.md
GitHubsweep-ace-web
Bulk wipe, not orphan sweep. Lists every Session in workspaces where the
caller is Owner or Editor on the deployed ace-web, prints the plan grouped by
workspace, prompts the human, then deletes all approved ids in one call.
CASCADE on the ace-web side handles IngestUpload, Message,
SessionParticipant, ShareToken, and Draft rows.
This skill does not consult the live-set. The user has explicitly opted into wiping all visible sessions; opp-orphan filtering is not its job.
Inputs
ACE_WEB_BASE_URL— env var; deployed ace-web base URL (no trailing slash). Defaults tohttps://labs.connect.dimagi.com/aceto match--ace-web-urlelsewhere in the plugin.ACE_WEB_PAT_TOKEN— env var; ACE's own ace-web token by default (written by/ace:setup), or yours if you minted one with/ace:ace-web-pat-mint. The sweep is scoped to whichever workspaces that identity belongs to on the ace-web side.sweepFolder— timestamped sweep folder created by the orchestrator (e.g.ACE/_sweep/<timestamp>/). Products land here.mode—recommend(default) |execute. Inrecommendthe skill is report-only: list + render the plan + return it; no delete call. Inexecuteit deletes onlyapprovedIds. The human-confirmation gate between the two is the orchestrator's job — seeagents/sweep.md § Human-confirmation gate.approvedIds(executemode only) — the exact session ids the human approved in chat. Delete nothing outside this set.
No liveSetPath input. If the orchestrator built one for a sibling sweep,
ignore it.
Products
<sweepFolder>/ace-web-sessions.md— human-readable plan grouped by workspace, with totals at the top.<sweepFolder>/ace-web-sessions.yaml— machine-readable session list (for replay).<sweepFolder>/ace-web-sessions-result.yaml— written post-execution withdeletedcount andfailed[]rows (if any).
Process
Verify preconditions.
ACE_WEB_PAT_TOKENset; if missing, stop and instruct the operator to run/ace:ace-web-pat-mint.ACE_WEB_BASE_URLresolved (env var or default). Strip any trailing slash.sweepFolderprovided by the orchestrator. If absent, abort — the skill must not invent its own.
GET
<ACE_WEB_BASE_URL>/api/sessions/sweepwith-H "Authorization: Bearer $ACE_WEB_PAT_TOKEN". Expect 200. Response shape:sessions: - id: int slug: str title: str source: "web" | "upload" status: "active" | "archived" | "imported" opp_slug: str # may be "" opp_run_id: str # may be "" workspace_slug: str message_count: int upload_count: int created_at: ISO-8601 updated_at: ISO-8601 total_raw_bytes: intOn 401, instruct the operator to re-mint the PAT. On any other non-200, print the body and abort.
Render the report. Group rows by
workspace_slug. Per-workspace section shows: row count, source breakdown (uploadvswebcount), opp linkage breakdown (with-opp vs unlinked count), and a short table of the first 30 rows for that workspace (slug, source, opp_slug, title, updated_at). Headline: "ace-web sweep plan — N sessions across M workspaces; T MB of raw transcripts."drive_create_fileto<sweepFolder>/ace-web-sessions.mdand the YAML peerace-web-sessions.yaml. The YAML must contain every row in full (not truncated) so a replay knows exactly which ids to send.Recommend — stop here in
mode: recommend. Return the plan to the orchestrator: the full session-id list (this is a bulk wipe — every session the PAT can write to, not an orphan-filtered subset; say so plainly so the human understands the scope) + per-workspace counts + the report Drive link + the reversibility note (delete CASCADEs throughIngestUpload,Message,SessionParticipant,ShareToken,Draft— not reversible). Issue no delete call. Do not try to prompt the human from this skill — a dispatched subagent can't reach them; the orchestrator runs the confirmation gate (seeagents/sweep.md § Human-confirmation gate). If the human declines at that gate, the orchestrator never dispatches the execute pass.
Execute phase (mode: execute only)
Runs only when the orchestrator re-dispatches with mode: execute +
approvedIds (the session ids the human approved in chat — typically all,
but honor a subset if that's what was approved).
POST
<ACE_WEB_BASE_URL>/api/sessions/sweep/deletewith-H "Content-Type: application/json",-H "Authorization: Bearer $ACE_WEB_PAT_TOKEN", and{"session_ids": [<approvedIds>...]}as the body. Expect 200. Response shape:deleted: int failed: - session_id: int reason: "not_found" | "forbidden" | "db_error: <msg>"Write the response to
<sweepFolder>/ace-web-sessions-result.yamlwithdecision: "approved"for downstream summary.Report. Return a short summary block:
ace-web sweep — N approved, K deleted, F failed deleted: K sessions across <M> workspaces failed: F items (see ace-web-sessions-result.yaml)
Shell reference
[ -n "$ACE_WEB_PAT_TOKEN" ] || { echo "ACE_WEB_PAT_TOKEN not set; run /ace:ace-web-pat-mint"; exit 2; }
BASE_URL="${ACE_WEB_BASE_URL:-https://labs.connect.dimagi.com/ace}"
BASE_URL="${BASE_URL%/}"
# Scratch response files: `mktemp`, never a fixed /tmp literal (ace#1046) —
# /tmp is shared across macOS users, so `-o` can fail EACCES while the
# follow-up `cat` silently returns another session's response body.
LIST_JSON="$(mktemp "${TMPDIR:-/tmp}/ace-sweep-list-XXXXXX.json")"
DEL_JSON="$(mktemp "${TMPDIR:-/tmp}/ace-sweep-del-XXXXXX.json")"
# 1. List
HTTP=$(curl -sS -o "$LIST_JSON" -w '%{http_code}' \
-X GET "$BASE_URL/api/sessions/sweep" \
-H "Authorization: Bearer $ACE_WEB_PAT_TOKEN")
[ "$HTTP" = "200" ] || { echo "list $HTTP"; cat "$LIST_JSON"; exit 3; }
# 2. (Render + approval handled by the agent calling drive_create_file)
# 3. Delete — agent constructs IDS from approved list
HTTP=$(curl -sS -o "$DEL_JSON" -w '%{http_code}' \
-X POST "$BASE_URL/api/sessions/sweep/delete" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACE_WEB_PAT_TOKEN" \
-d "{\"session_ids\": [$IDS]}")
[ "$HTTP" = "200" ] || { echo "delete $HTTP"; cat "$DEL_JSON"; exit 4; }
Failure modes
| HTTP | Cause | Remedy |
|---|---|---|
| 401 | ACE_WEB_PAT_TOKEN missing/revoked |
Re-run /ace:ace-web-pat-mint. |
| 422 | Body shape invalid (non-int ids) | Verify the YAML round-trip; ids must be JSON integers. |
200 with failed[] populated |
Per-row failures (e.g. session removed between list and delete, or workspace role changed mid-sweep) | Treat not_found as success-equivalent for idempotency. Re-run on db_error. forbidden means the user's role changed during the sweep. |
Implementation notes for agents
Sessionis the only ORM aggregate root. The ace-web endpoint relies on Django'son_delete=CASCADEto dropIngestUpload,Message,Draft,SessionParticipant, andShareTokenrows. Do not attempt to delete those individually.- Workspace scoping happens server-side via the PAT's user's
WorkspaceMembershiprows. The skill never sends a workspace filter — the server already knows what the caller can write to. - The list endpoint is unpaginated by design. If a deployment ever accumulates enough sessions to make a single 200 unwieldy, surface that back to the ace-web team rather than papering over it client-side.
total_raw_bytesis summed fromIngestUpload.raw_bytesserver-side. Display as MB in the report headline (bytes / 1048576).
Related skills
sweep-opp-runsis the other sweep that ignores the live-set (retention, not orphan). Same shape: list → render → approve → execute.upload-transcriptis the inverse atom — it's how the sessions wiped by this skill got there in the first place.