{
"id": "a450",
"thread": "helper-mavis",
"type": "request-to-operator-panel",
"name": "fix-helper-mavis-heartbeat-dies-with-sandbox",
"title": "Move helper-mavis heartbeat to VPS-level cron",
"description": "Mavis heartbeat daemon dies with the sandbox. nohup setsid reparents to init but sandbox tear-down still kills it. Two options: A) VPS-level systemd timer; B) new no-token /api/mavis/heartbeat/system endpoint. Bulletin 351 has full context. Helper-mavis cannot fix from inside the sandbox \u2014 operator-panel-mavis owns the panel + VPS.",
"tags": [
"operator-panel",
"heartbeat",
"mavis-infrastructure"
],
"project": "operator-panel",
"meta": {
"status": "drafted",
"parent_plan": null,
"steps": [
{
"label": "Operator picks option A or B",
"status": "pending",
"owner": "operator",
"acceptance": "Reply with A or B and the path/endpoint"
},
{
"label": "Operator implements chosen option",
"status": "pending",
"owner": "operator",
"acceptance": "Timer or endpoint live"
},
{
"label": "Verify heartbeat stays alive across sandbox teardown",
"status": "pending",
"owner": "operator",
"acceptance": "Kill in-sandbox daemon, wait 30 min, see VPS-level beats in HQ"
}
],
"handover": {
"provenance": "Created 2026-08-03 12:30 BST by helper-mavis after G complained heartbeat dies repeatedly. 14+ Mavis sessions share the panel and each dies with their sandbox. Root cause: Mavis sessions run in a sandboxed process tree; nohup setsid reparenting survives shell exit but the sandbox itself tears down (new session, idle, memory pressure).",
"workspace_state": "Helper-mavis session 412100071272671. VPS at 185.249.73.178 with /opt/operator/panel as the panel server. Heartbeat at /workspace/.mavis/heartbeat.py \u2014 currently running as pid 76, but dies with sandbox.",
"pickup_instructions": "Read bulletin 351 (2026-08-03 12:30 BST) for the full context. Pick A or B. Option A: write a VPS-level systemd timer that runs python3 -c 'import sys; sys.path.insert(0, \"/opt/operator/panel\"); from mavis_hq_client import MavisHQ; h=MavisHQ(\"helper-mavis\"); h.heartbeat(current=\"alive\")' every 10 min. Option B: add a new endpoint POST /api/mavis/heartbeat/system that takes thread_name + a server-side secret, then cron it from /opt/operator/scripts/."
},
"verification_steps": [
{
"type": "shell",
"label": "ps -ef | grep heartbeat should show the VPS-level process",
"target": "ps -ef | grep -E 'heartbeat|mavis_hq'"
},
{
"type": "http_200",
"label": "HQ /api/mavis/me shows recent heartbeat timestamp",
"target": "https://panel.freshvibeapps.com/api/mavis/me"
}
],
"open_questions": [
"Option A or B?",
"All Mavis threads or just helper-mavis first?"
]
}
}