# RUNBOOK — вывод голосового помощника в продакшен

Пошаговая инструкция, чтобы всё завелось на мини-ПК (где развёрнут
`https://tablet.digital-home.site`). Рассчитана на то, что её выполняет
Claude-сессия **на мини-ПК** (`cosmo@192.168.31.60`), с пометками, что делается
на рабочей машине (Windows), а что — на мини-ПК.

## TL;DR — что это добавляет

1. **Локальный агент (intent+slots)** — sidecar-служба, распознаёт команды
   офлайн за ~2 мс и исполняет действия (таймеры, свет, музыка, очиститель),
   минуя облачный LLM. Инфо-запросы (погода/календарь) по-прежнему у LLM.
   Модель обучена: **интент 40/40, слоты 40/40**.
2. **Голосовое управление светом** — новый tool `control_light` (2 лампы).
3. **Смелый визуал** — обновлённая тема (космический индиго/aurora), деплоится
   с обычным билдом.
4. **Wake «Эй, космо»** — пайплайн переобучения готов и проверен end-to-end;
   боевая модель требует досбора данных (см. раздел D).

Всё под фиче-флагами: без `AGENT_URL` планшет работает ровно как раньше →
безопасно для kiosk.

---

## Изменения в репозитории (уже в коде)

| Файл | Что |
|---|---|
| `lib/tools/lights.ts` | новый tool `control_light` (свет через HA) |
| `lib/tools/_registry.ts` | регистрация `control_light` |
| `lib/agent-local.ts` | быстрый путь: вызов агент-службы + исполнение действий |
| `app/api/voice/chat/route.ts` | вставка fast-path (под `AGENT_URL`, с fallback) |
| `app/globals.css`, `app/page.tsx` | смелый визуал (токены/aurora) |
| `agent/` | sidecar агента: `Dockerfile`, `predict.py`, `joint_model_def.py`, `tablet_map.py`, `requirements.txt` |

⚠️ **Эти изменения надо закоммитить и запушить** (с рабочей машины), чтобы мини-ПК
их подтянул `git pull`. Веса моделей в git НЕ идут (переносятся отдельно, ниже).

---

## Архитектура продакшена

```
Планшет (Android, kiosk-браузер)
  ├─ wake «Эй, космо» (public/wake/cosmo.onnx, WASM) ─┐
  └─ UI (Next.js)                                      │
                                                       ▼
tablet-контейнер (мини-ПК, network coolify) ── /api/voice/chat
   │  fast-path: AGENT_URL задан?
   │    ├─ да → POST http://tablet-agent:8091/predict
   │    │        интент-действие + score≥0.7 → executeTool → короткий ответ
   │    └─ нет/инфо/none → облачный LLM (Anthropic/Groq) как раньше
   ▼
tablet-agent-контейнер (sidecar, network coolify)
   └─ predict.py --serve  (joint_model смонтирован томом)
```

---

## A. Агент (sidecar) — главный шаг

### A1. Перенести модель на мини-ПК (с рабочей машины)

Веса `joint_model/` (~115 МБ) в git нет. Скопировать на мини-ПК:

```bash
# на рабочей машине (Windows, Git Bash):
scp -i ~/.ssh/id_ed25519 -r "/d/Digital home/tablet-agent/joint_model" \
    cosmo@192.168.31.60:/opt/digital-home/agent-model
```

Должно получиться `/opt/digital-home/agent-model/joint.pt` (+ tokenizer, config).

> Переобучить модель заново (если правил `tablet-agent/templates.txt`):
> `cd tablet-agent && python train_joint.py && python test_joint.py`, затем scp.

### A2. Собрать и запустить sidecar (на мини-ПК)

```bash
cd /opt/digital-home/smart-home-tablet
git pull origin main                       # подтянуть код (после пуша с рабочей машины)

docker build -t tablet-agent:latest ./agent
docker rm -f tablet-agent || true
docker run -d --name tablet-agent \
  --network coolify \
  --restart unless-stopped \
  -v /opt/digital-home/agent-model:/app/joint_model \
  tablet-agent:latest

# проверка службы:
docker exec tablet-agent python -c "import urllib.request,json; \
  r=urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:8091/predict', \
  data=json.dumps({'text':'поставь таймер на 5 минут'}).encode(), \
  headers={'Content-Type':'application/json'})); print(r.read().decode())"
# ждём: {"intent":"timer_set","slots":{"duration":"5 минут"},"score":...,"tool":{...}}
```

### A3. Включить fast-path в планшете

Добавить в `/opt/digital-home/tablet.env`:

```
AGENT_URL=http://tablet-agent:8091
AGENT_THRESHOLD=0.7
```

Пере-задеплоить планшет (push любой коммит ИЛИ вручную пере-`docker run` по
`deploy.yml`). После рестарта `route.ts` увидит `AGENT_URL` и начнёт использовать
агента.

> Откат агента: убрать `AGENT_URL` из `tablet.env`, пере-деплой. Мгновенно вернёт
> облачный LLM на все команды. Контейнер `tablet-agent` можно оставить.

---

## B. Свет — реальные entity Home Assistant

`control_light` по умолчанию шлёт на `light.bedside` / `light.reading` (заглушки).
Указать реальные entity_id твоих ламп в `/opt/digital-home/tablet.env`:

```
LIGHT_ENTITIES_JSON={"bedside":"light.ВАШ_СВЕТ_У_КРОВАТИ","reading":"light.ВАША_ЛАМПА_ДЛЯ_ЧТЕНИЯ","default":"light.ВАШ_СВЕТ_У_КРОВАТИ"}
```

Найти id ламп: HA → Developer Tools → States, отфильтровать `light.`. Ключи
`bedside`/`reading` соответствуют фразам «свет у кровати» / «лампа для чтения»
(маппинг в `lib/tools/lights.ts`, `DEVICE_ALIASES`). Пере-деплой планшета.

Без этой переменной команды света пройдут как mock (HA вернёт success, лампа не
переключится) — то есть безопасно, но не управляет реальным светом.

---

## C. Дизайн (смелый визуал)

Ничего дополнительно — тема живёт в `app/globals.css` (токены) и деплоится
обычным билдом. Раскладка/логика не менялись, kiosk не затронут. Если визуал не
зайдёт — откат = `git revert` коммита с globals.css.

---

## D. Wake «Эй, космо» — статус и деплой

Проект `tablet-wake` (на рабочей машине). Пайплайн переобучения **готов и
проверен end-to-end**: smoke-модель разделяет фразу (pos 0.96 / neg 0.01) через
тот же WASM-рантайм, что у планшета. Но это **не боевая модель** — нужен досбор
данных:

1. Реальная речь в негативах (корпус Golos) — главный убийца ложняков.
2. Фон Android-мика (запись, где планшет стоит).
3. Held-out тест своим голосом + подбор порога.

Полный прогон (на рабочей машине) — см. `tablet-wake/README.md`. Итог —
`tablet-wake/out/cosmo.onnx`.

**Деплой wake-модели** (когда готова): заменить файл в репозитории и запушить —
инференс планшета НЕ меняется (окно 25 сохранено), только веса:

```bash
# на рабочей машине:
cp "/d/Digital home/tablet-wake/out/cosmo.onnx" \
   "/d/Digital home/smart-home-tablet/public/wake/cosmo.onnx"
# затем commit + push → Gitea пересоберёт планшет
```

Порог срабатывания подстраивается в клиенте (`WAKE_THRESHOLD` в
`components/VoiceController.tsx`). Также обнови фразу-подпись в UI: сейчас
«Космо» → «Эй, космо».

---

## Сводка новых переменных `tablet.env`

```
AGENT_URL=http://tablet-agent:8091          # включает локального агента
AGENT_THRESHOLD=0.7                          # порог уверенности (опц.)
LIGHT_ENTITIES_JSON={"bedside":"...","reading":"...","default":"..."}
```

---

## Проверка (после деплоя)

```bash
# планшет жив:
curl -sk -o /dev/null -w "%{http_code}\n" https://tablet.digital-home.site/

# агент отвечает изнутри сети:
docker exec tablet-yfh53kixpwkjlo4zibglx4n2 \
  wget -qO- --post-data='{"text":"выключи свет у кровати"}' \
  --header='Content-Type: application/json' http://tablet-agent:8091/predict

# логи планшета: fast-path пишет "[voice/chat] local-agent intent=..."
docker logs --tail 50 tablet-yfh53kixpwkjlo4zibglx4n2 | grep local-agent
```

Живой тест голосом: сказать «поставь таймер на 5 минут» (действие → агент,
мгновенно), затем «какая погода» (инфо → LLM). В логах у первой команды —
`local-agent`, у второй — обычный LLM-раунд.

---

## Откат всего

| Что | Откат |
|---|---|
| Агент | убрать `AGENT_URL` из `tablet.env`, пере-деплой |
| Свет | убрать `control_light` из `_registry.ts` ИЛИ просто не задавать `LIGHT_ENTITIES_JSON` |
| Дизайн | `git revert` коммита globals.css |
| Wake | вернуть прежний `public/wake/cosmo.onnx` из git-истории |

Каждая часть независима — можно катить и откатывать по отдельности.
