Skip to content

# TASK_PHASE_NODE2_ROUTER_SWAPPER_ISOLATION_AND_AGENT_DISCOVERY_v1

Мета: 1) гарантувати, що НОДА2 використовує свій локальний Swapper та DAGI Router для метрик;
2) зібрати повний перелік моделей і агентів на обох нодах, розділити «бойових» і тестових;
3) зробити так, щоб у MVP показувались тільки реальні метрики та реальні агенти.

## 0. Симптоми / проблеми

- Swapper на NODE2 показує ті ж моделі, що на NODE1 (дані «змішані»).
- На NODE2 є реальні моделі (більші, ніж на NODE1) і створені агенти core-команди, але:
- частина з них періодично зникає з UI;
- зʼявляються старі тестові агенти.
- Очевидно, що:
- або `node-guardian-loop` на NODE2 стукає в Swapper/Router NODE1;
- або `fn_node_heartbeat` / `node_cache` перезаписують дані між нодами;
- або маршрути `/api/node-internal/{nodeId}/...` не ізолюють nodeId.

## 1. Інфра: повна ізоляція NODE2

### 1.1. Перевірити ENV та конфіг

На NODE2 знайти, чим стартує `node-guardian-loop.py` і DAGI Router / Swapper:

- Перевірити змінні оточення:

    echo $NODE_ID
    echo $SWAPPER_BASE_URL
    echo $DAGI_ROUTER_URL
    echo $CITY_API_BASE_URL

- Встановити для NODE2:

    NODE_ID=node-2-macbook-m4max
    SWAPPER_BASE_URL=http://127.0.0.1:8890       # локальний Swapper NODE2
    DAGI_ROUTER_URL=http://127.0.0.1:9102       # локальний DAGI Router NODE2
    CITY_API_BASE_URL=https://daarion.space/api/node-internal

### 1.2. Оновити node-guardian-loop.py

Переконатись, що скрипт використовує тільки ENV, а не захардкожені URL:

    NODE_ID = os.environ["NODE_ID"]
    SWAPPER_BASE_URL = os.environ["SWAPPER_BASE_URL"]
    DAGI_ROUTER_URL = os.environ.get("DAGI_ROUTER_URL")
    CITY_API_BASE_URL = os.environ["CITY_API_BASE_URL"]

- Для Swapper: використовувати `SWAPPER_BASE_URL` (не `http://swapper-service:8890` в коді).
- Для Router health (якщо є): використовувати `DAGI_ROUTER_URL`.

### 1.3. fn_node_heartbeat / node_cache

Перевірити SQL-функцію `fn_node_heartbeat`:

- Переконатися, що `update node_cache set ... where node_id = _node_id` (або аналог) і не перезаписує рядок іншої ноди.
- Додати короткий тест або скрипт, який:
- викликає `fn_node_heartbeat('node-1-...', ...)`;
- викликає `fn_node_heartbeat('node-2-...', ...)`;
- показує, що в `node_cache` два різні рядки з різними `swapper_state`.

## 2. Discovery: повний список моделей і агентів по нодах

### 2.1. Скрипт `scripts/discover_node_state.py`

Створити Python-скрипт, який:

- Приймає параметр `--node node-1-...` або `--node node-2-...` або `--all`.

- Для кожної ноди:

- Читає `node_cache`:

  `sql select * from node_cache where node_id = :node_id;`

- Викликає internal API:

  - `/internal/node/{node_id}/swapper` → реальні моделі.
  - `/internal/node/{node_id}/dagi-router/agents` → агенти Router vs DB.

- Читає таблицю `agents`:

  `sql select id, public_slug, kind, is_test, node_id, created_at, updated_at from agents where node_id = :node_id order by created_at;`

- Результат записує у:

- `docs/users/nodes/NODE_STATE_{node_id}.md`

Формат:

```md # Node {node_id} — State

## Swapper

- healthy: true
- models_loaded: X / Y

name | type | loaded | source
--- | --- | --- | ---
qwen2.5-7b-instruct | llm | true | swapper

## DAGI Router Agents

id | status | has_db_record | is_test | last_seen_at
--- | --- | --- | --- | ---
daarwizz | active | true | false | ...

## DB Agents (by node_id)

id | kind | is_test | public_slug
--- | --- | --- | ---
... |  |  |
``` |  |  |

Це стає «правдою» по реальному стану нод.

## 3. Прибирання тестових агентів і відфільтровування метрик

### 3.1. Маркування тестових агентів

- У таблиці `agents` вже є або додати поле `is_test boolean default false`.
- Міграція: позначити всі старі тестові агенти:
- за pattern'ами в `id` / `name` (`test_`, `demo_`, `sandbox_` і т.д.);
- або за списком, який уже є в документах (знайти в `docs/`).

### 3.2. Фільтрація на рівні API

- Для всіх endpointів, які використовуються в MVP (Node Cabinet, Agent Directory, DAGI Router Card):
- повертати тільки `is_test = false` за замовчуванням;
- додати параметр `include_test=true` тільки для внутрішніх / debug API.

Наприклад, у `get_dagi_router_agents_for_node`:

- при формуванні результату не включати `is_test=true` у таблицю для MVP.

### 3.3. Очищення «зомбі»-агентів

- Окрема міграція/скрипт, який:
- або архівує тестових агентів (`archived_at`),
- або видаляє їх,
- або переносить у окрему microDAO «Sandbox».

Головне — щоб вони не потрапляли в `router_total`/`system_total` метрик MVP.

## 4. Перевірка після ізоляції

Після оновлення:

1. Запустити `scripts/discover_node_state.py --all` і переглянути два файли:
2. `NODE_STATE_node-1-hetzner-gex44.md`

3. `NODE_STATE_node-2-macbook-m4max.md`

4. Переконатися, що:

5. Swapper-моделі для NODE1 і NODE2 різні, відповідають реальним встановленим моделям на кожній ноді.

6. Список агентів DAGI Router для NODE2 показує саме тих core-агентів, що були створені для NODE2.

7. UI:

8. `/nodes/node-1-hetzner-gex44` → Swapper + DAGI Router + агенти відповідають файлу NODE_STATE_NODE1.
9. `/nodes/node-2-macbook-m4max` → Swapper + DAGI Router + агенти відповідають файлу NODE_STATE_NODE2.
10. Тестові агенти в UI не відображаються (тільки бойові).

## 5. Результат

- НОДА1 і НОДА2 повністю ізольовані з точки зору Swapper/DAGI Router.
- У нас є документована «інвентаризація» моделей і агентів по нодах.
- MVP показує тільки реальні метрики й реальних агентів, тестові не потрапляють у кабінети.