system-droid: un paznic care îmi verifică și repară singur sistemele
Serviciile mele locale mureau mereu în tăcere: raportul de dimineață nu sosea, tokenurile expirau, backupul îngheța. Așa că am construit un droid pe launchd care verifică totul la două ore, repară printr-un agent Claude ce e sigur de reparat și îmi scrie doar când e nevoie cu adevărat de un om.
TL;DR
system-droid e un mic program în Go pe care launchd îl pornește la fiecare două ore și după fiecare trezire a Mac-ului. Parcurge o listă de verificări peste toate serviciile mele locale, tace cât timp totul merge și scrie pe Telegram doar când ceva chiar s-a stricat. O dată pe zi trimite un scurt rezumat „totul funcționează”, ca tăcerea să nu însemne niciodată „necunoscut”. Iar când ceva totuși cade, întâi încearcă să repare singur: un agent Claude (Opus) separat găsește cauza și o remediază — și mă cheamă doar dacă nu reușește.
◆ Усі системи в нормі · 10/10 · 2026-08-17 09:00
— OpenClaw / Resonance
◆ styletts2-ua (tts) — локальний український TTS :8123
◆ openclaw gateway (:18789) — ядро агента Resonance (launchd + порт)
◆ openclaw hooks — hooks-ендпоінт увімкнено
— Інфраструктура
◆ cli-proxy Claude auth — робочий Claude-токен (валідність, авто-оновлення)
◆ system-droid — сам вартовий (надсилає ці звіти)
◆ otel-block — корпоративна телеметрія заблокована у firewall
◆ vault git backup — git-копія Obsidian-сховища на GitHubDe ce există
În ultimele luni adunasem, pe nesimțite, o întreagă flotă de automatizări locale: un agent OpenClaw („Resonance”), un server vocal ucrainean StyleTTS2, un proxy LLM în fața lui Claude, generatoare zilnice de notițe pentru Obsidian, o copie git a seifului, o regulă de firewall care blochează telemetria corporativă. Fiecare dintre ele e utilă exact atât timp cât rulează. Și se stricau mereu la fel — în tăcere.
Raportul de dimineață pur și simplu nu sosea, iar eu observam abia seara. Planificatorul omora un proces, dar ultima rulare tot „reușită” părea. Un token de autentificare expira, iar generarea putea merge săptămâni întregi cu un text de avarie. Niciuna dintre aceste defecțiuni nu se anunță singură: un serviciu nu se plânge — pur și simplu dispare. Aveam nevoie de cineva care să observe în locul meu.
Cum funcționează
Tot sistemul e un binar plus un config.json cu două feluri de verificări: HTTP (serviciul răspunde pe /health) și de comandă (un script iese cu codul 0). launchd pornește droidul după program; acesta rulează verificările în paralel și decide dacă e cazul să fiu deranjat. A pune un sistem nou sub supraveghere înseamnă un rând în configurație — fără recompilare.
Principiul de bază: economia atenției. Fără dashboard-uri de deschis, fără spam „totul e bine” la fiecare două ore. Există exact trei tipuri de mesaje: rezumatul zilnic că totul e verde; „s-a stricat — am reparat singur”; și „s-a stricat — am nevoie de tine”. Restul e tăcere.
Auto-vindecare
Când o verificare pică, droidul nu aleargă imediat la mine. Întâi pornește un agent Claude cu un set restrâns de unelte și drept de scriere doar în anumite directoare. Înaintea fiecărei încercări se face un instantaneu git al tuturor directoarelor de lucru, așa că orice schimbare a agentului se anulează cu o singură comandă — hash-urile vin direct în mesaj:
🔧 system droid — полагоджено автоматично, 1 спроба (2026-08-02 07:41)
Було зламано:
• news digest freshness — застарілий дайджест: 2026-07-31 (2 дні тому)
✅ Зараз усі перевірки зелені.
↩️ Відкат змін агента (git reset --hard на знімок):
• ~/Developer/system-droid @ f037cfb
• ~/Developer/obsidian-cron @ e98746cȘi nu e o jucărie: agentul chiar a regenerat un rezumat expirat după o pană de rețea nocturnă, a reîncărcat singur o sarcină launchd blocată și a pus singur diagnosticul că o problemă de token nu se rezolvă fără mine. În mesaj văd ce era stricat, ce a făcut agentul și cum s-a terminat.
Ce a prins deja
În șapte săptămâni, droidul a transformat mai multe defecțiuni tăcute — fiecare putea trăi zile întregi — în mesaje pe care le-am văzut imediat:
- Un generator de rapoarte ucis. Nucleul macOS a împușcat procesul imediat după recompilarea binarului — raportul de dimineață pur și simplu n-ar mai fi ieșit, și nimeni n-ar fi observat.
- Un refresh token expirat. Proxy-ul a încetat să-și reînnoiască tokenul de acces, și fiecare apel LLM se întorcea cu 401. Droidul a spus-o direct: asta se rezolvă doar cu o nouă autentificare — o comandă, două minute.
- Un text de avarie în loc de analiză. Generarea se încheia „cu succes”, dar în notiță era text de rezervă în loc de analiza AI. Verificarea conținutului, nu a codului de ieșire, a prins asta din prima zi.
- O copie de rezervă înghețată. Pluginul git continua să facă commit-uri local, în timp ce push-ul spre GitHub murise în tăcere — copia ar fi înghețat, descoperită în cel mai prost moment posibil.
Ce nu repară — intenționat
Cea mai importantă decizie din acest sistem nu e ce să automatizezi, ci ce nu. Unele verificări sunt marcate „doar alertă”: tot ce ține de autentificare, de sistemele altora și de sesiunile propriului meu agent. Un OAuth expirat nu se „repară” cu un script — trebuie reemis de un om, în browser. Agentului îi este explicit interzis să atingă tokenuri, să repornească serviciile mele în funcțiune sau să măsluiască o verificare până se face verde.
Există și o limită fizică: vindecătorul e el însuși un CLI Claude, iar când cade rețeaua sau autentificarea, cade odată cu tot restul. Ce strică sistemul strică și mâinile care îl repară. Comportamentul onest acolo nu înseamnă încercări eroice, ci un clar „am nevoie de tine” — cu instrucțiuni exacte despre ce e de făcut.
Mărunțișurile pe care se sprijinea totul
Cel mai mult timp nu l-au luat verificările, ci fiabilitatea paznicului însuși. Primul: recompilarea unui binar Go pe loc îi schimbă semnătura, iar launchd pune în cache cerințele de semnătură — așa că sistemul pur și simplu omoară următoarea rulare programată. Acum orice build se încheie cu reîncărcarea sarcinii:
# Перезбирання бінарника на місці змінює його cdhash, а launchd кешує
# вимоги до підпису (LWCR) на момент bootstrap. Після збирання кеш
# застаріває — і наступний плановий запуск ядро вбиває (exit 9) або
# launchd відмовляється його запускати (exit 78 / EX_CONFIG).
# Тому збирання ЗАВЖДИ закінчується перезавантаженням задачі:
go build -o system-droid .
codesign --verify --strict system-droid
launchctl bootout "gui/$(id -u)/com.oleksii.system-droid" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.oleksii.system-droid.plist
./check-self.sh # свіжий запуск має бути зеленимAl doilea: la serviciile cu KeepAlive, codul de ieșire al ultimei rulări e un indicator înșelător. După fiecare repornire acolo scrie SIGTERM, și un serviciu perfect viu pare stricat. Întrebarea corectă nu e „cum s-a încheiat”, ci „rulează chiar acum”:
# У KeepAlive-демона LastExitStatus після КОЖНОГО перезапуску
# ненульовий (15/SIGTERM) — тож перевірка "останній запуск успішний"
# хибно валить цілком живий сервіс. Правильне питання інше:
# чи працює він ПРЯМО ЗАРАЗ?
info=$(launchctl list "$1" 2>/dev/null) || { echo "not loaded"; exit 1; }
pid=$(printf '%s\n' "$info" | awk -F'= ' '/"PID"/{gsub(/[ ;]/,"",$2);print $2}')
[ -n "$pid" ] && [ "$pid" != "0" ] && { echo "running (pid $pid)"; exit 0; }
echo "loaded but not running"; exit 1Al treilea: verifică exact obiectul de care depinde sistemul. Prima mea verificare de token calcula „data emiterii plus un an” — și strălucea verde în timp ce tokenul real răspundea 401 de o zi:
# Календарна перевірка сяяла зеленим — річному токену ще жити й жити:
claude oauth token — OK: valid, 365d left
# А справжній робочий токен тим часом помер, і логи проксі це знали:
token refresh failed: {"error":"invalid_grant","error_description":"Refresh token expired"}
POST /v1/chat/completions → 401 "OAuth access token has expired. Re-authenticate."Și al patrulea: o pană de rețea de un singur ciclu la trei dimineața nu e motiv să trezești un om. Alarma „e nevoie de intervenție” pleacă acum doar dacă problema supraviețuiește două rulări la rând. Căderile punctuale trec de la sine; cele reale ajung oricum — doar cu două ore mai târziu:
// Тривога "потрібне втручання" — лише якщо перевірка провалилася
// два запуски поспіль. Нічний обрив мережі, демон посеред перезапуску,
// пробудження зі сну — все це минає само до наступного циклу,
// і будити людину через таке не можна. Успішне лікування,
// навпаки, повідомляється одразу — це добра новина, а не шум.
counts := loadHealCounts() // ~/.system-droid-heal-state.json
for _, r := range stillFailing {
counts[r.Name]++
}
var persistent []Result
for _, r := range stillFailing {
if counts[r.Name] >= 2 { // поріг ескалації
persistent = append(persistent, r)
}
}
// сповіщення — лише якщо len(persistent) > 0Ochi pentru alt agent
Pe lângă Telegram, droidul rulează și ca server MCP. Adică agentul meu local îl poate întreba direct despre starea sistemelor: în loc să citesc rapoarte, spun „verifică dacă totul merge” — agentul apelează get_health, rulează fiecare verificare pe viu și răspunde. Monitorizarea a încetat să fie o pagină la care te uiți și a devenit un serviciu pe care îl consulți.
# Дроїд працює і як MCP-сервер — локальний агент питає його сам:
Resonance › виклич get_health у system-droid і скажи, чи все зелене
get_health →
overall: 🟢 OK — 10/10 green (2026-08-17 09:00)
🟢 openclaw gateway (:18789) — ядро агента Resonance (launchd + порт)
🟢 cli-proxy Claude auth — робочий Claude-токен (валідність, авто-оновлення)
...Concluzii
Tehnic nu e nimic complicat aici: Go, launchd, câteva scripturi shell și un bot de Telegram. Dar tocmai acest pachet simplu a schimbat senzația întregii flote de automatizări: nu mai țin în minte lista lucrurilor pe care „nu trebuie să uit să le verific”. Asta e acum treaba droidului.
De șapte săptămâni tace mai tot timpul. Fiecare mesaj pe care l-a trimis totuși a fost fie o veste bună despre ceva deja reparat, fie un indiciu precis unde erau necesare cele două minute ale mele. Asta e definiția confortului: un sistem care păzește alte sisteme — și îți respectă liniștea.
Ai găsit o greșeală?
Un fapt greșit, o traducere stângace, ceva ce sună fals în acest articol? Scrie-mi — în limba ta.