system-droid: вартовий, який сам перевіряє і сам лікує мої системи
Мої локальні сервіси ламалися тихо: бриф не приходив, токени протухали, бекап завмирав — а я дізнавався останнім. Тож я зробив дроїда на launchd, який кожні дві години все перевіряє, лагодить, що може, через агента Claude, і пише мені лише тоді, коли справді потрібна людина.
TL;DR
system-droid — це невелика програма на Go, яку launchd запускає кожні дві години і після кожного пробудження Mac. Вона проходить переліком перевірок по всіх моїх локальних сервісах, мовчить, поки все працює, і пише в Telegram лише тоді, коли щось справді зламалося. Раз на день надсилає короткий звіт «усе працює», щоб тиша ніколи не означала «невідомо». А коли щось таки падає — спершу намагається полагодити самотужки: окремий агент Claude (Opus) знаходить причину й усуває її, і тільки якщо не впорався — кличе мене.
◆ Усі системи в нормі · 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-сховища на GitHubНавіщо це все
За останні місяці в мене непомітно назбирався цілий парк локальної автоматизації: агент OpenClaw («Резонанс»), україномовний голосовий сервер StyleTTS2, LLM-проксі перед Claude, щоденні генератори нотаток в Obsidian, git-копія сховища, блокування корпоративної телеметрії у firewall. Кожна з цих речей корисна рівно доти, доки працює. А ламалися вони завжди однаково — тихо.
Ранковий бриф просто не приходив — і я помічав це аж увечері. Планувальник убивав процес, але останній запуск однаково виглядав «успішним». Токен авторизації протухав, і генерація тижнями могла жити на аварійній заглушці. Жодна з цих поломок не повідомляє про себе: сервіс не скаржиться — він просто зникає. Мені був потрібен хтось, хто помічатиме це замість мене.
Як воно влаштоване
Уся система — один бінарник і config.json з переліком перевірок двох типів: HTTP (сервіс відповідає на /health) та командних (скрипт завершився з кодом 0). launchd запускає дроїда за розкладом, той проходить перевірки паралельно й вирішує, чи варто мене турбувати. Поставити нову систему під нагляд — один рядок у конфігурації, без перекомпіляції.
Головний принцип — економія уваги. Жодних дашбордів, які треба відкривати, жодного потоку «все ок» щодві години. Повідомлень існує рівно три типи: щоденний підсумок, що все зелене; «зламалося — полагодив сам»; і «зламалося — потрібен ти». Усе інше — тиша.
Самолікування
Коли перевірка падає, дроїд не біжить одразу до мене. Спершу він запускає агента Claude з обмеженим набором інструментів і правом писати лише в певні теки. Перед кожною спробою робиться git-знімок усіх робочих директорій, тож будь-яку зміну агента можна відкотити однією командою — хеші знімків приходять просто в повідомленні:
🔧 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І це не іграшка: агент справді перегенеровував застарілий дайджест після нічного обриву мережі, сам перезавантажував завислу launchd-задачу і сам ставив діагноз, що проблема з токеном без мене не лікується. У повідомленні я бачу, що було зламано, що агент зробив і чим усе закінчилося.
Що він уже спіймав
За сім тижнів дроїд перетворив кілька тихих поломок — кожна з яких могла жити днями — на повідомлення, які я побачив одразу:
- Убитий генератор брифів. Ядро macOS прибило процес одразу після перезбирання бінарника — ранковий бриф просто не вийшов би, і ніхто б цього не помітив.
- Протухлий refresh-токен. Проксі перестав оновлювати токен доступу, і всі LLM-виклики поверталися з 401. Дроїд написав прямо: це лікується лише повторним входом — одна команда, дві хвилини.
- Заглушка замість аналітики. Генерація завершувалася «успішно», але замість AI-аналізу в нотатці стояв текст-заглушка. Перевірка вмісту нотатки, а не коду виходу, зловила це першого ж дня.
- Завислий бекап сховища. Git-плагін продовжував комітити локально, а push на GitHub мовчки помер — резервна копія застигла б, і з'ясувалося б це в найгірший можливий момент.
Чого він свідомо не лікує
Найважливіше рішення в цій системі — не що автоматизувати, а що ні. Частина перевірок позначена «лише сповістити»: усе, що стосується авторизації, чужих систем і сесій мого власного агента. Протухлий OAuth не «лагодиться» скриптом — його має перевипустити людина в браузері. Агентові прямо заборонено чіпати токени, перезапускати мої робочі сервіси й «підганяти» перевірки, аби вони позеленіли.
Є й фізична межа: лікар — це той самий Claude CLI, і коли падає мережа чи авторизація, він падає разом з усім. Те, що ламає систему, ламає і руки, якими її лагодять. Тому чесна поведінка в таких випадках — не героїчні спроби, а чітке «потрібен ти» з поясненням, що саме зробити.
Дрібниці, на яких усе трималося
Найбільше часу забрали не перевірки, а надійність самого вартового. Перше: перезбирання Go-бінарника на місці змінює його підпис, а launchd кешує вимоги до підпису — і наступний плановий запуск система просто вбиває. Тепер збирання завжди закінчується перезавантаженням задачі:
# Перезбирання бінарника на місці змінює його 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 # свіжий запуск має бути зеленимДруге: для сервісів із KeepAlive код виходу останнього запуску — оманлива метрика. Після кожного перезапуску там SIGTERM, і цілком живий сервіс виглядає зламаним. Правильне питання — не «як він завершився», а «чи працює він зараз»:
# У 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 1Третє: перевіряти треба саме той об'єкт, від якого залежить система. Моя перша перевірка токена рахувала «дата видачі плюс рік» — і сяяла зеленим, поки справжній токен уже добу віддавав 401:
# Календарна перевірка сяяла зеленим — річному токену ще жити й жити:
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."І четверте: нічний обрив мережі на один цикл — не привід будити людину. Тривога «потрібне втручання» тепер надсилається, лише якщо проблема пережила два запуски поспіль. Одноразові збої минають самі; справжні однаково доходять — просто на дві години пізніше:
// Тривога "потрібне втручання" — лише якщо перевірка провалилася
// два запуски поспіль. Нічний обрив мережі, демон посеред перезапуску,
// пробудження зі сну — все це минає само до наступного циклу,
// і будити людину через таке не можна. Успішне лікування,
// навпаки, повідомляється одразу — це добра новина, а не шум.
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) > 0Очі для іншого агента
Окрім Telegram, дроїд працює і як MCP-сервер. Це означає, що мій локальний агент може сам спитати його про стан систем: замість читати звіти, я кажу «перевір, чи все працює» — агент викликає get_health, проганяє всі перевірки наживо й відповідає. Моніторинг перестав бути сторінкою, на яку дивляться, і став сервісом, до якого звертаються.
# Дроїд працює і як 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-токен (валідність, авто-оновлення)
...Підсумок
Технічно тут немає нічого складного: Go, launchd, кілька shell-скриптів і бот у Telegram. Але саме ця проста зв'язка змінила відчуття від усього парку автоматизації: я перестав тримати в голові список речей, які «треба не забути перевірити». Тепер це робота дроїда.
Сім тижнів він здебільшого мовчить. Кожне його повідомлення за цей час було або доброю новиною про вже полагоджене, або точною вказівкою, де потрібні мої дві хвилини. Це і є визначення зручності: система, яка охороняє інші системи — і поважає твою тишу.
Помітили помилку?
Невірний факт, кривий переклад, щось звучить неправдиво в цій статті? Напишіть мені — своєю мовою.