---
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
```
