Wzorzec mechaniczny: sekcje deploy/verify/install/testy wycinane do kb/runbooks/<serwis>-*.md, reszta zostaje dokumentem type: service. Wzajemne `links` w obie strony. Tresc sekcji nietknieta — przenoszone doslownie, dodany wylacznie naglowek H1 nowego runbooka. kb-query, paperless-worker, planner-agent, ha-diag-agent, ollama-piha, narty27, home-assistant, ha-mcp, job-gmail-header-backfill, job-mail-body-ingest. Weryfikacja: dla kazdego pliku multizbior niepustych linii (main + runbook) == oryginal z HEAD. Zero zgubionych, zero dodanych. Recon szacowal 13 splitow service+runbook; faktycznie 2-typowych jest 10, pozostale 5 (paperless, nextcloud, gokapi, fleet-prometheus, deploy-runner) sa 3-typowe i ida osobno jako splity wielotypowe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
153 lines
4.4 KiB
Markdown
153 lines
4.4 KiB
Markdown
---
|
||
okf: "0.1"
|
||
type: service
|
||
visibility: private
|
||
status: active
|
||
updated: 2026-05-27
|
||
links:
|
||
- ../runbooks/planner-agent-deploy.md
|
||
---
|
||
|
||
# planner-agent
|
||
|
||
Asynchroniczny agent diagnozujący zdarzenia zdrowotne w homelabowej infrastrukturze.
|
||
Nasłuchuje na kanałach Redis pub/sub, wysyła zdarzenie do LLM i zapisuje propozycję
|
||
akcji do `actions/pending/` — gdzie musi ją zaakceptować operator.
|
||
|
||
---
|
||
|
||
## Co robi
|
||
|
||
```
|
||
Redis pub/sub LLM (Ollama → Haiku → Sonnet)
|
||
health_events ──────────────► diagnoza zdarzenia
|
||
world_updates ──────────────► propozycja JSON
|
||
│
|
||
cooldown gate (5 min / svc_key)
|
||
│
|
||
actions/pending/<action_id>.json
|
||
│
|
||
events/<date>/<node>/evt-*.json
|
||
(typ: remediation_started)
|
||
```
|
||
|
||
**HITL invariant:** planner pisze wyłącznie do `actions/pending/`.
|
||
Executor wymaga pliku w `actions/approved/` — planner nigdy tego katalogu nie dotyka.
|
||
|
||
### Obsługiwane kanały
|
||
|
||
| Kanał | Źródło | Opis |
|
||
|-------|--------|------|
|
||
| `health_events` | node-agent, stability-agent | Zdarzenia zdrowotne kontenerów i systemu |
|
||
| `world_updates` | observer (control-plane) | Zmiany w world state |
|
||
|
||
### Benign events (pomijane bez wywołania LLM)
|
||
|
||
`service_healthy`, `service_recovered`, `node_online`, `deployment_completed`,
|
||
`deployment_started`, `remediation_started`, `remediation_completed`
|
||
|
||
---
|
||
|
||
## Fallback chain LLM
|
||
|
||
```
|
||
1. ollama/<OLLAMA_MODEL> timeout 8 s (lokalny GPU — SOLARIA)
|
||
2. claude-haiku-4-5-20251001 timeout 30 s (Anthropic cloud)
|
||
3. claude-sonnet-4-6 timeout 30 s (Anthropic cloud)
|
||
```
|
||
|
||
Model jest odrzucany, gdy:
|
||
- przekroczy timeout
|
||
- zwróci błąd sieci / API
|
||
- odpowie tekstem pasującym do wzorca odmowy (`"I cannot"`, `"nie wiem"`, …)
|
||
- zwróci JSON niezgodny ze schematem propozycji
|
||
|
||
Metryki każdego wywołania są publikowane na kanał Redis `llm_router_metrics`.
|
||
|
||
### Schemat propozycji (JSON Schema)
|
||
|
||
```json
|
||
{
|
||
"action": "restart | redeploy | notify | ignore",
|
||
"service": "<nazwa serwisu>",
|
||
"node": "<nazwa noda>",
|
||
"reason": "<wyjaśnienie, min. 10 znaków>",
|
||
"confidence": <0.0–1.0>,
|
||
"requires_human": <true|false>
|
||
}
|
||
```
|
||
|
||
Mapowanie na typ executora:
|
||
|
||
| LLM action | Executor type | Risk level |
|
||
|------------|--------------------|------------|
|
||
| restart | container_restart | low |
|
||
| redeploy | redeploy | guarded |
|
||
| notify | notify | low |
|
||
| ignore | *(nie zapisuje)* | — |
|
||
|
||
---
|
||
|
||
## Zmienne środowiskowe
|
||
|
||
Wszystkie zmienne runtime żyją w `/opt/homelab/config/planner-agent/.env` na węźle.
|
||
**Nie commituj tego pliku** — nie jest w repo.
|
||
|
||
```dotenv
|
||
# Ollama — lokalny GPU węzła (np. SOLARIA)
|
||
# Użyj host-gateway zamiast localhost (kontener nie może sięgnąć hosta przez localhost)
|
||
OLLAMA_HOST=http://host-gateway:11434
|
||
OLLAMA_MODEL=qwen2.5-coder:14b # dowolny model dostępny w ollama list
|
||
|
||
# Redis na piha
|
||
REDIS_URL=redis://100.108.208.3:6379
|
||
|
||
# Tożsamość noda
|
||
NODE_NAME=solaria
|
||
|
||
# Cooldown między propozycjami dla tego samego serwisu
|
||
COOLDOWN_SECONDS=300
|
||
|
||
# Ścieżka do runtime state
|
||
RUNTIME_PATH=/opt/homelab
|
||
|
||
# Opcjonalnie — wymagane do cloud fallback (haiku/sonnet)
|
||
# ANTHROPIC_API_KEY=sk-ant-...
|
||
```
|
||
|
||
`ANTHROPIC_API_KEY` jest jedyną zmienną przekazywaną przez sekcję `environment`
|
||
w docker-compose.yml (nie jest w env_file — sekret injektowany przez operatora).
|
||
|
||
---
|
||
|
||
## Healthcheck
|
||
|
||
Skrypt `healthcheck.sh` sprawdza czy plik heartbeat
|
||
`/opt/homelab/state/planner-agent.heartbeat` jest świeższy niż 300 s.
|
||
Heartbeat jest pisany na górze każdej iteracji pętli (≤5 s interwał).
|
||
|
||
```bash
|
||
# Ręczny test healthchecku
|
||
docker exec planner-agent /bin/sh /app/healthcheck.sh
|
||
```
|
||
|
||
---
|
||
|
||
## Struktura plików
|
||
|
||
```
|
||
services/planner-agent/
|
||
├── src/
|
||
│ ├── planner.py # Główna pętla agenta
|
||
│ └── llm_router.py # Routing LLM z fallback chain
|
||
├── tests/
|
||
│ ├── test_planner.py # 49 testów
|
||
│ └── test_llm_router.py # 34 testy
|
||
├── docker-compose.yml
|
||
├── Dockerfile
|
||
├── requirements.txt
|
||
├── service.yaml # Kontrakt operacyjny dla agentów
|
||
├── healthcheck.sh
|
||
└── README.md
|
||
```
|