build(agents): add stale supervisor cleanup at startup to prevent duplicates

When the product-builder is interrupted mid-run (Ctrl+C, crash, timeout),
supervisor sessions launched via prompt_async survive independently on the
OpenCode server. Restarting the product-builder without cleanup creates
duplicate supervisors — 22 agents competing for the same work, causing
duplicate PR reviews, conflicting git pushes, and wasted resources.

Added Phase C.0 (runs before planning or launching) that:
1. Queries GET /session for all server sessions
2. Filters for sessions with titles starting with "[CA-AUTO] supervisor:"
3. Aborts any that are still active via POST /session/:id/abort
4. Deletes the stale sessions via DELETE /session/:id
5. Cleans up the /tmp/ca-supervisor-sessions.env tracking file
6. Logs the cleanup count to the session state issue

Also changed the supervisor session title convention from
"supervisor: <name>" to "[CA-AUTO] supervisor: <name>" to reliably
distinguish product-builder-managed sessions from user-created ones.
This commit is contained in:
2026-04-02 17:37:12 +00:00
parent 2a266929fd
commit 79403188f6
+50 -1
View File
@@ -359,6 +359,55 @@ milestones = list of all milestones to complete (ordered)
ref_summary = result from ca-ref-reader
SERVER = "http://localhost:4096"
# ── PHASE C.0: Clean Up Stale Supervisor Sessions ───────────────
# If the product-builder was previously interrupted (Ctrl+C, crash,
# session timeout, etc.), old supervisor sessions launched via
# prompt_async may still be running on the server. They are
# independent sessions that survive the product-builder's death.
#
# We MUST find and abort them before launching fresh supervisors,
# otherwise there will be duplicate supervisors competing for the
# same work — duplicate PR reviews, conflicting git pushes, etc.
#
# Identification: All supervisor sessions have titles starting with
# "[CA-AUTO] supervisor:". This prefix is unique to product-builder-
# managed supervisors and will not match user-created sessions.
# Step 1: Query all sessions from the server
ALL_SESSIONS=$(bash("curl -s ${SERVER}/session", timeout=30000))
# Step 2: Extract sessions whose title starts with "[CA-AUTO] supervisor:"
# Use python3 to parse JSON and filter:
STALE_IDS=$(bash("echo '${ALL_SESSIONS}' | python3 -c \"
import sys, json
sessions = json.loads(sys.stdin.read())
for s in sessions:
if s.get('title', '').startswith('[CA-AUTO] supervisor:'):
print(s['id'])
\"", timeout=30000))
# Step 3: Abort and delete each stale session
stale_count = 0
for session_id in STALE_IDS (one per line):
# Abort if still running
bash("curl -s -X POST ${SERVER}/session/${session_id}/abort", timeout=15000)
# Delete the session entirely to avoid clutter
bash("curl -s -X DELETE ${SERVER}/session/${session_id}", timeout=15000)
stale_count += 1
# Step 4: Clean up tracking file from previous run
bash("rm -f /tmp/ca-supervisor-sessions.env", timeout=5000)
# Step 5: Log cleanup results
if stale_count > 0:
invoke ca-session-persister with:
checkpoint: "Phase C.0: Cleaned up <stale_count> stale supervisor
sessions from a previous interrupted run.
All old supervisors aborted and deleted."
else:
# No stale sessions — first run or clean shutdown previously.
pass
# ── PHASE C.1: Planning (if needed) ─────────────────────────────
# If there are milestones with no issues yet, plan them first.
# This is the ONLY step that uses the Task tool (one-shot planners).
@@ -411,7 +460,7 @@ function launch_supervisor(agent_name, display_name, prompt_text):
# Step 1: Create a session
SESSION_ID=$(curl -s -X POST "${SERVER}/session" \
-H "Content-Type: application/json" \
-d "{\"title\": \"supervisor: ${display_name}\"}" \
-d "{\"title\": \"[CA-AUTO] supervisor: ${display_name}\"}" \
| python3 -c "import sys,json; print(json.loads(sys.stdin.read())['id'])")
# Step 2: Fire-and-forget launch via prompt_async