Skip to content

[Backend] Implement Agent Heartbeat Monitoring and Automatic Dead-Agent Cleanup #170

Description

@devJaja

🎯 Objective

Implement agent heartbeat monitoring and automatic dead-agent cleanup so that the agent registry reflects real-time online/offline status.


📁 Files to Modify

Action File Path Description
Modify backend/src/api/app.ts Add heartbeat endpoint and background cleanup job
Modify backend/src/db/agents.ts Add updateLastSeen and markOfflineStale queries
Modify backend/src/config/index.ts Add heartbeat and cleanup interval config
Modify backend/.env.example Document new config vars
Modify frontend/src/hooks/useAgentRegistry.ts Auto-refresh on status change

📁 Files to Create

Action File Path Description
Create backend/src/api/routes/agents.ts (extend) Heartbeat POST endpoint
Create backend/src/services/heartbeat.ts Background cleanup service

🔍 Current State

// db/agents.ts — lastSeenAt set once at registration, never updated
INSERT INTO agents (id, ..., lastSeenAt, status)
VALUES (?, ..., datetime('now'), 'online');
// status is always 'online' — never changes to 'offline'
// Frontend AgentFilterBar — status toggle exists but data is stale
const [statusFilter, setStatusFilter] = useState<'all' | 'online' | 'offline'>('all');

💥 Impact

  • Agents that go offline remain marked as online forever
  • Users see stale agent availability data
  • Coordinator may try to dispatch to dead agents
  • No automatic cleanup of abandoned agent registrations

✅ Expected Implementation

1. Add Heartbeat Endpoint (app.ts)

// POST /api/agents/:id/heartbeat
app.post('/api/agents/:id/heartbeat', async (req, res) => {
  const { id } = req.params;
  const agent = agentDb.getAgent(id);

  if (!agent) {
    return res.status(404).json({ error: 'Agent not found' });
  }

  // Update lastSeenAt and ensure status is online
  agentDb.updateLastSeen(id);

  res.json({ status: 'ok', lastSeenAt: new Date().toISOString() });
});

2. Add Database Methods (db/agents.ts)

export function updateLastSeen(agentId: string): void {
  db.prepare(`
    UPDATE agents
    SET lastSeenAt = datetime('now'),
        status = 'online'
    WHERE id = ?
  `).run(agentId);
}

export function markStaleAgents(staleThresholdMinutes: number = 5): number {
  const result = db.prepare(`
    UPDATE agents
    SET status = 'offline'
    WHERE status = 'online'
      AND datetime(lastSeenAt, '+' || ? || ' minutes') < datetime('now')
  `).run(staleThresholdMinutes);

  return result.changes;
}

export function deleteOfflineAgents(offlineThresholdHours: number = 24): number {
  const result = db.prepare(`
    DELETE FROM agents
    WHERE status = 'offline'
      AND datetime(lastSeenAt, '+' || ? || ' hours') < datetime('now')
  `).run(offlineThresholdHours);

  return result.changes;
}

3. Background Cleanup Service (services/heartbeat.ts)

import { markStaleAgents, deleteOfflineAgents } from '../db/agents';

const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes

export function startHeartbeatService() {
  setInterval(() => {
    const markedOffline = markStaleAgents(5); // 5 min threshold
    const deleted = deleteOfflineAgents(24);  // 24h threshold

    if (markedOffline > 0 || deleted > 0) {
      console.log(`Heartbeat cleanup: ${markedOffline} marked offline, ${deleted} deleted`);
    }
  }, CLEANUP_INTERVAL_MS);
}

4. Config (config/index.ts)

HEARTBEAT_INTERVAL_MS: z.coerce.number().default(300_000),  // 5 min
HEARTBEAT_STALE_THRESHOLD_MINUTES: z.coerce.number().default(5),
AGENT_OFFLINE_DELETE_HOURS: z.coerce.number().default(24),

📁 Reference Files

File Path Purpose
backend/src/api/app.ts Add heartbeat route
backend/src/db/agents.ts Add update/delete methods
backend/src/config/index.ts Add config vars
backend/.env.example Document config
frontend/src/hooks/useAgentRegistry.ts Auto-refresh
frontend/src/components/agents/AgentFilterBar.tsx Status filter UI

✅ Acceptance Criteria

  • Add POST /api/agents/:id/heartbeat endpoint
  • Add updateLastSeen(agentId) to db/agents.ts
  • Add markStaleAgents(thresholdMinutes) to mark offline
  • Add deleteOfflineAgents(thresholdHours) for cleanup
  • Create services/heartbeat.ts background service
  • Start heartbeat service on server boot
  • Add config vars for intervals
  • Update .env.example with new vars
  • Add rate limiting to heartbeat endpoint (prevent abuse)
  • Add test: agent marked offline after stale threshold
  • Add test: offline agents deleted after 24 hours
  • Update frontend to show real-time status

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions