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>
This commit is contained in:
parent
ff6fda1f04
commit
5ccdfa0ca6
16
README.md
16
README.md
|
|
@ -13,6 +13,22 @@ The homelab consists of several nodes connected via a Tailscale internal mesh.
|
||||||
| **PIHA** | Infra Node | Core infrastructure services, automation, and monitoring. |
|
| **PIHA** | Infra Node | Core infrastructure services, automation, and monitoring. |
|
||||||
| **VPS** | Edge Node | Public ingress, reverse proxy, and edge services. |
|
| **VPS** | Edge Node | Public ingress, reverse proxy, and edge services. |
|
||||||
|
|
||||||
|
## Agent System
|
||||||
|
|
||||||
|
The homelab uses a multi-agent orchestration model with human-in-the-loop for destructive actions:
|
||||||
|
|
||||||
|
| Agent | Node | Role |
|
||||||
|
|-------|------|------|
|
||||||
|
| **stability-agent** | all nodes | Per-node watchdog — monitors Docker, disk, Tailscale, MQTT; emits events |
|
||||||
|
| **node-agent** | all nodes | Publishes container health events to Redis pub/sub |
|
||||||
|
| **observer** | VPS | Synthesizes world state from events into `/opt/homelab/world/*.json` |
|
||||||
|
| **supervisor** | VPS | Detects drift between desired and actual state; writes `pending` actions |
|
||||||
|
| **planner-agent** | SOLARIA | LLM-powered diagnosis — listens to Redis, proposes remediation actions |
|
||||||
|
| **executor** | VPS | Executes actions only after operator approval |
|
||||||
|
| **operator-ui** + **telegram-bot** | VPS / PIHA | Operator reviews and approves/rejects pending actions |
|
||||||
|
|
||||||
|
Action approval flow: `pending/` → operator approves → `approved/` → executor runs.
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
- `docs/`: [Infrastructure Standards](docs/standards.md) and [Deployment Conventions](docs/deployment.md).
|
- `docs/`: [Infrastructure Standards](docs/standards.md) and [Deployment Conventions](docs/deployment.md).
|
||||||
|
|
|
||||||
234
docs/sessions/2026-05-27-planner-agent.md
Normal file
234
docs/sessions/2026-05-27-planner-agent.md
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
# SESSION: Budowa planner-agent — LLM-based diagnostics
|
||||||
|
|
||||||
|
**DATA:** 2026-05-27
|
||||||
|
**REZULTAT:** planner-agent działa na SOLARIA (`healthy`), Ollama primary, cloud fallback gotowy do włączenia
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Co zostało zbudowane
|
||||||
|
|
||||||
|
### `services/planner-agent/src/llm_router.py`
|
||||||
|
|
||||||
|
Moduł LLM routing z local-first fallback chain:
|
||||||
|
|
||||||
|
- **`LLMRouter`** — główna klasa routingu przez litellm
|
||||||
|
- **`ModelConfig`** — konfiguracja jednego modelu (name, timeout, api_base, extra_kwargs)
|
||||||
|
- **`ModelMetrics`** — liczniki per model × outcome (`success`/`fallback`/`error`); success_rate
|
||||||
|
- **`RouteResult`** — wynik routingu z `content`, `model_used`, `attempts`, `latency_ms`
|
||||||
|
- **`AttemptRecord`** — zapis jednej próby (model, outcome, reason, latency_ms)
|
||||||
|
- **`_extract_json_from_fence()`** — wydobywa JSON z bloków ` ```json ``` ` jeśli model nie odpowie czystym JSON
|
||||||
|
|
||||||
|
Domyślny chain: `ollama/qwen2.5:7b` (8s) → `claude-haiku-4-5-20251001` (30s) → `claude-sonnet-4-6` (30s)
|
||||||
|
|
||||||
|
Metryki każdego wywołania publikowane na Redis kanał `llm_router_metrics`.
|
||||||
|
|
||||||
|
### `services/planner-agent/src/planner.py`
|
||||||
|
|
||||||
|
Główna pętla agenta:
|
||||||
|
|
||||||
|
- **`PlannerAgent`** — async agent: Redis sub → diagnoza LLM → pending action file → event
|
||||||
|
- **`HealthEvent`** — znormalizowane zdarzenie zdrowotne z Redis (node, service, event_type, severity, payload)
|
||||||
|
- **`ActionProposal`** — propozycja akcji z pełnymi metadanymi; `.to_action_file()` → format executora
|
||||||
|
- **`CooldownTracker`** — gate 5-minutowy per `svc_key` (node/service); NIE rejestruje jeśli LLM się wysypał
|
||||||
|
- **`parse_event()`** — normalizuje dwa formaty wejściowe (node-agent / control-plane)
|
||||||
|
- **`write_pending_action()`** — atomiczny zapis: `.tmp` → rename
|
||||||
|
- **`emit_event()`** — zapis zdarzenia `remediation_started` do systemu plików (bez importów z control-plane)
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
```
|
||||||
|
Redis msg → parse_event() → benign skip → cooldown gate → _propose_action() (LLM)
|
||||||
|
→ write_pending_action() → emit_event("remediation_started")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pliki towarzyszące
|
||||||
|
|
||||||
|
| Plik | Opis |
|
||||||
|
|------|------|
|
||||||
|
| `service.yaml` | Kontrakt operacyjny: owner_node=solaria, deps=redis+ollama, healthcheck=file |
|
||||||
|
| `docker-compose.yml` | env_file + extra_hosts:host-gateway + ANTHROPIC_API_KEY w environment |
|
||||||
|
| `Dockerfile` | python:3.11-slim, litellm, redis, jsonschema, structlog |
|
||||||
|
| `healthcheck.sh` | Sprawdza wiek pliku heartbeat (max 300s) |
|
||||||
|
| `requirements.txt` | litellm, redis, jsonschema, structlog |
|
||||||
|
| `tests/test_planner.py` | 49 testów jednostkowych |
|
||||||
|
| `tests/test_llm_router.py` | 34 testy jednostkowe |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kluczowe decyzje architektoniczne
|
||||||
|
|
||||||
|
### 1. HITL invariant (Human-in-the-loop)
|
||||||
|
|
||||||
|
Planner **wyłącznie** zapisuje do `actions/pending/`. Executor wymaga pliku w `actions/approved/`.
|
||||||
|
Planner nigdy nie wykona akcji samodzielnie — to fundamentalna zasada systemu.
|
||||||
|
|
||||||
|
Implementacja: `write_pending_action()` pisze do `pending/`, żadna ścieżka w kodzie nie dotyka `approved/`.
|
||||||
|
|
||||||
|
### 2. Cooldown gate
|
||||||
|
|
||||||
|
Per `svc_key` (= `node/service`), domyślnie 5 minut. Cel: nie zalewać operatora powtórzonymi
|
||||||
|
propozycjami dla tego samego serwisu.
|
||||||
|
|
||||||
|
**Kluczowa decyzja:** cooldown NIE jest rejestrowany jeśli cały chain LLM się wysypał.
|
||||||
|
Dzięki temu kolejne zdarzenie może spróbować ponownie, zamiast być cicho zablokowanym
|
||||||
|
przez 5 minut mimo że nie powstała żadna propozycja.
|
||||||
|
|
||||||
|
### 3. Fallback chain — local-first
|
||||||
|
|
||||||
|
Kolejność: Ollama (lokalny GPU) → Haiku → Sonnet.
|
||||||
|
|
||||||
|
Uzasadnienie:
|
||||||
|
- Ollama nie wysyła danych do zewnętrznych serwisów; niskie opóźnienie dla prostych przypadków
|
||||||
|
- Haiku = szybki i tani cloud fallback
|
||||||
|
- Sonnet = ostatnia deska ratunku dla trudnych przypadków
|
||||||
|
|
||||||
|
Odrzucenie modelu na podstawie: timeout, błąd sieci, wzorzec odmowy, invalid JSON, schema error.
|
||||||
|
|
||||||
|
### 4. Brak importów z control-plane
|
||||||
|
|
||||||
|
`services/planner-agent/` jest w pełni samodzielny. Nie importuje nic z
|
||||||
|
`services/control-plane/`. Emisja eventów jest implementowana lokalnie (kopia logiki
|
||||||
|
`scripts/lib/events.py`).
|
||||||
|
|
||||||
|
Uzasadnienie: planner musi działać nawet jeśli control-plane jest offline; oddzielne
|
||||||
|
cykl deploymentu.
|
||||||
|
|
||||||
|
### 5. structlog z PrintLoggerFactory
|
||||||
|
|
||||||
|
Nie używamy `structlog.stdlib.add_logger_name` — `PrintLogger` nie ma atrybutu `.name`.
|
||||||
|
Zamiast tego łańcuch procesorów: `add_log_level` → `TimeStamper` → `StackInfoRenderer`
|
||||||
|
→ `format_exc_info` → `JSONRenderer`.
|
||||||
|
|
||||||
|
### 6. NODE_NAME czytany w czasie wywołania, nie importu
|
||||||
|
|
||||||
|
`_emit_event_sync` czyta `NODE_NAME` z modułowego `NODE_NAME` przy każdym wywołaniu
|
||||||
|
(nie jako default parameter). Umożliwia patchowanie w testach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problemy napotkane i rozwiązania
|
||||||
|
|
||||||
|
### Problem: `localhost` w kontenerze nie sięga do hosta
|
||||||
|
|
||||||
|
**Kontekst:** Ollama działa na SOLARIA pod `localhost:11434`. Kontener Docker
|
||||||
|
z domyślną siecią bridge nie może sięgnąć do hosta przez `localhost`.
|
||||||
|
|
||||||
|
**Rozwiązanie:**
|
||||||
|
1. Dodano `extra_hosts: - "host-gateway:host-gateway"` do docker-compose.yml
|
||||||
|
2. `.env` używa `OLLAMA_HOST=http://host-gateway:11434`
|
||||||
|
|
||||||
|
### Problem: `environment` vs `env_file` — podwójne zmienne
|
||||||
|
|
||||||
|
**Kontekst:** Pierwsza wersja docker-compose.yml miała wszystkie zmienne hardkodowane
|
||||||
|
w sekcji `environment` z fallback wartościami (`${VAR:-default}`). Powodowało to
|
||||||
|
że `.env` był opcjonalny a nie wymagany.
|
||||||
|
|
||||||
|
**Rozwiązanie:** Usunięto wszystkie zmienne runtime z `environment`, przeniesiono do `env_file`.
|
||||||
|
Pozostał tylko `ANTHROPIC_API_KEY` w `environment` (opcjonalny sekret, nie powinien być w pliku na dysku).
|
||||||
|
|
||||||
|
### Problem: `structlog.stdlib.add_logger_name` crashuje z PrintLogger
|
||||||
|
|
||||||
|
**Symptom:** `AttributeError: 'PrintLogger' object has no attribute 'name'`
|
||||||
|
|
||||||
|
**Rozwiązanie:** Usunięto `add_logger_name` z łańcucha procesorów. Nie jest
|
||||||
|
kompatybilny z `PrintLoggerFactory`.
|
||||||
|
|
||||||
|
### Problem: verify stage failuje zaraz po starcie
|
||||||
|
|
||||||
|
**Symptom:** `deploy.sh` raportuje FAILED przy verify bo heartbeat nie istnieje.
|
||||||
|
|
||||||
|
**Przyczyna:** Race condition — agent potrzebuje kilku sekund na uruchomienie
|
||||||
|
pętli i pierwsze `touch()` heartbeatu.
|
||||||
|
|
||||||
|
**Rozwiązanie:** Nie jest to prawdziwy błąd. Docker healthcheck ma `start_period: 30s`.
|
||||||
|
Kontener pokazuje `(healthy)` po 30s od startu.
|
||||||
|
|
||||||
|
### Problem: git pull z divergent branches na solaria
|
||||||
|
|
||||||
|
**Symptom:** Solaria miała 2 lokalne commity nie będące na Forgejo + ręczne zmiany w working tree.
|
||||||
|
`git pull` failował z "Need to specify how to reconcile divergent branches."
|
||||||
|
|
||||||
|
**Rozwiązanie:**
|
||||||
|
```bash
|
||||||
|
git checkout -- services/planner-agent/docker-compose.yml # porzuć ręczne zmiany
|
||||||
|
git fetch origin
|
||||||
|
git rebase origin/master # rebase local commits on top of master
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status deploymentu na SOLARIA
|
||||||
|
|
||||||
|
```
|
||||||
|
Container: planner-agent Up ~30m (healthy)
|
||||||
|
Image: planner-agent-planner-agent
|
||||||
|
Node: solaria (100.100.231.104)
|
||||||
|
Heartbeat: /opt/homelab/state/planner-agent.heartbeat (age 0s)
|
||||||
|
|
||||||
|
Channels subscribed:
|
||||||
|
- health_events
|
||||||
|
- world_updates
|
||||||
|
|
||||||
|
LLM chain:
|
||||||
|
PRIMARY: ollama/qwen2.5-coder:14b @ http://host-gateway:11434
|
||||||
|
FALLBACK: claude-haiku-4-5-20251001 (disabled — brak ANTHROPIC_API_KEY)
|
||||||
|
FALLBACK: claude-sonnet-4-6 (disabled — brak ANTHROPIC_API_KEY)
|
||||||
|
|
||||||
|
Redis: redis://100.108.208.3:6379 ✓ connected
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Co zostało na później
|
||||||
|
|
||||||
|
### 1. ANTHROPIC_API_KEY — cloud fallback wyłączony
|
||||||
|
|
||||||
|
Haiku i Sonnet są skonfigurowane w chain ale nie mają klucza API.
|
||||||
|
Gdy Ollama nie da rady (złożony przypadek / timeout), chain się wysypie bez fallbacku.
|
||||||
|
|
||||||
|
Aby włączyć:
|
||||||
|
```bash
|
||||||
|
ssh oskar@100.100.231.104
|
||||||
|
echo "ANTHROPIC_API_KEY=sk-ant-..." >> /opt/homelab/config/planner-agent/.env
|
||||||
|
docker compose -f ~/homelab-codex-ws/services/planner-agent/docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. End-to-end test z prawdziwym eventem
|
||||||
|
|
||||||
|
Planner jest podłączony do Redis i nasłuchuje, ale żadne zdarzenie jeszcze nie
|
||||||
|
przeszło przez pełną ścieżkę (LLM call → pending action → operator UI).
|
||||||
|
|
||||||
|
Test:
|
||||||
|
```bash
|
||||||
|
redis-cli -h 100.108.208.3 PUBLISH health_events '{
|
||||||
|
"type": "service_unhealthy",
|
||||||
|
"node": "piha",
|
||||||
|
"service": "mosquitto",
|
||||||
|
"severity": "error",
|
||||||
|
"payload": {"reason": "container exited"},
|
||||||
|
"timestamp": "2026-05-27T20:00:00Z"
|
||||||
|
}'
|
||||||
|
# Obserwuj: docker logs planner-agent -f
|
||||||
|
# Sprawdź: ls /opt/homelab/actions/pending/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Solaria local commits
|
||||||
|
|
||||||
|
Solaria ma 2 lokalne commity (`feat: add ECC skills`, `fix: remove duplicate CLAUDE.md sections`)
|
||||||
|
które nie są na Forgejo. Zostały zrebase'owane na top of master ale nie wypchnięte.
|
||||||
|
Należy je wypchnąć lub zreviewować i ewentualnie squashować.
|
||||||
|
|
||||||
|
### 4. Integracja z operator UI / Telegram
|
||||||
|
|
||||||
|
Propozycje w `actions/pending/` nie mają jeszcze kanału notyfikacji do operatora.
|
||||||
|
Telegram bot powinien wysyłać powiadomienie gdy pojawi się nowy plik w `pending/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commity tej sesji
|
||||||
|
|
||||||
|
```
|
||||||
|
ff6fda1 planner-agent: use env_file, keep only ANTHROPIC_API_KEY in environment
|
||||||
|
ca37fca Add planner-agent: LLM-powered remediation planner
|
||||||
|
(llm_router.py, planner.py, tests, service.yaml, docker-compose.yml,
|
||||||
|
healthcheck.sh, Dockerfile)
|
||||||
|
```
|
||||||
235
services/planner-agent/README.md
Normal file
235
services/planner-agent/README.md
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
# 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
|
||||||
|
```
|
||||||
Loading…
Reference in a new issue