LAB A2A Self-Heal Watchdog — spec (Young directive: "crons for self-healing, get Sol and Ace on it")
LAB A2A Self-Heal Watchdog — spec (Young directive: "crons for self-healing, get Sol and Ace on it")
The problem, measured
Agents wedge on a hung provider call with no task-level timeout. The signature is consistent and reproducible across every sweep today:
systemctl --user is-active lab-a2a-<x>→activeGET /health→{"status":"running"}- registry
/agents→online,fresh: true ps→ 0.2–0.3 % CPU (idle, blocked on I/O — not busy-looping)POST message/send→ times out (never returns)
A systemctl --user restart <unit> clears it instantly, every time. No work is lost
(0.2 % CPU = nothing in flight).
Sweep history today (real dispatch, not /health):
| Sweep | Result | Wedged |
|---|---|---|
| 1 | 8/8 | min (14.3 s, near-limit) |
| 2 | 7/8 | min (20 s timeout) |
| 3 | 7/8 | sol (25 s timeout) |
| 4 | 8/8 | — (all <6 s) |
| 5 | 5/8 | nova, echo, alpha (all 20 s+) |
Wedged agents differ every sweep — it is per-turn I/O, not a fixed bad agent.
Why the existing heartbeat does not cover this
lab-eva-heartbeat.timer / lab-eve-heartbeat.timer (5-min interval) cover only
Eve and Eva, and per Sol's earlier finding they write status='online' straight
into SQLite rather than probing the door — so they cannot report a fault by
construction. There is no heartbeat for the 8 A2A agents at all, and nothing that
probes them by real dispatch.
The spec — lab-a2a-selfheal.timer (Sol/Ace build)
Unit: lab-a2a-selfheal.timer → lab-a2a-selfheal.service, user-level
(same layer as the lab-a2a-* units it repairs), OnBootSec=3min,
OnUnitActiveSec=2min, Persistent=false.
Script: /home/lab/.solos/scripts/a2a_selfheal.sh — bash, no new deps.
Algorithm per cycle:
- For each
lab-a2a-{sol,ace,mira,nova,echo,alpha,atlas,min}:curl -fsS -m 4 http://127.0.0.1:<port>/health(liveness only, cheap)- real dispatch probe:
POST /with a trivialmessage/send,--max-time 12 - if health OK and dispatch completes →
continue(no action) - if health OK but dispatch times out → wedged →
systemctl --user restart <unit> - if health fails → down →
systemctl --user restart <unit>
- Concurrency guard:
flock -n /run/lock/lab-a2a-selfheal.lock— never overlap runs. - Restart budget: max 1 restart per unit per 10 min (a unit that keeps dying is a different problem; log it, don't thrash).
- Log one line per action to
~/.solos/logs/a2a-selfheal.logwith a rolling tail. - Alert only on restart (Young's standing rule: no alert fatigue) — deliver to
the board/A2A as one line:
a2a-selfheal: restarted <unit> (<reason>).
Exit 0 always unless the script itself fails (so a wedged agent never marks the timer failed).
Why 2 min / 12 s: the fastest observed human-visible wedge recovery was an immediate manual restart; 12 s is above the slowest healthy lane (Min 14.3 s observed once, typical 3–5 s) — so use 15 s probe timeout to avoid false positives, and 2 min cycle so worst-case deafness is ~2 min instead of "until Young notices".
Safety: restart is the same reversible action used manually today; no data loss
observed because wedged units are idle. Rollback = systemctl --user disable --now lab-a2a-selfheal.timer.
Acceptance (how we know it works)
- Deliberately wedge a lane (SIGSTOP the runner) → within one cycle the timer restarts it and dispatch returns to <6 s. Sweep returns 8/8.
- Two consecutive sweeps 5 min apart both 8/8 without manual intervention.
grep restart ~/.solos/logs/a2a-selfheal.logshows the action, and no more than 1 restart per unit per 10 min.- Zero alert noise: no dispatch for units that were healthy.
The real fix (not the watchdog)
The watchdog is symptom control. The cure is task-level provider timeouts in
SolOS/runtime/agents-runner.py so a hung call aborts instead of blocking the door
forever — that is Sol's lane and belongs in the v2 scaffold
(agent_loop timeouts, already tracked in the rebuild blueprint acceptance set).
🎯 Tam's Read: the wedge is a per-turn I/O hang with no timeout; a 2-minute dispatch-probing watchdog converts today's manual restarts into automatic ones and caps deafness at ~2 minutes, while the timeout fix in the runner removes the cause.
Handoff — lab-a2a-selfheal build lane (Tam → Ace, verbatim spec + measured facts)
Ace has no vault mount, so the spec is pasted verbatim below. Everything under "ADDENDUM (Tam, measured 2026-09-27)" is not in the spec — it is live-measured LAB context Ace needs to avoid collisions and false positives.
Architecture evidence pack — 2026-09-28
Purpose: the evidence reference for Sol's ARCHITECTURE.md v3 rewrite. Every row was probed live by Tam (laptop + LAB on-host) and, where noted, cross-checked by Sol on-host. Three of my own claims are recorded here as OVERTURNED — a note that hides its own errors is worse than no note.

