236 lines
6.4 KiB
Markdown
236 lines
6.4 KiB
Markdown
|
|
# 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).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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
|
|||
|
|
```
|