homelab-codex-ws/services/planner-agent/README.md
Oskar Kapala 5ccdfa0ca6 docs: add planner-agent docs and session summary 2026-05-27
- services/planner-agent/README.md: full service doc (what it does,
  LLM fallback chain, env vars, deploy steps, local run, redis-cli
  end-to-end test, healthcheck)
- README.md: add Agent System section with all agents and their roles
- docs/sessions/2026-05-27-planner-agent.md: session summary (built
  files, architectural decisions, problems + solutions, deployment
  status, pending work)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 22:35:59 +02:00

236 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.01.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).
---
## Deployment na SOLARIA
```bash
# 1. Przygotuj .env na solaria
ssh oskar@100.100.231.104
mkdir -p /opt/homelab/config/planner-agent
cat > /opt/homelab/config/planner-agent/.env << 'EOF'
OLLAMA_HOST=http://host-gateway:11434
OLLAMA_MODEL=qwen2.5-coder:14b
REDIS_URL=redis://100.108.208.3:6379
NODE_NAME=solaria
COOLDOWN_SECONDS=300
RUNTIME_PATH=/opt/homelab
EOF
# 2. Deploy przez standardowy skrypt (z SATURN)
scripts/deploy/deploy.sh --service planner-agent
# 3. Weryfikacja
docker ps --filter name=planner-agent
docker logs planner-agent --tail 30
ls -la /opt/homelab/state/planner-agent.heartbeat
```
> **Uwaga:** stage `verify` w deploy.sh może failować bezpośrednio po starcie
> (race condition — heartbeat jest pisany co ~5 s). Kontener jest zdrowy gdy
> `docker ps` pokazuje `(healthy)`.
---
## Uruchamianie lokalne (bez Dockera)
```bash
cd services/planner-agent
# Zainstaluj zależności
pip install -r requirements.txt
# Wyeksportuj zmienne (lub stwórz .env i użyj `export $(cat .env | xargs)`)
export REDIS_URL=redis://localhost:6379
export OLLAMA_HOST=http://localhost:11434
export OLLAMA_MODEL=qwen2.5:7b
export NODE_NAME=dev-local
export RUNTIME_PATH=/tmp/homelab-dev
export COOLDOWN_SECONDS=10
# Uruchom
python src/planner.py
```
---
## Testowanie
### Testy jednostkowe
```bash
cd services/planner-agent
pip install -r requirements.txt pytest pytest-asyncio
pytest tests/ -v
# 49 testów planner + 34 testy llm_router
```
### Ręczny end-to-end test przez Redis
```bash
# Opublikuj sztuczne zdarzenie unhealthy na kanale health_events
redis-cli -h 100.108.208.3 PUBLISH health_events '{
"type": "service_unhealthy",
"node": "piha",
"service": "mosquitto",
"severity": "error",
"payload": {"exit_code": 1, "reason": "OOMKilled"},
"timestamp": "2026-05-27T20:00:00Z"
}'
# Obserwuj logi planera
docker logs planner-agent -f
# Sprawdź czy propozycja trafiła do pending
ls /opt/homelab/actions/pending/
cat /opt/homelab/actions/pending/plan-piha-mosquitto-*.json
```
### Śledzenie metryk LLM
```bash
# Subskrybuj metryki routera w czasie rzeczywistym
redis-cli -h 100.108.208.3 SUBSCRIBE llm_router_metrics
```
---
## 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
```