Service Loops
Fast, gateway-managed execution loops for intensive goal phases.
Service Loops
Service loops are fast, in-process execution loops managed by the gateway. They're designed for intensive phases that need to run more frequently than cron allows — think real-time monitoring, rapid iteration, or time-sensitive optimization.
How Service Loops Work
Unlike cron (which has a minimum 1-minute granularity), service loops run as setInterval timers inside the gateway process:
- A loop is started for a specific goal + phase
- Each iteration enqueues a system event to the agent's session
- The system event triggers a heartbeat wake — the agent processes the event
- The agent reads its goal context, does work, and the loop fires again
Service loops do not run agent sessions directly. They use the existing system event + heartbeat wake infrastructure to deliver work to agents.
Starting a Loop
goals({
action: "start_loop",
goalId: "g_abc123",
phaseIndex: 1, // Which phase to loop
intervalMs: 60000, // Every 60 seconds
maxRuntimeMs: 28800000 // Stop after 8 hours (optional)
})Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
goalId | string | required | Goal to loop |
phaseIndex | number | 0 | Phase index within the goal |
intervalMs | number | 300000 (5m) | Interval between iterations |
maxRuntimeMs | number | 28800000 (8h) | Maximum total runtime |
Constraints
- Minimum interval: 10 seconds (
MIN_INTERVAL_MS = 10_000) - Maximum runtime: 8 hours default (
DEFAULT_MAX_RUNTIME_MS) - One loop per goal: Only one service loop can be active per goal ID
- Goal must exist: The goal must be in the store and have the specified phase
Stopping a Loop
goals({ action: "stop_loop", goalId: "g_abc123" })Loops also stop automatically when:
- The
maxRuntimeMsis reached (kill timer fires) - The goal is paused, failed, cancelled, or completed
- The gateway restarts (loops are in-memory only)
Loop State
Each active loop tracks:
{
running: true,
iterations: 42,
startedAt: "2026-02-27T12:00:00Z",
nextAtMs: 1740664800000 // Next iteration timestamp
}Service Loop vs Cron
| Feature | Service Loop | Cron |
|---|---|---|
| Minimum interval | 10 seconds | 1 minute |
| Maximum runtime | 8 hours (configurable) | Unlimited |
| Survives restart | No (in-memory) | Yes (persisted) |
| Managed by | goals/service-loop.js | goals/scheduler.js |
| Best for | Intensive, time-limited phases | Long-running periodic work |
Use service loops when:
- You need sub-minute granularity
- The phase is time-boxed (monitoring during a launch, etc.)
- Rapid iteration is important
Use cron when:
- The phase runs indefinitely (daily reports, weekly checks)
- You need to survive gateway restarts
- Interval is 1 minute or longer
Architecture
Service Loop Timer (setInterval)
└─ enqueueSystemEvent(agentId, "goal_loop_iteration", { goalId, phase, iteration })
└─ requestHeartbeatNow(agentId)
└─ Agent wakes up
└─ Reads goal context (injected by heartbeat-hook)
└─ Does work, updates metrics, checkpointsThe indirection through system events is important — it means the loop doesn't hold a reference to the agent session. The agent processes the event in its normal message handling flow.
Service loops are in-memory only and don't survive gateway restarts. For production workloads that must persist, use cron-based loop phases instead. The gateway's startup sync in goals/scheduler.js re-creates cron jobs for active loop phases but does not restart service loops.
Example: Real-Time Ad Monitoring
// Create goal with monitoring phase
goals({
action: "create",
goal: {
title: "Launch day ad monitoring",
agentId: "ad-ops",
phases: [
{ name: "Pre-launch check", type: "once" },
{ name: "Real-time monitoring", type: "loop" },
{ name: "Post-launch report", type: "once" }
],
budget: { maxCost: 25, approvalGate: 15 }
}
})
// After pre-launch phase completes, start the monitoring loop
goals({ action: "complete_phase", goalId: "g_abc123" })
goals({
action: "start_loop",
goalId: "g_abc123",
phaseIndex: 1,
intervalMs: 30000, // Every 30 seconds
maxRuntimeMs: 14400000 // 4 hours
})
// Agent receives system events every 30s, checks ad metrics,
// adjusts bids, pauses underperformers, etc.
// After 4 hours (or manual stop), complete the monitoring phase
goals({ action: "stop_loop", goalId: "g_abc123" })
goals({ action: "complete_phase", goalId: "g_abc123", result: "4h monitoring complete. CPA reduced 18%." })