--- 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/.json │ events///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/ 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": "", "node": "", "reason": "", "confidence": <0.0–1.0>, "requires_human": } ``` 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 ```