+ Підключіть свій сервер або комп'ютер до мережі DAARION
+
+
+
+
+
+ {/* Info Card */}
+
+
+
+ Як це працює
+
+
+ Щоб нода з'явилась у каталозі, потрібно встановити Node Guardian на
+ вашому сервері або ноутбуці. Скрипт буде автоматично надсилати heartbeat до DAARION.city,
+ і ваша нода з'явиться в каталозі.
+
+
+
+ {/* Steps */}
+
+ {steps.map((step, index) => (
+
+
+
+ {index + 1}
+
+
+
{step.title}
+
{step.description}
+
+
+ {step.code}
+
+
+
+
+
+
+ ))}
+
+
+ {/* Requirements */}
+
+
Вимоги
+
+
+
+ Python 3.9+ з встановленим httpx
+
+
+
+ Доступ до інтернету для надсилання heartbeat
+
+
+
+ (Опціонально) Swapper service для AI моделей
+
+ );
+}
+
diff --git a/docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_ISOLATION_AND_AGENT_DISCOVERY_v1.md b/docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_ISOLATION_AND_AGENT_DISCOVERY_v1.md
new file mode 100644
index 00000000..c2663bf7
--- /dev/null
+++ b/docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_ISOLATION_AND_AGENT_DISCOVERY_v1.md
@@ -0,0 +1,192 @@
+# 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:
+
+- Перевірити змінні оточення:
+
+```bash
+echo $NODE_ID
+echo $SWAPPER_BASE_URL
+echo $DAGI_ROUTER_URL
+echo $CITY_API_BASE_URL
+```
+
+- Встановити для NODE2:
+
+```env
+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:
+
+```python
+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`.
+
+- Для кожної ноди:
+
+ 1. Читає `node_cache`:
+
+ ```sql
+ select * from node_cache where node_id = :node_id;
+ ```
+
+ 2. Викликає internal API:
+
+ - `/internal/node/{node_id}/swapper` → реальні моделі.
+ - `/internal/node/{node_id}/dagi-router/agents` → агенти Router vs DB.
+
+ 3. Читає таблицю `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` і переглянути два файли:
+ - `NODE_STATE_node-1-hetzner-gex44.md`
+ - `NODE_STATE_node-2-macbook-m4max.md`
+
+2. Переконатися, що:
+ - Swapper-моделі для NODE1 і NODE2 **різні**, відповідають реальним встановленим моделям на кожній ноді.
+ - Список агентів DAGI Router для NODE2 показує саме тих core-агентів, що були створені для NODE2.
+
+3. UI:
+ - `/nodes/node-1-hetzner-gex44` → Swapper + DAGI Router + агенти відповідають файлу NODE_STATE_NODE1.
+ - `/nodes/node-2-macbook-m4max` → Swapper + DAGI Router + агенти відповідають файлу NODE_STATE_NODE2.
+ - Тестові агенти в UI не відображаються (тільки бойові).
+
+---
+
+## 5. Результат
+
+- НОДА1 і НОДА2 повністю ізольовані з точки зору Swapper/DAGI Router.
+- У нас є документована «інвентаризація» моделей і агентів по нодах.
+- MVP показує тільки реальні метрики й реальних агентів, тестові не потрапляють у кабінети.
+
diff --git a/docs/tasks/TASK_PHASE_NODE_DIRECTORY_ADD_NODE_CTA_v1.md b/docs/tasks/TASK_PHASE_NODE_DIRECTORY_ADD_NODE_CTA_v1.md
new file mode 100644
index 00000000..c8df6871
--- /dev/null
+++ b/docs/tasks/TASK_PHASE_NODE_DIRECTORY_ADD_NODE_CTA_v1.md
@@ -0,0 +1,106 @@
+# TASK_PHASE_NODE_DIRECTORY_ADD_NODE_CTA_v1
+
+Мета: повернути та зафіксувати кнопку «Додати ноду» у Node Directory, щоб можна було запускати процес реєстрації нової ноди з інтерфейсу.
+
+---
+
+## 0. Поточний стан
+
+- Сторінка `/nodes` (Node Directory) відображає:
+ - Production / Development фільтри.
+ - Список нод або empty state «Наразі немає зареєстрованих нод».
+- Раніше була кнопка «Додати ноду», але після рефакторингів зникла.
+
+---
+
+## 1. UX / UI вимоги
+
+### 1.1. Розміщення
+
+- Сторінка `/nodes`:
+ - У верхній частині (праворуч від заголовка «Node Directory» або під фільтрами) додати кнопку:
+ - Текст: **«Додати ноду»**
+ - Іконка: server / plus (та сама стилістика, що й у кнопки «Створити MicroDAO»).
+
+### 1.2. Поведінка
+
+- Натискання → `router.push("/nodes/register")`.
+- Якщо `/nodes/register` ще не існує:
+ - Створити просту заглушку-сторінку з текстом:
+ - «Реєстрація нової ноди DAARION. На цьому етапі нода реєструється через встановлення Node Agent і heartbeat. Перегляньте інструкцію в docs/NODE_SETUP.md».
+- Кнопка показується тільки для авторизованих користувачів із ролями:
+ - `is_admin` або `is_orchestrator` (перевірити існуючу систему ролей і використати наявний хук, наприклад `useCurrentUser()`).
+
+### 1.3. Empty state
+
+- Якщо `nodes.length === 0`:
+ - Замість просто «Ноди не знайдені» показати:
+ - Текст: «Наразі немає жодної зареєстрованої ноди»;
+ - Під текстом — кнопку **«Додати першу ноду»** з таким самим handler'ом (`/nodes/register`).
+
+---
+
+## 2. Технічна реалізація
+
+### 2.1. Node Directory page
+
+Файл (ймовірно): `apps/web/src/app/nodes/page.tsx`.
+
+Зробити:
+
+- Імпортувати кнопку (Button component) та `useRouter`.
+- Додати кнопку в header:
+
+```tsx
+const router = useRouter();
+
+
+```
+
+- Обгорнути рендер у перевірку ролі користувача.
+
+### 2.2. Сторінка `/nodes/register`
+
+Файл: `apps/web/src/app/nodes/register/page.tsx`.
+
+Мінімум:
+
+```tsx
+export default function NodeRegisterPage() {
+ return (
+
+
Додати ноду
+
+ Щоб нода зʼявилась у каталозі, встановіть Node Agent на сервері / ноутбуці
+ і переконайтесь, що heartbeat надсилається до DAARION.city.
+