Skip to content

# TASK PHASE — AGENT PRESENCE INDICATORS (MVP)

Version: 1.0 Status: ACTIVE Target: DAARION.space (Next.js frontend + city-service)

## 1. Мета

Реалізувати Presence Indicators для всіх агентів у МВП: - Online/Offline статус з Matrix presence - DAGI Router heartbeat індикатори - Візуальні бейджі у всіх місцях де відображаються агенти

Правило онтології:

> Агенти мають бути "живими" — їх статус має бути видимим завжди

## 2. Область робіт

### 2.1. Сторінки де потрібні Presence Indicators

1. Agent List (`/agents`) — кожен агент у списку
2. Agent Cabinet (`/agents/:agentId`) — основний статус агента
3. Node Dashboard (`/nodes/:nodeId`) — Node Core Agents (Guardian/Steward)
4. MicroDAO Dashboard (`/microdao/:slug`) — Orchestrator + team agents
5. City Dashboard (`/city`) — civic agents (DAARWIZZ, DARIO, DARIA)

### 2.2. Типи Presence

#### Matrix Presence

- `online` — користувач активний (зелений)
- `unavailable` — користувач неактивний >5 хв (жовтий)
- `offline` — користувач офлайн (сірий)

#### DAGI Router Presence

- `healthy` — DAGI router відповідає (зелений)
- `degraded` — проблеми з router (жовтий)
- `offline` — router недоступний (червоний)

## 3. Backend Implementation

### 3.1. Додати Presence API

Додати до `services/city-service/routes_city.py`:

    @api_router.get("/agents/presence")
    async def get_agents_presence():
        """
        Отримати presence статус всіх активних агентів.
        """
        try:
            # Get agents from DB
            agents = await repo_city.list_agents_summaries(limit=1000)

            # Get Matrix presence from matrix-presence-aggregator
            matrix_presence = await get_matrix_presence_status()

            # Get DAGI router health
            dagi_health = await get_dagi_router_health()

            # Combine data
            presence_data = []
            for agent in agents:
                matrix_status = matrix_presence.get(agent["id"], "offline")
                dagi_status = dagi_health.get(agent["node_id"], {}).get("router_status", "unknown")

                presence_data.append({
                    "agent_id": agent["id"],
                    "matrix_presence": matrix_status,
                    "dagi_router_presence": dagi_status,
                    "last_seen": matrix_presence.get(f"{agent['id']}_last_seen"),
                    "node_id": agent.get("node_id")
                })

            return {"presence": presence_data}
        except Exception as e:
            logger.error(f"Failed to get agents presence: {e}")
            raise HTTPException(status_code=500, detail="Failed to get agents presence")

### 3.2. Інтегрувати з існуючими Presence системами

Використати: - `matrix-presence-aggregator` для Matrix presence - `city-service/presence_gateway.py` для forwarding - `usePresenceHeartbeat` hook для client-side heartbeats

## 4. Frontend Implementation

### 4.1. Presence Hook

Створити `apps/web/src/hooks/useAgentPresence.ts`:

    interface AgentPresence {
      agent_id: string;
      matrix_presence: 'online' | 'unavailable' | 'offline';
      dagi_router_presence: 'healthy' | 'degraded' | 'offline' | 'unknown';
      last_seen?: string;
      node_id?: string;
    }

    export function useAgentPresence(agentIds?: string[]) {
      const [presenceData, setPresenceData] = useState<Record<string, AgentPresence>>({});

      // Fetch presence data
      const fetchPresence = useCallback(async () => {
        try {
          const params = agentIds ? `?agent_ids=${agentIds.join(',')}` : '';
          const res = await fetch(`/api/v1/agents/presence${params}`);
          const data = await res.json();
          setPresenceData(data.presence);
        } catch (error) {
          console.error('Failed to fetch agent presence:', error);
        }
      }, [agentIds]);

      // Auto-refresh every 30 seconds
      useEffect(() => {
        fetchPresence();
        const interval = setInterval(fetchPresence, 30000);
        return () => clearInterval(interval);
      }, [fetchPresence]);

      return presenceData;
    }

### 4.2. Presence Badge Component

Створити `apps/web/src/components/ui/AgentPresenceBadge.tsx`:

    interface AgentPresenceBadgeProps {
      agentId: string;
      size?: 'sm' | 'md' | 'lg';
      showLabel?: boolean;
      className?: string;
    }

    export function AgentPresenceBadge({
      agentId,
      size = 'sm',
      showLabel = false,
      className
    }: AgentPresenceBadgeProps) {
      const presenceData = useAgentPresence([agentId]);
      const presence = presenceData[agentId];

      if (!presence) return null;

      const getStatusInfo = () => {
        const matrixStatus = presence.matrix_presence;
        const dagiStatus = presence.dagi_router_presence;

        // Priority: Matrix status, then DAGI status
        if (matrixStatus === 'online') {
          return { color: 'bg-emerald-500', label: 'Online' };
        } else if (matrixStatus === 'unavailable') {
          return { color: 'bg-amber-500', label: 'Away' };
        } else if (dagiStatus === 'healthy') {
          return { color: 'bg-blue-500', label: 'Healthy' };
        } else if (dagiStatus === 'degraded') {
          return { color: 'bg-orange-500', label: 'Degraded' };
        } else if (dagiStatus === 'offline') {
          return { color: 'bg-red-500', label: 'Offline' };
        } else {
          return { color: 'bg-gray-500', label: 'Unknown' };
        }
      };

      const statusInfo = getStatusInfo();
      const sizeClasses = {
        sm: 'w-2 h-2',
        md: 'w-3 h-3',
        lg: 'w-4 h-4'
      };

      return (
        <div className={cn('flex items-center gap-1.5', className)}>
          <div className={cn(
            'rounded-full border border-white/20',
            sizeClasses[size],
            statusInfo.color
          )} />
          {showLabel && (
            <span className="text-xs text-white/70">{statusInfo.label}</span>
          )}
        </div>
      );
    }

### 4.3. Інтеграція в Agent Cards

Додати Presence Badge до `apps/web/src/components/agent/AgentCard.tsx`:

    // Inside AgentCard component
    <AgentPresenceBadge
      agentId={agent.id}
      size="sm"
      showLabel={false}
      className="absolute top-2 right-2"
    />

## 5. Acceptance Criteria

1. Agent List (`/agents`)
2. Кожен агент має presence indicator у правому верхньому куті аватара
3. Зеленый = online, Жовтий = away, Сірий = offline

4. Індикатор оновлюється в реальному часі

5. Agent Cabinet (`/agents/:agentId`)

6. Великий presence badge у заголовку
7. Показує обидва статуси: Matrix + DAGI Router

8. Індикатор "Last seen: 5 min ago"

9. Node Dashboard (`/nodes/:nodeId`)

10. Guardian/Steward агенти мають presence indicators

11. Показується DAGI router статус ноди

12. MicroDAO Dashboard (`/microdao/:slug`)

13. Orchestrator агент має presence indicator

14. Team agents мають presence indicators

15. Real-time Updates

16. Presence оновлюється автоматично кожні 30 секунд
17. Не блокує рендеринг якщо API недоступне

## 6. Файли до створення

### Backend

- `services/city-service/routes_presence.py` — presence API endpoints
- Інтеграція з `matrix-presence-aggregator`
- Інтеграція з DAGI router health checks

### Frontend

- `apps/web/src/hooks/useAgentPresence.ts` — presence hook
- `apps/web/src/components/ui/AgentPresenceBadge.tsx` — presence badge component
- Інтеграція в усі agent-related компоненти

### Конфігурація

- Додати presence API до Next.js rewrites
- Оновити presence gateway для forwarding agent presence

## 7. Тестування

    # Перевірити API
    curl https://daarion.space/api/v1/agents/presence

    # Перевірити UI
    # Відкрити /agents - подивитися на presence indicators
    # Відкрити /agents/daarwizz - подивитися на великий badge
    # Відкрити /nodes/node-1-hetzner-gex44 - presence для Guardian

## 8. Подальше розширення (не в цьому MVP)

- Agent typing indicators у кімнатах
- Agent "working on task" статус
- Presence history & analytics
- Push notifications для presence changes
- Agent availability scheduling

Target Date: Today Priority: High (core UX improvement) Dependencies: Matrix presence aggregator, DAGI router health API