Safety & enforcement

Loop detection

Zelyx watches for repeating call patterns and can warn or block when an agent appears to be looping. The single biggest cause of unexpected AI spend is an agent that retries in a tight loop — loop detection catches this before the bill does.

How it works

For each call, Zelyx creates a fingerprint based on the session ID, model, and whether the call uses tools. It then counts how many calls with the same fingerprint have occurred within a rolling time window.

If the count reaches the configured threshold, loop detection triggers and takes the configured action (warn or block).

NoteLoop detection requires a session ID. Calls without X-Zelyx-Session-Id are grouped under a default per-company fingerprint, which has a much higher effective threshold. Set a session ID on agent calls to get accurate per-session detection.

Configuration

Configure loop detection in Settings Loop detection.

SettingDefaultDescription
EnabledOnToggle loop detection on or off for the workspace.
Threshold10 callsNumber of matching calls within the window before detection triggers.
Window60 secondsThe rolling window over which calls are counted.
ActionwarnWhat to do when a loop is detected. warn fires an alert but the call proceeds. block rejects the call (HTTP 429) and fires an alert.

Actions

ActionHTTPEffect
warn200 (call proceeds)Loop alert fires to all configured channels. The call is still forwarded to the provider. Useful to observe looping patterns before enforcing.
block429Call rejected. Budget for that call is released. Loop alert fires. The agent must handle the 429 and back off.

Handling a 429 in your agent

import time, httpx

for attempt in range(max_attempts):
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            extra_headers={"X-Zelyx-Session-Id": session_id},
        )
        break
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 429:
            # Loop detected — back off and re-evaluate
            time.sleep(10)
            messages = trim_messages(messages)   # reduce context, try again
        else:
            raise

Resetting a session counter

Loop counters are in-memory and reset automatically on server restart. If a legitimate burst of calls (e.g. parallel document processing) triggers loop detection, the counter resets by itself within the window — no manual action required. If you need an immediate reset, use a different session ID for the new job.

TipFor batch jobs that legitimately make many similar calls, use a unique session ID per batch (e.g. batch-{date}-{uuid}). This keeps batch traffic isolated from interactive session traffic and avoids false positives.