Skip to main content
Tillbaka till bloggen
Claude CodeAIAutomationMonitoringlaunchdResilience

system-droid: en vakthund som själv kontrollerar och lagar mina system

Mina lokala tjänster dog alltid ljudlöst — morgonrapporten kom inte, tokens gick ut, backupen frös. Så jag byggde en launchd-droid som kontrollerar allt varannan timme, lagar det som riskfritt kan lagas via en Claude-agent och skriver till mig bara när en människa verkligen behövs.

Publicerad 17 augusti 202610 min läsning

TL;DR

system-droid är ett litet Go-program som launchd startar varannan timme och efter varje gång Macen vaknar ur viloläge. Det går igenom en lista med kontroller över alla mina lokala tjänster, håller tyst så länge allt fungerar och skriver till Telegram bara när något faktiskt har gått sönder. En gång om dagen skickar det en kort sammanfattning om att allt är grönt, så att tystnad aldrig betyder "okänt". Och när något väl går sönder försöker det först laga det på egen hand: en separat Claude-agent (Opus) spårar orsaken och åtgärdar den — och kallar på mig bara om den misslyckas.

◆ Усі системи в нормі · 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

Varför det finns

Under de senaste månaderna hade jag i det tysta samlat på mig en hel flotta av lokal automation: en OpenClaw-agent ("Resonance"), en ukrainsk StyleTTS2-röstserver, en LLM-proxy framför Claude, dagliga anteckningsgeneratorer för Obsidian, en git-kopia av mitt valv, en brandväggsregel som blockerar företagstelemetri. Var och en av dessa är användbar exakt så länge den körs. Och de gick alltid sönder på samma sätt — ljudlöst.

Morgonrapporten kom helt enkelt inte, och jag märkte det först på kvällen. Schemaläggaren dödade en process, men dess senaste körning såg ändå "lyckad" ut. En inloggningstoken gick ut, och genereringen kunde leva i veckor på en nödlösning. Inget av dessa fel ger sig till känna: en tjänst klagar inte — den försvinner bara. Jag behövde någon som märkte det åt mig.

Hur det fungerar

Hela systemet är en binär plus en config.json med två sorters kontroller: HTTP (tjänsten svarar på /health) och kommando (ett skript avslutas med kod 0). launchd startar droiden enligt schema; den kör kontrollerna parallellt och avgör om jag alls behöver störas. Att sätta ett nytt system under bevakning är en rad i konfigurationen — ingen omkompilering.

Grundprincipen är hushållning med uppmärksamhet. Inga instrumentpaneler att öppna, inget "allt väl"-spam varannan timme. Det finns exakt tre sorters meddelanden: den dagliga sammanfattningen att allt är grönt; "det gick sönder — jag lagade det"; och "det gick sönder — jag behöver dig". Allt annat är tystnad.

Självläkning

När en kontroll fallerar springer droiden inte direkt till mig. Först startar den en Claude-agent med en begränsad verktygslåda och skrivrättigheter bara i vissa kataloger. Före varje försök tas en git-ögonblicksbild av alla arbetskataloger, så att allt agenten ändrar kan rullas tillbaka med ett enda kommando — hasharna kommer direkt i meddelandet:

🔧 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

Och det är ingen leksak: agenten har på riktigt genererat om en inaktuell sammanfattning efter ett nattligt nätavbrott, själv laddat om ett fastnat launchd-jobb och själv ställt diagnosen att ett tokenproblem inte gick att lösa utan mig. I meddelandet ser jag vad som var trasigt, vad agenten gjorde och hur det slutade.

Vad den redan har fångat

På sju veckor har droiden förvandlat flera tysta haverier — vart och ett kunde ha levt i dagar — till meddelanden jag såg direkt:

  • En dödad rapportgenerator. macOS-kärnan sköt processen direkt efter en ombyggnad av binären — morgonrapporten hade helt enkelt aldrig gått ut, och ingen hade märkt något.
  • En utgången refresh-token. Proxyn slutade förnya sin åtkomsttoken och varje LLM-anrop kom tillbaka med 401. Droiden sa det rakt ut: det här löses bara med en ny inloggning — ett kommando, två minuter.
  • En platshållare i stället för analys. Genereringen avslutades "framgångsrikt", men anteckningen innehöll reservtext i stället för AI-analys. Att kontrollera innehållet, inte exitkoden, fångade det första dagen.
  • En fastfrusen valvbackup. Git-pluginet fortsatte committa lokalt medan pushen till GitHub tyst hade dött — säkerhetskopian hade frusit, och det hade upptäckts i sämsta tänkbara läge.

Vad den medvetet inte lagar

Det viktigaste beslutet i det här systemet är inte vad som ska automatiseras, utan vad som inte ska det. Vissa kontroller är märkta enbart-larm: allt som rör autentisering, andras system och min egen agents sessioner. En utgången OAuth "lagas" inte av ett skript — en människa måste utfärda den på nytt i webbläsaren. Agenten är uttryckligen förbjuden att röra tokens, starta om mina tjänster i drift eller fiffla med en kontroll tills den blir grön.

Det finns också en fysisk gräns: läkaren är själv en Claude-CLI, och när nätet eller inloggningen går ner, går den ner med allt annat. Det som knäcker systemet knäcker också händerna som lagar det. Ärligt beteende där är inte heroiska försök utan ett tydligt "jag behöver dig" — med exakt anvisning om vad som ska göras.

Småsakerna som allt vilade på

Det som tog mest tid var inte kontrollerna utan väktarens egen tillförlitlighet. För det första: att bygga om en Go-binär på plats ändrar dess signatur, och launchd cachar signaturkraven — så systemet dödar helt enkelt nästa schemalagda körning. Ett bygge slutar nu alltid med att jobbet laddas om:

deploy.sh
# Перезбирання бінарника на місці змінює його 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   # свіжий запуск має бути зеленим

För det andra: för KeepAlive-tjänster är den senaste körningens exitkod ett vilseledande mått. Efter varje omstart står det SIGTERM där, och en fullt levande tjänst ser trasig ut. Rätt fråga är inte "hur avslutades den" utan "kör den just nu":

check-launchd-running.sh
# У 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

För det tredje: kontrollera exakt det objekt systemet beror på. Min första tokenkontroll räknade "utfärdandedatum plus ett år" — och lyste grönt medan den riktiga token hade svarat 401 i ett dygn:

# Календарна перевірка сяяла зеленим — річному токену ще жити й жити:
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."

Och för det fjärde: ett nätavbrott på en enda cykel klockan tre på natten är ingen anledning att väcka en människa. Larmet "ingripande krävs" skickas nu bara om problemet överlever två körningar i rad. Engångsfel löser sig själva; riktiga når ändå fram — bara två timmar senare:

main.go
// Тривога "потрібне втручання" — лише якщо перевірка провалилася
// два запуски поспіль. Нічний обрив мережі, демон посеред перезапуску,
// пробудження зі сну — все це минає само до наступного циклу,
// і будити людину через таке не можна. Успішне лікування,
// навпаки, повідомляється одразу — це добра новина, а не шум.
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

Ögon åt en annan agent

Utöver Telegram kör droiden också som MCP-server. Det betyder att min lokala agent själv kan fråga den om systemens tillstånd: i stället för att läsa rapporter säger jag "kolla att allt fungerar" — agenten anropar get_health, kör varje kontroll live och svarar. Övervakningen slutade vara en sida man tittar på och blev en tjänst man rådfrågar.

# Дроїд працює і як 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-токен (валідність, авто-оновлення)
...

Summering

Tekniskt finns här inget svårt: Go, launchd, några skalskript och en Telegram-bot. Men just det enkla paketet förändrade känslan i hela automationsflottan: jag slutade bära på en mental lista över saker jag "inte får glömma att kolla". Det är droidens jobb nu.

Formeln är kort: kontrollera verkligt beteende, inte indirekta signaler; laga automatiskt det som är riskfritt att laga; kalla ärligt på människan där inget annat räcker; och var tyst resten av tiden.

I sju veckor har den mest varit tyst. Varje meddelande den ändå skickade var antingen goda nyheter om något redan lagat, eller en exakt anvisning om var mina två minuter behövdes. Det är vad bekvämlighet betyder: ett system som vaktar andra system — och respekterar din tystnad.

Hittade du ett fel?

Ett felaktigt faktum, en skev översättning, något som känns falskt i den här artikeln? Skriv till mig — på ditt eget språk.