Skip to content

Execution Loop

flowchart LR
  A[Coolify starts\nagent-runner] --> B[Load config\nfrom Backend API]
  B --> C[Poll for active\nscheduled agents]
  C --> D{Agents found?}
  D -->|Yes| E[Register cron jobs\nfor each agent]
  D -->|No| F[Wait 60s]
  F --> C
  E --> G[Begin cron\nlistening]
  G --> H{Cron fires}
  H --> I[Execute agent]
  I --> H
sequenceDiagram
  participant CRON as Cron Job
  participant AR as Agent Runner
  participant BE as Backend :8080
  participant AIS as AI Service :9300
  participant DC as Discord

  CRON->>AR: Agent 42 fires
  AR->>BE: GET /api/v1/agents/42
  BE-->>AR: {systemPrompt, toolsAllowed, alertCondition}

  AR->>AR: Build execution context\n{startTime, agentId, workspaceId}

  AR->>AIS: POST /api/v1/chat/sync\n{message: suggestedQuery,\n systemPrompt,\n toolsAllowed}

  Note over AIS: AgentLoop runs\ntools called internally

  AIS-->>AR: {response, toolsUsed, tokenUsage}

  AR->>AR: Parse response\nExtract metrics

  AR->>BE: POST /api/v1/agents/42/executions\n{status, duration, toolsUsed,\n aiResponse, alertTriggered}

  alt Alert condition met
    AR->>AR: Evaluate alertCondition\ne.g. error_rate > 5%
    AR->>DC: POST webhook\n{embed with findings}
  end
interface AgentExecutionContext {
agentId: number;
workspaceId: string;
startedAt: Date;
systemPrompt: string;
toolsAllowed: string[];
suggestedQuery: string;
alertCondition?: {
metric: string; // e.g. "error_count", "avg_latency"
operator: 'gt' | 'lt' | 'eq';
threshold: number;
};
alertChannel?: string; // Discord webhook URL
}
interface AgentExecutionResult {
agentId: number;
workspaceId: string;
startedAt: Date;
finishedAt: Date;
status: 'success' | 'failed' | 'timeout';
durationMs: number;
toolsUsed: string[];
aiResponse: string;
alertTriggered: boolean;
alertValue?: number;
errorMessage?: string;
}

Agents have a 120-second timeout by default. If the AI Service doesn’t respond within that window:

  1. AbortController cancels the request
  2. Execution is logged as status: 'timeout'
  3. Agent state transitions to error
  4. Discord notification sent: “Agent 42 timed out after 120s”
Failure TypeRetry?Backoff
Network errorYes3 retries, exponential
LLM errorYes2 retries, 5s delay
Tool errorNo (LLM decides)—
TimeoutNoLog and move on

After each execution, the Agent Runner evaluates the alert condition against the AI response:

function evaluateAlert(
condition: AlertCondition,
aiResponse: string
): { triggered: boolean; value?: number } {
// Extract numeric value from AI response using regex
// e.g. "error rate: 7.3%" → 7.3
const extracted = extractMetric(aiResponse, condition.metric);
if (extracted === null) return { triggered: false };
const triggered =
condition.operator === 'gt' ? extracted > condition.threshold :
condition.operator === 'lt' ? extracted < condition.threshold :
extracted === condition.threshold;
return { triggered, value: extracted };
}

When an alert triggers, the Agent Runner posts a Discord embed:

🚨 Agent Alert: Payment Error Monitor
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Workspace: Logrite
Condition: error_rate > 5%
Detected: 7.3%
Agent Summary:
"In the last hour, I found 47 payment timeout
errors (7.3% error rate). Most affected: Stripe
webhook endpoint. Errors spiked at 14:23 UTC..."
[View Details] [Silence for 1h]
// Agent runner uses node-cron for schedule management
import cron from 'node-cron';
function registerAgent(agent: AgentConfig): void {
const task = cron.schedule(agent.schedule, async () => {
const result = await executeAgent(agent);
await storeResult(result);
await evaluateAndAlert(result, agent);
}, {
timezone: 'UTC'
});
activeTasks.set(agent.id, task);
}

The Agent Runner re-polls the Backend every 60 seconds to pick up:

  • Newly created agents
  • Schedule changes
  • Paused/deleted agents
setInterval(async () => {
const current = await fetchActiveAgents();
reconcileCronJobs(current, activeTasks);
}, 60_000);